Repository: tinygo-org/tinygo Branch: release Commit: db9f1182f5f2 Files: 1654 Total size: 5.3 MB Directory structure: gitextract_7h6a1yup/ ├── .circleci/ │ └── config.yml ├── .dockerignore ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ ├── build-macos.yml │ ├── docker.yml │ ├── linux.yml │ ├── llvm.yml │ ├── nix.yml │ ├── sizediff-install-pkgs.sh │ ├── sizediff.yml │ ├── tinygo-extract-version.sh │ └── windows.yml ├── .gitignore ├── .gitmodules ├── BUILDING.md ├── CHANGELOG.md ├── CODE-OF-CONDUCT.md ├── CONTRIBUTING.md ├── CONTRIBUTORS ├── Dockerfile ├── GNUmakefile ├── GOVERNANCE.md ├── LICENSE ├── README.md ├── bin/ │ └── .keep ├── builder/ │ ├── ar.go │ ├── bdwgc.go │ ├── build.go │ ├── builder_test.go │ ├── buildid.go │ ├── builtins.go │ ├── cc.go │ ├── cc1as.cpp │ ├── cc1as.h │ ├── cc_test.go │ ├── clang.cpp │ ├── commands.go │ ├── config.go │ ├── darwin-libsystem.go │ ├── elfpatch.go │ ├── error.go │ ├── esp.go │ ├── jobs.go │ ├── library.go │ ├── lld.cpp │ ├── mingw-w64.go │ ├── musl.go │ ├── nrfutil.go │ ├── objcopy.go │ ├── picolibc.go │ ├── size-report.go │ ├── size-report.html │ ├── sizes.go │ ├── sizes_test.go │ ├── tools-builtin.go │ ├── tools-external.go │ ├── tools.go │ ├── uf2.go │ ├── wasilibc.go │ └── wasmbuiltins.go ├── cgo/ │ ├── cgo.go │ ├── cgo_go122.go │ ├── cgo_test.go │ ├── const.go │ ├── const_test.go │ ├── libclang.go │ ├── libclang_config_llvm15.go │ ├── libclang_config_llvm16.go │ ├── libclang_config_llvm17.go │ ├── libclang_config_llvm18.go │ ├── libclang_config_llvm19.go │ ├── libclang_config_llvm20.go │ ├── libclang_stubs.c │ ├── security.go │ ├── security_test.go │ ├── sync.go │ └── testdata/ │ ├── basic.go │ ├── basic.out.go │ ├── const.go │ ├── const.out.go │ ├── errors.go │ ├── errors.out.go │ ├── flags.go │ ├── flags.out.go │ ├── include/ │ │ └── foo.h │ ├── symbols.go │ ├── symbols.out.go │ ├── types.go │ └── types.out.go ├── colorwriter.go ├── compileopts/ │ ├── config.go │ ├── options.go │ ├── options_test.go │ ├── target.go │ └── target_test.go ├── compiler/ │ ├── alias.go │ ├── asserts.go │ ├── atomic.go │ ├── calls.go │ ├── channel.go │ ├── compiler.go │ ├── compiler_test.go │ ├── defer.go │ ├── errors.go │ ├── func.go │ ├── gc.go │ ├── goroutine.go │ ├── inlineasm.go │ ├── interface.go │ ├── interrupt.go │ ├── intrinsics.go │ ├── ircheck/ │ │ ├── check.go │ │ └── errors.go │ ├── llvm.go │ ├── llvmutil/ │ │ └── llvm.go │ ├── map.go │ ├── sizes.go │ ├── symbol.go │ ├── syscall.go │ ├── testdata/ │ │ ├── basic.go │ │ ├── basic.ll │ │ ├── channel.go │ │ ├── channel.ll │ │ ├── defer-cortex-m-qemu.ll │ │ ├── defer.go │ │ ├── errors.go │ │ ├── float.go │ │ ├── float.ll │ │ ├── func.go │ │ ├── func.ll │ │ ├── gc.go │ │ ├── gc.ll │ │ ├── generics.go │ │ ├── generics.ll │ │ ├── go1.20.go │ │ ├── go1.20.ll │ │ ├── go1.21.go │ │ ├── go1.21.ll │ │ ├── goroutine-cortex-m-qemu-tasks.ll │ │ ├── goroutine-wasm-asyncify.ll │ │ ├── goroutine.go │ │ ├── interface.go │ │ ├── interface.ll │ │ ├── pointer.go │ │ ├── pointer.ll │ │ ├── pragma.go │ │ ├── pragma.ll │ │ ├── slice.go │ │ ├── slice.ll │ │ ├── string.go │ │ ├── string.ll │ │ ├── zeromap.go │ │ └── zeromap.ll │ └── volatile.go ├── corpus_test.go ├── diagnostics/ │ └── diagnostics.go ├── diff.go ├── docs/ │ ├── GNUmakefile │ ├── conf.py │ ├── index.rst │ ├── make.bat │ └── moved.rst ├── errors_test.go ├── flake.nix ├── go.mod ├── go.sum ├── goenv/ │ ├── goenv.go │ ├── tools-builtin.go │ ├── version.go │ └── version_test.go ├── hooks/ │ ├── README.md │ └── post_checkout ├── internal/ │ ├── tools/ │ │ ├── go.mod │ │ ├── go.sum │ │ └── tools.go │ └── wasm-tools/ │ ├── README.md │ ├── go.mod │ ├── go.sum │ └── tools.go ├── interp/ │ ├── README.md │ ├── compiler.go │ ├── errors.go │ ├── interp.go │ ├── interp_test.go │ ├── interpreter.go │ ├── memory.go │ └── testdata/ │ ├── alloc.ll │ ├── alloc.out.ll │ ├── basic.ll │ ├── basic.out.ll │ ├── consteval.ll │ ├── consteval.out.ll │ ├── interface.ll │ ├── interface.out.ll │ ├── phi.ll │ ├── phi.out.ll │ ├── revert.ll │ ├── revert.out.ll │ ├── slice-copy.ll │ └── slice-copy.out.ll ├── lib/ │ └── picolibc-stdio.c ├── loader/ │ ├── errors.go │ ├── goroot.go │ ├── list.go │ ├── loader.go │ ├── loader_go122.go │ └── ssa.go ├── main.go ├── main_test.go ├── misspell.csv ├── monitor.go ├── monitor_test.go ├── revive.toml ├── src/ │ ├── crypto/ │ │ ├── internal/ │ │ │ └── boring/ │ │ │ └── sig/ │ │ │ └── sig_other.go │ │ ├── rand/ │ │ │ ├── rand.go │ │ │ ├── rand_arc4random.go │ │ │ ├── rand_baremetal.go │ │ │ ├── rand_urandom.go │ │ │ ├── rand_windows.go │ │ │ └── util.go │ │ ├── tls/ │ │ │ ├── common.go │ │ │ ├── ticket.go │ │ │ └── tls.go │ │ └── x509/ │ │ └── internal/ │ │ └── macos/ │ │ └── macos.go │ ├── device/ │ │ ├── arm/ │ │ │ ├── arm.go │ │ │ ├── cortexm.S │ │ │ ├── interrupts.c │ │ │ ├── scb.go │ │ │ └── semihosting.go │ │ ├── arm64/ │ │ │ └── arm64.go │ │ ├── asm.go │ │ ├── esp/ │ │ │ ├── esp32.S │ │ │ ├── esp32c3.S │ │ │ └── esp8266.S │ │ ├── gba/ │ │ │ └── gba.go │ │ ├── nrf/ │ │ │ └── README.markdown │ │ ├── riscv/ │ │ │ ├── csr.go │ │ │ ├── handleinterrupt.S │ │ │ ├── riscv.go │ │ │ └── start.S │ │ └── tkey/ │ │ └── tkey.go │ ├── examples/ │ │ ├── adc/ │ │ │ └── adc.go │ │ ├── bench-goro/ │ │ │ └── bench.go │ │ ├── blinkm/ │ │ │ └── blinkm.go │ │ ├── blinky1/ │ │ │ └── blinky1.go │ │ ├── blinky2/ │ │ │ └── blinky2.go │ │ ├── button/ │ │ │ └── button.go │ │ ├── button2/ │ │ │ └── button2.go │ │ ├── can/ │ │ │ ├── feather-m4-can.go │ │ │ └── main.go │ │ ├── caninterrupt/ │ │ │ ├── feather-m4-can.go │ │ │ └── main.go │ │ ├── dac/ │ │ │ ├── circuitplay_express.go │ │ │ ├── dac.go │ │ │ └── pyportal.go │ │ ├── device-id/ │ │ │ └── main.go │ │ ├── echo/ │ │ │ └── echo.go │ │ ├── echo2/ │ │ │ └── echo2.go │ │ ├── empty/ │ │ │ └── main.go │ │ ├── flash/ │ │ │ └── main.go │ │ ├── gba-display/ │ │ │ └── gba-display.go │ │ ├── hello-wasm-unknown/ │ │ │ └── main.go │ │ ├── hid-joystick/ │ │ │ └── main.go │ │ ├── hid-keyboard/ │ │ │ └── main.go │ │ ├── hid-mouse/ │ │ │ └── main.go │ │ ├── i2c-target/ │ │ │ ├── main.go │ │ │ ├── main_feather_nrf52840.go │ │ │ └── main_feather_rp2040.go │ │ ├── i2s/ │ │ │ └── i2s.go │ │ ├── machinetest/ │ │ │ └── machinetest.go │ │ ├── mcp3008/ │ │ │ └── mcp3008.go │ │ ├── memstats/ │ │ │ └── memstats.go │ │ ├── microbit-blink/ │ │ │ └── microbit-blink.go │ │ ├── pdm/ │ │ │ └── pdm.go │ │ ├── pininterrupt/ │ │ │ ├── arduino.go │ │ │ ├── circuitplay-express.go │ │ │ ├── pca10040.go │ │ │ ├── pininterrupt.go │ │ │ ├── stm32.go │ │ │ └── wioterminal.go │ │ ├── pwm/ │ │ │ ├── arduino-mega1280.go │ │ │ ├── arduino.go │ │ │ ├── bluepill.go │ │ │ ├── feather-m4.go │ │ │ ├── itsybitsy-m0.go │ │ │ ├── itsybitsy-m4.go │ │ │ ├── nucleo-f722ze.go │ │ │ ├── nucleo-l031k6.go │ │ │ ├── nucleo-l432kc.go │ │ │ ├── nucleo-l552ze.go │ │ │ ├── pico.go │ │ │ ├── pwm.go │ │ │ └── stm32f4disco.go │ │ ├── ram-func/ │ │ │ └── main.go │ │ ├── rand/ │ │ │ └── main.go │ │ ├── rtcinterrupt/ │ │ │ └── rtcinterrupt.go │ │ ├── serial/ │ │ │ └── serial.go │ │ ├── systick/ │ │ │ ├── README.md │ │ │ └── systick.go │ │ ├── temp/ │ │ │ └── temp.go │ │ ├── time-offset/ │ │ │ └── time-offset.go │ │ ├── uart/ │ │ │ └── uart.go │ │ ├── usb-midi/ │ │ │ └── main.go │ │ ├── usb-storage/ │ │ │ └── main.go │ │ ├── wasm/ │ │ │ ├── .gitignore │ │ │ ├── GNUmakefile │ │ │ ├── README.md │ │ │ ├── callback/ │ │ │ │ ├── index.html │ │ │ │ ├── wasm.go │ │ │ │ └── wasm.js │ │ │ ├── export/ │ │ │ │ ├── index.html │ │ │ │ ├── wasm.go │ │ │ │ └── wasm.js │ │ │ ├── invoke/ │ │ │ │ ├── index.html │ │ │ │ ├── wasm.go │ │ │ │ └── wasm.js │ │ │ ├── main/ │ │ │ │ ├── README.md │ │ │ │ ├── index.html │ │ │ │ └── main.go │ │ │ ├── server.go │ │ │ └── slices/ │ │ │ ├── index.html │ │ │ ├── wasm.go │ │ │ └── wasm.js │ │ └── watchdog/ │ │ └── main.go │ ├── internal/ │ │ ├── abi/ │ │ │ ├── abi.go │ │ │ ├── escape.go │ │ │ ├── funcpc.go │ │ │ └── type.go │ │ ├── binary/ │ │ │ └── binary.go │ │ ├── bytealg/ │ │ │ └── bytealg.go │ │ ├── cm/ │ │ │ ├── abi.go │ │ │ ├── case.go │ │ │ ├── docs.go │ │ │ ├── empty.s │ │ │ ├── error.go │ │ │ ├── error.wasm.go │ │ │ ├── future.go │ │ │ ├── hostlayout_go122.go │ │ │ ├── hostlayout_go123.go │ │ │ ├── list.go │ │ │ ├── option.go │ │ │ ├── resource.go │ │ │ ├── result.go │ │ │ ├── stream.go │ │ │ ├── tuple.go │ │ │ └── variant.go │ │ ├── futex/ │ │ │ ├── futex.go │ │ │ ├── futex_darwin.c │ │ │ └── futex_linux.c │ │ ├── fuzz/ │ │ │ └── fuzz.go │ │ ├── gclayout/ │ │ │ └── gclayout.go │ │ ├── reflectlite/ │ │ │ ├── deepequal.go │ │ │ ├── endian_big.go │ │ │ ├── endian_little.go │ │ │ ├── strconv.go │ │ │ ├── swapper.go │ │ │ ├── type.go │ │ │ ├── value.go │ │ │ └── visiblefields.go │ │ ├── syscall/ │ │ │ └── unix/ │ │ │ ├── constants.go │ │ │ ├── eaccess.go │ │ │ └── getrandom.go │ │ ├── task/ │ │ │ ├── atomic-cooperative.go │ │ │ ├── atomic-preemptive.go │ │ │ ├── darwin.go │ │ │ ├── futex-cooperative.go │ │ │ ├── futex-cores.go │ │ │ ├── futex-threads.go │ │ │ ├── gc_stack_chain.go │ │ │ ├── gc_stack_noop.go │ │ │ ├── linux.go │ │ │ ├── mutex-cooperative.go │ │ │ ├── mutex-preemptive.go │ │ │ ├── pmutex-cooperative.go │ │ │ ├── pmutex-preemptive.go │ │ │ ├── queue.go │ │ │ ├── semaphore.go │ │ │ ├── task.go │ │ │ ├── task_asyncify.go │ │ │ ├── task_asyncify_wasm.S │ │ │ ├── task_none.go │ │ │ ├── task_stack.go │ │ │ ├── task_stack_386.S │ │ │ ├── task_stack_386.go │ │ │ ├── task_stack_amd64.S │ │ │ ├── task_stack_amd64.go │ │ │ ├── task_stack_amd64_windows.S │ │ │ ├── task_stack_amd64_windows.go │ │ │ ├── task_stack_arm.S │ │ │ ├── task_stack_arm.go │ │ │ ├── task_stack_arm64.S │ │ │ ├── task_stack_arm64.go │ │ │ ├── task_stack_avr.S │ │ │ ├── task_stack_avr.go │ │ │ ├── task_stack_cortexm.S │ │ │ ├── task_stack_cortexm.c │ │ │ ├── task_stack_cortexm.go │ │ │ ├── task_stack_esp32.S │ │ │ ├── task_stack_esp32.go │ │ │ ├── task_stack_esp8266.S │ │ │ ├── task_stack_esp8266.go │ │ │ ├── task_stack_mipsx.S │ │ │ ├── task_stack_mipsx.go │ │ │ ├── task_stack_multicore.go │ │ │ ├── task_stack_tinygoriscv.S │ │ │ ├── task_stack_tinygoriscv.go │ │ │ ├── task_stack_unicore.go │ │ │ ├── task_threads.c │ │ │ └── task_threads.go │ │ └── wasi/ │ │ ├── cli/ │ │ │ └── v0.2.0/ │ │ │ ├── command/ │ │ │ │ └── command.wit.go │ │ │ ├── environment/ │ │ │ │ ├── empty.s │ │ │ │ ├── environment.wasm.go │ │ │ │ └── environment.wit.go │ │ │ ├── exit/ │ │ │ │ ├── empty.s │ │ │ │ ├── exit.wasm.go │ │ │ │ └── exit.wit.go │ │ │ ├── run/ │ │ │ │ ├── empty.s │ │ │ │ ├── run.exports.go │ │ │ │ ├── run.wasm.go │ │ │ │ └── run.wit.go │ │ │ ├── stderr/ │ │ │ │ ├── empty.s │ │ │ │ ├── stderr.wasm.go │ │ │ │ └── stderr.wit.go │ │ │ ├── stdin/ │ │ │ │ ├── empty.s │ │ │ │ ├── stdin.wasm.go │ │ │ │ └── stdin.wit.go │ │ │ ├── stdout/ │ │ │ │ ├── empty.s │ │ │ │ ├── stdout.wasm.go │ │ │ │ └── stdout.wit.go │ │ │ ├── terminal-input/ │ │ │ │ ├── empty.s │ │ │ │ ├── terminal-input.wasm.go │ │ │ │ └── terminal-input.wit.go │ │ │ ├── terminal-output/ │ │ │ │ ├── empty.s │ │ │ │ ├── terminal-output.wasm.go │ │ │ │ └── terminal-output.wit.go │ │ │ ├── terminal-stderr/ │ │ │ │ ├── empty.s │ │ │ │ ├── terminal-stderr.wasm.go │ │ │ │ └── terminal-stderr.wit.go │ │ │ ├── terminal-stdin/ │ │ │ │ ├── empty.s │ │ │ │ ├── terminal-stdin.wasm.go │ │ │ │ └── terminal-stdin.wit.go │ │ │ └── terminal-stdout/ │ │ │ ├── empty.s │ │ │ ├── terminal-stdout.wasm.go │ │ │ └── terminal-stdout.wit.go │ │ ├── clocks/ │ │ │ └── v0.2.0/ │ │ │ ├── monotonic-clock/ │ │ │ │ ├── empty.s │ │ │ │ ├── monotonic-clock.wasm.go │ │ │ │ └── monotonic-clock.wit.go │ │ │ └── wall-clock/ │ │ │ ├── empty.s │ │ │ ├── wall-clock.wasm.go │ │ │ └── wall-clock.wit.go │ │ ├── filesystem/ │ │ │ └── v0.2.0/ │ │ │ ├── preopens/ │ │ │ │ ├── empty.s │ │ │ │ ├── preopens.wasm.go │ │ │ │ └── preopens.wit.go │ │ │ └── types/ │ │ │ ├── abi.go │ │ │ ├── empty.s │ │ │ ├── types.wasm.go │ │ │ └── types.wit.go │ │ ├── io/ │ │ │ └── v0.2.0/ │ │ │ ├── error/ │ │ │ │ ├── empty.s │ │ │ │ ├── error.wasm.go │ │ │ │ └── error.wit.go │ │ │ ├── poll/ │ │ │ │ ├── empty.s │ │ │ │ ├── poll.wasm.go │ │ │ │ └── poll.wit.go │ │ │ └── streams/ │ │ │ ├── empty.s │ │ │ ├── streams.wasm.go │ │ │ └── streams.wit.go │ │ ├── random/ │ │ │ └── v0.2.0/ │ │ │ ├── insecure/ │ │ │ │ ├── empty.s │ │ │ │ ├── insecure.wasm.go │ │ │ │ └── insecure.wit.go │ │ │ ├── insecure-seed/ │ │ │ │ ├── empty.s │ │ │ │ ├── insecure-seed.wasm.go │ │ │ │ └── insecure-seed.wit.go │ │ │ └── random/ │ │ │ ├── empty.s │ │ │ ├── random.wasm.go │ │ │ └── random.wit.go │ │ └── sockets/ │ │ └── v0.2.0/ │ │ ├── instance-network/ │ │ │ ├── empty.s │ │ │ ├── instance-network.wasm.go │ │ │ └── instance-network.wit.go │ │ ├── ip-name-lookup/ │ │ │ ├── abi.go │ │ │ ├── empty.s │ │ │ ├── ip-name-lookup.wasm.go │ │ │ └── ip-name-lookup.wit.go │ │ ├── network/ │ │ │ ├── abi.go │ │ │ ├── empty.s │ │ │ ├── network.wasm.go │ │ │ └── network.wit.go │ │ ├── tcp/ │ │ │ ├── abi.go │ │ │ ├── empty.s │ │ │ ├── tcp.wasm.go │ │ │ └── tcp.wit.go │ │ ├── tcp-create-socket/ │ │ │ ├── empty.s │ │ │ ├── tcp-create-socket.wasm.go │ │ │ └── tcp-create-socket.wit.go │ │ ├── udp/ │ │ │ ├── abi.go │ │ │ ├── empty.s │ │ │ ├── udp.wasm.go │ │ │ └── udp.wit.go │ │ └── udp-create-socket/ │ │ ├── empty.s │ │ ├── udp-create-socket.wasm.go │ │ └── udp-create-socket.wit.go │ ├── machine/ │ │ ├── adc.go │ │ ├── board_adafruit-esp32-feather-v2.go │ │ ├── board_ae_rp2040.go │ │ ├── board_arduino.go │ │ ├── board_arduino_leonardo.go │ │ ├── board_arduino_mega1280.go │ │ ├── board_arduino_mega2560.go │ │ ├── board_arduino_mkr1000.go │ │ ├── board_arduino_mkrwifi1010.go │ │ ├── board_arduino_nano.go │ │ ├── board_arduino_nano33.go │ │ ├── board_arduino_zero.go │ │ ├── board_atmega328p.go │ │ ├── board_atmega328pb.go │ │ ├── board_atsamd21.go │ │ ├── board_atsame54-xpro.go │ │ ├── board_badger2040-w.go │ │ ├── board_badger2040.go │ │ ├── board_bluemicro840.go │ │ ├── board_bluepill.go │ │ ├── board_btt_skr_pico.go │ │ ├── board_challenger_rp2040.go │ │ ├── board_circuitplay_bluefruit.go │ │ ├── board_circuitplay_express.go │ │ ├── board_clue_alpha.go │ │ ├── board_digispark.go │ │ ├── board_elecrow-rp2040-w5.go │ │ ├── board_elecrow-rp2350-w5.go │ │ ├── board_esp-c3-32s-kit.go │ │ ├── board_esp32-c3-devkit-rust-1.go │ │ ├── board_esp32-coreboard-v2.go │ │ ├── board_esp32c3-12f.go │ │ ├── board_esp32c3-supermini.go │ │ ├── board_fe310.go │ │ ├── board_feather-m0-express.go │ │ ├── board_feather-m0.go │ │ ├── board_feather-m4-can.go │ │ ├── board_feather-m4.go │ │ ├── board_feather-nrf52840-sense.go │ │ ├── board_feather-nrf52840.go │ │ ├── board_feather-stm32f405.go │ │ ├── board_feather_rp2040.go │ │ ├── board_gemma-m0.go │ │ ├── board_gnse.go │ │ ├── board_gopher-arcade.go │ │ ├── board_gopher-badge.go │ │ ├── board_grandcentral-m4.go │ │ ├── board_hifive1b.go │ │ ├── board_hifive1b_baremetal.go │ │ ├── board_hw-651.go │ │ ├── board_itsybitsy-m0.go │ │ ├── board_itsybitsy-m4.go │ │ ├── board_itsybitsy-nrf52840.go │ │ ├── board_k210.go │ │ ├── board_kb2040.go │ │ ├── board_lgt92.go │ │ ├── board_lorae5.go │ │ ├── board_m5paper.go │ │ ├── board_m5stack.go │ │ ├── board_m5stack_core2.go │ │ ├── board_m5stamp_c3.go │ │ ├── board_m5stick_c.go │ │ ├── board_macropad-rp2040.go │ │ ├── board_maixbit.go │ │ ├── board_maixbit_baremetal.go │ │ ├── board_makerfabs-esp32c3spi35.go │ │ ├── board_matrixportal-m4.go │ │ ├── board_mch2022.go │ │ ├── board_mdbt50qrx.go │ │ ├── board_metro-m4-airlift.go │ │ ├── board_metro_rp2350.go │ │ ├── board_microbit-v2.go │ │ ├── board_microbit.go │ │ ├── board_mksnanov3.go │ │ ├── board_nano-33-ble.go │ │ ├── board_nano-rp2040.go │ │ ├── board_nicenano.go │ │ ├── board_nodemcu.go │ │ ├── board_nrf51.go │ │ ├── board_nrf52840-mdk-usb-dongle.go │ │ ├── board_nrf52840-mdk.go │ │ ├── board_nrf52840.go │ │ ├── board_nrf52840_generic.go │ │ ├── board_nucleof103rb.go │ │ ├── board_nucleof722ze.go │ │ ├── board_nucleol031k6.go │ │ ├── board_nucleol432kc.go │ │ ├── board_nucleol476rg.go │ │ ├── board_nucleol552ze.go │ │ ├── board_nucleowl55jc.go │ │ ├── board_p1am-100.go │ │ ├── board_particle_argon.go │ │ ├── board_particle_boron.go │ │ ├── board_particle_xenon.go │ │ ├── board_pca10031.go │ │ ├── board_pca10040.go │ │ ├── board_pca10056.go │ │ ├── board_pca10059.go │ │ ├── board_pga2350.go │ │ ├── board_pico.go │ │ ├── board_pico2.go │ │ ├── board_pico2_ice.go │ │ ├── board_pico_plus2.go │ │ ├── board_pinetime.go │ │ ├── board_pybadge.go │ │ ├── board_pygamer.go │ │ ├── board_pyportal.go │ │ ├── board_qtpy.go │ │ ├── board_qtpy_esp32c3.go │ │ ├── board_qtpy_rp2040.go │ │ ├── board_rak4631.go │ │ ├── board_reelboard.go │ │ ├── board_stm32f469disco.go │ │ ├── board_stm32f4disco.go │ │ ├── board_stm32l0x1.go │ │ ├── board_swan.go │ │ ├── board_teensy36.go │ │ ├── board_teensy40.go │ │ ├── board_teensy41.go │ │ ├── board_thingplus_rp2040.go │ │ ├── board_thumby.go │ │ ├── board_tiny2350.go │ │ ├── board_trinket.go │ │ ├── board_trinkey_qt2040.go │ │ ├── board_tufty2040.go │ │ ├── board_waveshare-rp2040-zero.go │ │ ├── board_waveshare_rp2040_tiny.go │ │ ├── board_wioterminal.go │ │ ├── board_x9pro.go │ │ ├── board_xiao-ble.go │ │ ├── board_xiao-esp32c3.go │ │ ├── board_xiao-esp32s3.go │ │ ├── board_xiao-rp2040.go │ │ ├── board_xiao.go │ │ ├── buffer.go │ │ ├── buffer_atmega.go │ │ ├── buffer_generic.go │ │ ├── deviceid.go │ │ ├── flash.go │ │ ├── i2c.go │ │ ├── i2s.go │ │ ├── machine.go │ │ ├── machine_atmega.go │ │ ├── machine_atmega1280.go │ │ ├── machine_atmega1284p.go │ │ ├── machine_atmega2560.go │ │ ├── machine_atmega328.go │ │ ├── machine_atmega328p.go │ │ ├── machine_atmega328p_simulator.go │ │ ├── machine_atmega328pb.go │ │ ├── machine_atmega32u4.go │ │ ├── machine_atsam.go │ │ ├── machine_atsamd21.go │ │ ├── machine_atsamd21_simulator.go │ │ ├── machine_atsamd21_usb.go │ │ ├── machine_atsamd21e18.go │ │ ├── machine_atsamd21g18.go │ │ ├── machine_atsamd51.go │ │ ├── machine_atsamd51_usb.go │ │ ├── machine_atsamd51g19.go │ │ ├── machine_atsamd51j19.go │ │ ├── machine_atsamd51j20.go │ │ ├── machine_atsamd51p19.go │ │ ├── machine_atsamd51p20.go │ │ ├── machine_atsame51j19.go │ │ ├── machine_atsame54p20.go │ │ ├── machine_atsame5x_can.go │ │ ├── machine_attiny1616.go │ │ ├── machine_attiny85.go │ │ ├── machine_avr.go │ │ ├── machine_avrtiny.go │ │ ├── machine_cortexm.go │ │ ├── machine_esp32.go │ │ ├── machine_esp32_i2c.go │ │ ├── machine_esp32c3.go │ │ ├── machine_esp32c3_i2c.go │ │ ├── machine_esp32c3_spi.go │ │ ├── machine_esp32s3.go │ │ ├── machine_esp8266.go │ │ ├── machine_fe310.go │ │ ├── machine_fe310_simulator.go │ │ ├── machine_gameboyadvance.go │ │ ├── machine_generic.go │ │ ├── machine_generic_peripherals.go │ │ ├── machine_k210.go │ │ ├── machine_mimxrt1062.go │ │ ├── machine_mimxrt1062_i2c.go │ │ ├── machine_mimxrt1062_spi.go │ │ ├── machine_mimxrt1062_uart.go │ │ ├── machine_nrf.go │ │ ├── machine_nrf51.go │ │ ├── machine_nrf51_simulator.go │ │ ├── machine_nrf52.go │ │ ├── machine_nrf52833.go │ │ ├── machine_nrf52840.go │ │ ├── machine_nrf52840_enter_bootloader.go │ │ ├── machine_nrf52840_lfxtal_false.go │ │ ├── machine_nrf52840_lfxtal_true.go │ │ ├── machine_nrf52840_simulator.go │ │ ├── machine_nrf52840_usb.go │ │ ├── machine_nrf52840_usb_reset_bossa.go │ │ ├── machine_nrf52840_usb_reset_none.go │ │ ├── machine_nrf52840_usb_reset_uf2.go │ │ ├── machine_nrf528xx.go │ │ ├── machine_nrf52xxx.go │ │ ├── machine_nrf5x.go │ │ ├── machine_nrf_bare.go │ │ ├── machine_nrf_sd.go │ │ ├── machine_nxpmk66f18.go │ │ ├── machine_nxpmk66f18_uart.go │ │ ├── machine_rp2.go │ │ ├── machine_rp2040_rom.go │ │ ├── machine_rp2040_rtc.go │ │ ├── machine_rp2040_simulator.go │ │ ├── machine_rp2040_usb.go │ │ ├── machine_rp2040_usb_fix_usb_device_enumeration.go │ │ ├── machine_rp2350_rom.go │ │ ├── machine_rp2350_usb.go │ │ ├── machine_rp2_2040.go │ │ ├── machine_rp2_2350.go │ │ ├── machine_rp2_2350a.go │ │ ├── machine_rp2_2350b.go │ │ ├── machine_rp2_adc.go │ │ ├── machine_rp2_clocks.go │ │ ├── machine_rp2_flash.go │ │ ├── machine_rp2_gpio.go │ │ ├── machine_rp2_i2c.go │ │ ├── machine_rp2_pins.go │ │ ├── machine_rp2_pll.go │ │ ├── machine_rp2_pwm.go │ │ ├── machine_rp2_resets.go │ │ ├── machine_rp2_rng.go │ │ ├── machine_rp2_spi.go │ │ ├── machine_rp2_sync.go │ │ ├── machine_rp2_timer.go │ │ ├── machine_rp2_uart.go │ │ ├── machine_rp2_usb.go │ │ ├── machine_rp2_watchdog.go │ │ ├── machine_rp2_xosc.go │ │ ├── machine_stm32.go │ │ ├── machine_stm32_adc_f1.go │ │ ├── machine_stm32_adc_f4.go │ │ ├── machine_stm32_exti_afio.go │ │ ├── machine_stm32_exti_exti.go │ │ ├── machine_stm32_exti_syscfg.go │ │ ├── machine_stm32_exti_syscfg_noenable.go │ │ ├── machine_stm32_flash.go │ │ ├── machine_stm32_gpio_reva.go │ │ ├── machine_stm32_gpio_revb.go │ │ ├── machine_stm32_gpio_revb_mp.go │ │ ├── machine_stm32_i2c_reva.go │ │ ├── machine_stm32_i2c_revb.go │ │ ├── machine_stm32_iwdg.go │ │ ├── machine_stm32_moder_gpio.go │ │ ├── machine_stm32_rng.go │ │ ├── machine_stm32_spi.go │ │ ├── machine_stm32_tim.go │ │ ├── machine_stm32_tim_moder.go │ │ ├── machine_stm32_uart.go │ │ ├── machine_stm32f103.go │ │ ├── machine_stm32f4.go │ │ ├── machine_stm32f40x.go │ │ ├── machine_stm32f469.go │ │ ├── machine_stm32f7.go │ │ ├── machine_stm32f7x2.go │ │ ├── machine_stm32l0.go │ │ ├── machine_stm32l0x1.go │ │ ├── machine_stm32l0x2.go │ │ ├── machine_stm32l4.go │ │ ├── machine_stm32l4x2.go │ │ ├── machine_stm32l4x5.go │ │ ├── machine_stm32l4x6.go │ │ ├── machine_stm32l5.go │ │ ├── machine_stm32l5x2.go │ │ ├── machine_stm32wlx.go │ │ ├── machine_tkey.go │ │ ├── machine_tkey_rom.go │ │ ├── pdm.go │ │ ├── pwm.go │ │ ├── runtime.go │ │ ├── serial-none.go │ │ ├── serial-rtt.go │ │ ├── serial-uart.go │ │ ├── serial-usb.go │ │ ├── serial.go │ │ ├── spi.go │ │ ├── spi_tx.go │ │ ├── uart.go │ │ ├── usb/ │ │ │ ├── adc/ │ │ │ │ ├── doc.go │ │ │ │ └── midi/ │ │ │ │ ├── buffer.go │ │ │ │ ├── messages.go │ │ │ │ ├── midi.go │ │ │ │ └── notes.go │ │ │ ├── cdc/ │ │ │ │ ├── buffer.go │ │ │ │ ├── cdc.go │ │ │ │ ├── doc.go │ │ │ │ └── usbcdc.go │ │ │ ├── config.go │ │ │ ├── descriptor/ │ │ │ │ ├── cdc.go │ │ │ │ ├── classspecific.go │ │ │ │ ├── configuration.go │ │ │ │ ├── descriptor.go │ │ │ │ ├── device.go │ │ │ │ ├── doc.go │ │ │ │ ├── endpoint.go │ │ │ │ ├── hid.go │ │ │ │ ├── hidreport.go │ │ │ │ ├── interface.go │ │ │ │ ├── interfaceassociation.go │ │ │ │ ├── joystick.go │ │ │ │ ├── midi.go │ │ │ │ └── msc.go │ │ │ ├── doc.go │ │ │ ├── hid/ │ │ │ │ ├── buffer.go │ │ │ │ ├── doc.go │ │ │ │ ├── hid.go │ │ │ │ ├── joystick/ │ │ │ │ │ ├── joystick.go │ │ │ │ │ └── state.go │ │ │ │ ├── keyboard/ │ │ │ │ │ ├── keyboard.go │ │ │ │ │ └── keycode.go │ │ │ │ └── mouse/ │ │ │ │ └── mouse.go │ │ │ ├── msc/ │ │ │ │ ├── cbw.go │ │ │ │ ├── csw/ │ │ │ │ │ └── csw.go │ │ │ │ ├── disk.go │ │ │ │ ├── msc.go │ │ │ │ ├── scsi/ │ │ │ │ │ └── scsi.go │ │ │ │ ├── scsi.go │ │ │ │ ├── scsi_inquiry.go │ │ │ │ ├── scsi_readwrite.go │ │ │ │ ├── scsi_unmap.go │ │ │ │ └── setup.go │ │ │ └── usb.go │ │ ├── usb.go │ │ ├── virt.go │ │ └── watchdog.go │ ├── os/ │ │ ├── deadline_test.go │ │ ├── dir.go │ │ ├── dir_darwin.go │ │ ├── dir_other.go │ │ ├── dir_test.go │ │ ├── dir_unix.go │ │ ├── dir_wasi.go │ │ ├── dirent_linux.go │ │ ├── endian_little.go │ │ ├── env.go │ │ ├── env_test.go │ │ ├── env_unix_test.go │ │ ├── errors.go │ │ ├── exec/ │ │ │ └── exec.go │ │ ├── exec.go │ │ ├── exec_linux.go │ │ ├── exec_linux_test.go │ │ ├── exec_other.go │ │ ├── exec_test.go │ │ ├── executable_darwin.go │ │ ├── executable_other.go │ │ ├── executable_procfs.go │ │ ├── export_test.go │ │ ├── export_windows_test.go │ │ ├── file.go │ │ ├── file_anyos.go │ │ ├── file_anyos_test.go │ │ ├── file_darwin.go │ │ ├── file_notdarwin.go │ │ ├── file_other.go │ │ ├── file_posix.go │ │ ├── file_unix.go │ │ ├── file_windows.go │ │ ├── filesystem.go │ │ ├── getpagesize_test.go │ │ ├── os_anyos_test.go │ │ ├── os_chmod_test.go │ │ ├── os_hardlink_test.go │ │ ├── os_symlink_test.go │ │ ├── os_test.go │ │ ├── osexec.go │ │ ├── path.go │ │ ├── path_test.go │ │ ├── path_unix.go │ │ ├── path_windows.go │ │ ├── path_windows_test.go │ │ ├── pipe_test.go │ │ ├── proc.go │ │ ├── read_test.go │ │ ├── removeall_noat.go │ │ ├── removeall_other.go │ │ ├── removeall_test.go │ │ ├── seek_unix_bad.go │ │ ├── stat.go │ │ ├── stat_darwin.go │ │ ├── stat_linuxlike.go │ │ ├── stat_other.go │ │ ├── stat_test.go │ │ ├── stat_unix.go │ │ ├── stat_windows.go │ │ ├── sys.go │ │ ├── tempfile.go │ │ ├── tempfile_test.go │ │ ├── truncate_test.go │ │ ├── types.go │ │ ├── types_anyos.go │ │ ├── types_unix.go │ │ └── types_windows.go │ ├── reflect/ │ │ ├── all_test.go │ │ ├── benchmark_test.go │ │ ├── convert_test.go │ │ ├── deepequal.go │ │ ├── export_test.go │ │ ├── internal/ │ │ │ ├── example1/ │ │ │ │ └── example.go │ │ │ └── example2/ │ │ │ └── example.go │ │ ├── intw.go │ │ ├── intw_avr.go │ │ ├── intw_test.go │ │ ├── iter.go │ │ ├── iter_test.go │ │ ├── makefunc.go │ │ ├── swapper.go │ │ ├── tostring_test.go │ │ ├── type.go │ │ ├── type_test.go │ │ ├── value.go │ │ ├── value_test.go │ │ ├── visiblefields.go │ │ └── visiblefields_test.go │ ├── runtime/ │ │ ├── algorithm.go │ │ ├── arch-has-returnaddr.go │ │ ├── arch-no-returnaddr.go │ │ ├── arch_386.go │ │ ├── arch_amd64.go │ │ ├── arch_arm.go │ │ ├── arch_arm64.go │ │ ├── arch_avr.go │ │ ├── arch_cortexm.go │ │ ├── arch_mips.go │ │ ├── arch_mipsle.go │ │ ├── arch_tinygoriscv.go │ │ ├── arch_tinygoriscv32.go │ │ ├── arch_tinygoriscv64.go │ │ ├── arch_tinygowasm.go │ │ ├── arch_tinygowasm_malloc.go │ │ ├── arch_xtensa.go │ │ ├── asm_386.S │ │ ├── asm_amd64.S │ │ ├── asm_amd64_windows.S │ │ ├── asm_arm.S │ │ ├── asm_arm64.S │ │ ├── asm_avr.S │ │ ├── asm_mipsx.S │ │ ├── asm_riscv.S │ │ ├── asm_tinygowasm.S │ │ ├── atomics_critical.go │ │ ├── baremetal.go │ │ ├── build_asserts.go │ │ ├── build_noasserts.go │ │ ├── cgo/ │ │ │ └── cgo.go │ │ ├── chan.go │ │ ├── complex.go │ │ ├── coro.go │ │ ├── debug/ │ │ │ ├── debug.go │ │ │ └── garbage.go │ │ ├── debug.go │ │ ├── defer.go │ │ ├── dynamic_arm64.go │ │ ├── env.go │ │ ├── env_unix.go │ │ ├── env_windows.go │ │ ├── error.go │ │ ├── extern.go │ │ ├── float.go │ │ ├── gc_blocks.go │ │ ├── gc_boehm.c │ │ ├── gc_boehm.go │ │ ├── gc_conservative.go │ │ ├── gc_custom.go │ │ ├── gc_globals.go │ │ ├── gc_leaking.go │ │ ├── gc_none.go │ │ ├── gc_precise.go │ │ ├── gc_stack_cores.go │ │ ├── gc_stack_portable.go │ │ ├── gc_stack_raw.go │ │ ├── gc_stack_threads.go │ │ ├── hashmap.go │ │ ├── hosted.go │ │ ├── interface.go │ │ ├── internal/ │ │ │ └── sys/ │ │ │ └── zversion.go │ │ ├── interrupt/ │ │ │ ├── checkpoint.go │ │ │ ├── interrupt.go │ │ │ ├── interrupt_avr.go │ │ │ ├── interrupt_cortexm.go │ │ │ ├── interrupt_esp32c3.go │ │ │ ├── interrupt_gameboyadvance.go │ │ │ ├── interrupt_k210.go │ │ │ ├── interrupt_none.go │ │ │ ├── interrupt_sifive.go │ │ │ ├── interrupt_tinygoriscv.go │ │ │ └── interrupt_xtensa.go │ │ ├── memhash_fnv.go │ │ ├── memhash_leveldb.go │ │ ├── memhash_tsip.go │ │ ├── metrics/ │ │ │ └── metrics.go │ │ ├── metrics.go │ │ ├── mstats.go │ │ ├── nonhosted.go │ │ ├── os_darwin.c │ │ ├── os_darwin.go │ │ ├── os_js.go │ │ ├── os_linux.go │ │ ├── os_other.go │ │ ├── os_wasip1.go │ │ ├── os_wasip2.go │ │ ├── os_windows.go │ │ ├── panic.go │ │ ├── poll.go │ │ ├── pprof/ │ │ │ └── pprof.go │ │ ├── print.go │ │ ├── proc.go │ │ ├── rand.go │ │ ├── rand_hwrng.go │ │ ├── rand_norng.go │ │ ├── runtime.go │ │ ├── runtime_arm7tdmi.go │ │ ├── runtime_atmega.go │ │ ├── runtime_atsamd21.go │ │ ├── runtime_atsamd21e18.go │ │ ├── runtime_atsamd21g18.go │ │ ├── runtime_atsamd51.go │ │ ├── runtime_atsamd51g19.go │ │ ├── runtime_atsamd51j19.go │ │ ├── runtime_atsamd51j20.go │ │ ├── runtime_atsamd51p19.go │ │ ├── runtime_atsamd51p20.go │ │ ├── runtime_atsame51j19.go │ │ ├── runtime_atsame54p20.go │ │ ├── runtime_atsame5x_can.go │ │ ├── runtime_attiny.go │ │ ├── runtime_avr.go │ │ ├── runtime_avrtiny.go │ │ ├── runtime_cortexm.go │ │ ├── runtime_cortexm_abort.go │ │ ├── runtime_cortexm_hardfault.go │ │ ├── runtime_cortexm_hardfault_debug.go │ │ ├── runtime_cortexm_qemu.go │ │ ├── runtime_esp32.go │ │ ├── runtime_esp32c3.go │ │ ├── runtime_esp32s3.go │ │ ├── runtime_esp32sx.go │ │ ├── runtime_esp32xx.go │ │ ├── runtime_esp8266.go │ │ ├── runtime_fe310.go │ │ ├── runtime_fe310_baremetal.go │ │ ├── runtime_fe310_qemu.go │ │ ├── runtime_k210.go │ │ ├── runtime_k210_baremetal.go │ │ ├── runtime_mimxrt1062.go │ │ ├── runtime_mimxrt1062_clock.go │ │ ├── runtime_mimxrt1062_mpu.go │ │ ├── runtime_mimxrt1062_time.go │ │ ├── runtime_nintendoswitch.S │ │ ├── runtime_nintendoswitch.go │ │ ├── runtime_nrf.go │ │ ├── runtime_nrf52840.go │ │ ├── runtime_nrf_bare.go │ │ ├── runtime_nrf_softdevice.go │ │ ├── runtime_nxpmk66f18.go │ │ ├── runtime_rp2.go │ │ ├── runtime_rp2040.go │ │ ├── runtime_rp2350.go │ │ ├── runtime_stm32.go │ │ ├── runtime_stm32_timers.go │ │ ├── runtime_stm32f103.go │ │ ├── runtime_stm32f4.go │ │ ├── runtime_stm32f405.go │ │ ├── runtime_stm32f407.go │ │ ├── runtime_stm32f469.go │ │ ├── runtime_stm32f7x2.go │ │ ├── runtime_stm32l0.go │ │ ├── runtime_stm32l0x1.go │ │ ├── runtime_stm32l0x2.go │ │ ├── runtime_stm32l4.go │ │ ├── runtime_stm32l4x2.go │ │ ├── runtime_stm32l4x5.go │ │ ├── runtime_stm32l4x6.go │ │ ├── runtime_stm32l5x2.go │ │ ├── runtime_stm32wlx.go │ │ ├── runtime_tinygoriscv.go │ │ ├── runtime_tinygoriscv64.go │ │ ├── runtime_tinygoriscv_qemu.go │ │ ├── runtime_tinygowasm.go │ │ ├── runtime_tinygowasm_unknown.go │ │ ├── runtime_tinygowasmp2.go │ │ ├── runtime_tkey.go │ │ ├── runtime_tkey_baremetal.go │ │ ├── runtime_unix.c │ │ ├── runtime_unix.go │ │ ├── runtime_wasip1.go │ │ ├── runtime_wasip2.go │ │ ├── runtime_wasm_js.go │ │ ├── runtime_wasm_js_scheduler.go │ │ ├── runtime_wasm_unknown.go │ │ ├── runtime_wasmentry.go │ │ ├── runtime_windows.go │ │ ├── scheduler.go │ │ ├── scheduler_cooperative.go │ │ ├── scheduler_cores.go │ │ ├── scheduler_none.go │ │ ├── scheduler_tasks.go │ │ ├── scheduler_threads.go │ │ ├── signal.c │ │ ├── signalstub.go │ │ ├── slice.go │ │ ├── stack.go │ │ ├── string.go │ │ ├── symtab.go │ │ ├── sync.go │ │ ├── synctest.go │ │ ├── time.go │ │ ├── time_go122.go │ │ ├── time_go123.go │ │ ├── time_nxpmk66f18.go │ │ ├── trace/ │ │ │ └── trace.go │ │ ├── volatile/ │ │ │ ├── bitband_nxpmk66f18.go │ │ │ ├── register.go │ │ │ └── volatile.go │ │ ├── wait_other.go │ │ ├── zero_new_alloc.go │ │ └── zero_new_alloc_noop.go │ ├── sync/ │ │ ├── cond.go │ │ ├── cond_test.go │ │ ├── doc.go │ │ ├── map.go │ │ ├── map_go123.go │ │ ├── map_test.go │ │ ├── mutex.go │ │ ├── mutex_test.go │ │ ├── once.go │ │ ├── once_test.go │ │ ├── oncefunc.go │ │ ├── oncefunc_test.go │ │ ├── pool.go │ │ ├── pool_test.go │ │ ├── waitgroup.go │ │ └── waitgroup_test.go │ ├── syscall/ │ │ ├── env_libc.go │ │ ├── env_nonhosted.go │ │ ├── env_wasip2.go │ │ ├── errno.go │ │ ├── errno_other.go │ │ ├── errno_wasilibc.go │ │ ├── errno_wasip2.go │ │ ├── file_emulated.go │ │ ├── file_hosted.go │ │ ├── libc_wasip2.go │ │ ├── mmap_unix_test.go │ │ ├── net.go │ │ ├── proc_emulated.go │ │ ├── proc_hosted.go │ │ ├── str.go │ │ ├── syscall.go │ │ ├── syscall_libc.go │ │ ├── syscall_libc_nintendoswitch.go │ │ ├── syscall_libc_wasi.go │ │ ├── syscall_linux.go │ │ ├── syscall_nonhosted.go │ │ ├── syscall_unix.go │ │ └── tables_nonhosted.go │ ├── testing/ │ │ ├── benchmark.go │ │ ├── benchmark_test.go │ │ ├── doc.go │ │ ├── fuzz.go │ │ ├── is_baremetal.go │ │ ├── is_not_baremetal.go │ │ ├── match.go │ │ ├── match_test.go │ │ ├── sub_test.go │ │ ├── testing.go │ │ └── testing_test.go │ ├── tinygo/ │ │ └── runtime.go │ └── unique/ │ ├── handle.go │ └── handle_test.go ├── stacksize/ │ ├── dwarf.go │ └── stacksize.go ├── targets/ │ ├── adafruit-esp32-feather-v2.json │ ├── ae-rp2040.json │ ├── arduino-leonardo.json │ ├── arduino-mega1280.json │ ├── arduino-mega2560.json │ ├── arduino-mkr1000.json │ ├── arduino-mkrwifi1010.json │ ├── arduino-nano-new.json │ ├── arduino-nano.json │ ├── arduino-nano33.json │ ├── arduino-zero.json │ ├── arduino.json │ ├── arm.ld │ ├── atmega1280.json │ ├── atmega1284p.json │ ├── atmega2560.json │ ├── atmega328p.json │ ├── atmega328pb.json │ ├── atmega32u4.json │ ├── atsamd21.ld │ ├── atsamd21e18a.json │ ├── atsamd21g18a.json │ ├── atsamd51.ld │ ├── atsamd51g19a.json │ ├── atsamd51j19a.json │ ├── atsamd51j20a.json │ ├── atsamd51j20a.ld │ ├── atsamd51p19a.json │ ├── atsamd51p20a.json │ ├── atsamd51p20a.ld │ ├── atsame51j19a.json │ ├── atsame54-xpro.json │ ├── atsame54p20a.json │ ├── atsame5xx19.ld │ ├── atsame5xx20-no-bootloader.ld │ ├── attiny1616.json │ ├── attiny85.json │ ├── avr.S │ ├── avr.json │ ├── avr.ld │ ├── avrtiny.S │ ├── avrtiny.json │ ├── avrtiny.ld │ ├── badger2040-w.json │ ├── badger2040.json │ ├── bluemicro840.json │ ├── bluepill-clone.json │ ├── bluepill.json │ ├── btt-skr-pico.json │ ├── challenger-rp2040.json │ ├── circuitplay-bluefruit.json │ ├── circuitplay-express.json │ ├── clue-alpha.json │ ├── clue.json │ ├── cortex-m-qemu.json │ ├── cortex-m-qemu.s │ ├── cortex-m.json │ ├── cortex-m0.json │ ├── cortex-m0plus.json │ ├── cortex-m3.json │ ├── cortex-m33.json │ ├── cortex-m4.json │ ├── cortex-m7.json │ ├── d1mini.json │ ├── digispark.json │ ├── elecrow-rp2040.json │ ├── elecrow-rp2350.json │ ├── esp-c3-32s-kit.json │ ├── esp32-c3-devkit-rust-1.json │ ├── esp32-coreboard-v2.json │ ├── esp32-mini32.json │ ├── esp32.json │ ├── esp32.ld │ ├── esp32c3-12f.json │ ├── esp32c3-supermini.json │ ├── esp32c3.json │ ├── esp32c3.ld │ ├── esp32s3.json │ ├── esp32s3.ld │ ├── esp8266.json │ ├── esp8266.ld │ ├── fe310.json │ ├── feather-m0-express.json │ ├── feather-m0.json │ ├── feather-m4-can.json │ ├── feather-m4.json │ ├── feather-nrf52840-sense.json │ ├── feather-nrf52840.json │ ├── feather-rp2040-boot-stage2.S │ ├── feather-rp2040.json │ ├── feather-stm32f405.json │ ├── gameboy-advance.json │ ├── gameboy-advance.ld │ ├── gameboy-advance.s │ ├── gemma-m0.json │ ├── gnse.json │ ├── gobadge.json │ ├── gopher-arcade.json │ ├── gopher-badge.json │ ├── gopherbot.json │ ├── gopherbot2.json │ ├── grandcentral-m4.json │ ├── hifive1b.json │ ├── hifive1b.ld │ ├── hw-651-s110v8.json │ ├── hw-651.json │ ├── itsybitsy-m0.json │ ├── itsybitsy-m4.json │ ├── itsybitsy-nrf52840.json │ ├── k210.json │ ├── kb2040.json │ ├── lgt92.json │ ├── lm3s6965.ld │ ├── lorae5.json │ ├── m5paper.json │ ├── m5stack-core2.json │ ├── m5stack.json │ ├── m5stamp-c3.json │ ├── m5stick-c.json │ ├── macropad-rp2040-boot-stage2.S │ ├── macropad-rp2040.json │ ├── maixbit.json │ ├── maixbit.ld │ ├── makerfabs-esp32c3spi35.json │ ├── matrixportal-m4.json │ ├── mch2022.json │ ├── mdbt50qrx-uf2.json │ ├── metro-m4-airlift.json │ ├── metro-rp2350.json │ ├── microbit-s110v8.json │ ├── microbit-v2-s113v7.json │ ├── microbit-v2-s140v7.json │ ├── microbit-v2.json │ ├── microbit.json │ ├── mimxrt1062-teensy40.ld │ ├── mksnanov3.json │ ├── nano-33-ble-s140v6-uf2.json │ ├── nano-33-ble-s140v7-uf2.json │ ├── nano-33-ble-s140v7.json │ ├── nano-33-ble.json │ ├── nano-33-ble.ld │ ├── nano-rp2040.json │ ├── nicenano.json │ ├── nintendoswitch.json │ ├── nintendoswitch.ld │ ├── nintendoswitch.s │ ├── nodemcu.json │ ├── nrf51-s110v8.json │ ├── nrf51-s110v8.ld │ ├── nrf51.json │ ├── nrf51.ld │ ├── nrf52-s132v6.json │ ├── nrf52-s132v6.ld │ ├── nrf52.json │ ├── nrf52.ld │ ├── nrf52833-s113v7.json │ ├── nrf52833-s113v7.ld │ ├── nrf52833-s140v7.json │ ├── nrf52833-s140v7.ld │ ├── nrf52833.json │ ├── nrf52833.ld │ ├── nrf52840-mdk-usb-dongle.json │ ├── nrf52840-mdk.json │ ├── nrf52840-s140v6-uf2-generic.json │ ├── nrf52840-s140v6-uf2.json │ ├── nrf52840-s140v6-uf2.ld │ ├── nrf52840-s140v7-uf2.json │ ├── nrf52840-s140v7-uf2.ld │ ├── nrf52840-s140v7.json │ ├── nrf52840-s140v7.ld │ ├── nrf52840.json │ ├── nrf52840.ld │ ├── nucleo-f103rb.json │ ├── nucleo-f722ze.json │ ├── nucleo-l031k6.json │ ├── nucleo-l432kc.json │ ├── nucleo-l476rg.json │ ├── nucleo-l552ze.json │ ├── nucleo-wl55jc.json │ ├── nxpmk66f18.ld │ ├── p1am-100.json │ ├── particle-3rd-gen.json │ ├── particle-argon.json │ ├── particle-boron.json │ ├── particle-xenon.json │ ├── pca10031.json │ ├── pca10040-s132v6.json │ ├── pca10040.json │ ├── pca10056-s140v6-uf2.json │ ├── pca10056-s140v7.json │ ├── pca10056.json │ ├── pca10059-s140v7.json │ ├── pca10059.json │ ├── pca10059.ld │ ├── pga2350.json │ ├── pico-boot-stage2.S │ ├── pico-plus2.json │ ├── pico-w.json │ ├── pico.json │ ├── pico2-ice.json │ ├── pico2-w.json │ ├── pico2.json │ ├── pinetime.json │ ├── pybadge.json │ ├── pygamer.json │ ├── pyportal.json │ ├── qtpy-esp32c3.json │ ├── qtpy-rp2040-boot-stage2.S │ ├── qtpy-rp2040.json │ ├── qtpy.json │ ├── rak4631.json │ ├── reelboard-s140v7.json │ ├── reelboard.json │ ├── riscv-qemu.json │ ├── riscv-qemu.ld │ ├── riscv.json │ ├── riscv.ld │ ├── riscv32.json │ ├── riscv64.json │ ├── rp2040-boot-stage2.S │ ├── rp2040.json │ ├── rp2040.ld │ ├── rp2350.json │ ├── rp2350.ld │ ├── rp2350_embedded_block.s │ ├── rp2350b.json │ ├── simavr.json │ ├── stm32.ld │ ├── stm32f103rb.ld │ ├── stm32f405.ld │ ├── stm32f407.ld │ ├── stm32f469.ld │ ├── stm32f469disco.json │ ├── stm32f4disco-1.json │ ├── stm32f4disco.json │ ├── stm32f7x2zetx.ld │ ├── stm32l031k6.ld │ ├── stm32l031x6.ld │ ├── stm32l072czt6.ld │ ├── stm32l0x1.json │ ├── stm32l0x2.json │ ├── stm32l4x2.ld │ ├── stm32l4x5.ld │ ├── stm32l4x6.ld │ ├── stm32l5x2xe.ld │ ├── stm32wl5x_cm4.json │ ├── stm32wle5.json │ ├── stm32wlx.ld │ ├── swan.json │ ├── teensy36.json │ ├── teensy36.s │ ├── teensy40.json │ ├── teensy40.s │ ├── teensy41.json │ ├── thingplus-rp2040.json │ ├── thumby.json │ ├── tiny2350.json │ ├── tkey.json │ ├── tkey.ld │ ├── trinket-m0.json │ ├── trinkey-qt2040-boot-stage2.S │ ├── trinkey-qt2040.json │ ├── tufty2040.json │ ├── wasi.json │ ├── wasip1.json │ ├── wasip2.json │ ├── wasm-undefined.txt │ ├── wasm-unknown.json │ ├── wasm.json │ ├── wasm_exec.js │ ├── waveshare-rp2040-tiny.json │ ├── waveshare-rp2040-zero.json │ ├── wioterminal.json │ ├── x9pro.json │ ├── xiao-ble.json │ ├── xiao-esp32c3.json │ ├── xiao-esp32s3.json │ ├── xiao-rp2040.json │ ├── xiao.json │ └── xtensa.json ├── testdata/ │ ├── alias.go │ ├── alias.txt │ ├── atomic.go │ ├── atomic.txt │ ├── binop.go │ ├── binop.txt │ ├── calls.go │ ├── calls.txt │ ├── cgo/ │ │ ├── extra.go │ │ ├── main.c │ │ ├── main.go │ │ ├── main.h │ │ ├── out.txt │ │ └── test.h │ ├── channel.go │ ├── channel.txt │ ├── corpus.yaml │ ├── embed/ │ │ ├── a/ │ │ │ └── b/ │ │ │ ├── .hidden │ │ │ ├── bar.txt │ │ │ └── foo.txt │ │ ├── embed.go │ │ ├── hello.txt │ │ └── out.txt │ ├── env.go │ ├── env.txt │ ├── errors/ │ │ ├── cgo.go │ │ ├── compiler.go │ │ ├── importcycle/ │ │ │ └── cycle.go │ │ ├── interp.go │ │ ├── invaliddep/ │ │ │ └── invaliddep.go │ │ ├── invalidmain.go │ │ ├── invalidname/ │ │ │ └── invalidname.go │ │ ├── invalidname.go │ │ ├── linker-flashoverflow.go │ │ ├── linker-ramoverflow.go │ │ ├── linker-undefined.go │ │ ├── loader-importcycle.go │ │ ├── loader-invaliddep.go │ │ ├── loader-invalidpackage.go │ │ ├── loader-nopackage.go │ │ ├── optimizer.go │ │ ├── syntax.go │ │ └── types.go │ ├── filesystem.go │ ├── filesystem.txt │ ├── float.go │ ├── float.txt │ ├── gc.go │ ├── gc.txt │ ├── generics/ │ │ ├── testa/ │ │ │ └── testa.go │ │ ├── testb/ │ │ │ └── testb.go │ │ └── value/ │ │ └── value.go │ ├── generics.go │ ├── generics.txt │ ├── go1.21.go │ ├── go1.21.txt │ ├── go1.22/ │ │ ├── go.mod │ │ ├── main.go │ │ └── out.txt │ ├── go1.23/ │ │ ├── go.mod │ │ ├── main.go │ │ └── out.txt │ ├── goroutines.go │ ├── goroutines.txt │ ├── init.go │ ├── init.txt │ ├── init_multi.go │ ├── init_multi.txt │ ├── interface.go │ ├── interface.txt │ ├── json.go │ ├── json.txt │ ├── ldflags.go │ ├── ldflags.txt │ ├── map.go │ ├── map.txt │ ├── math.go │ ├── math.txt │ ├── oldgo/ │ │ ├── go.mod │ │ ├── main.go │ │ └── out.txt │ ├── print.go │ ├── print.txt │ ├── rand.go │ ├── rand.txt │ ├── recover.go │ ├── recover.txt │ ├── reflect.go │ ├── reflect.txt │ ├── signal.go │ ├── signal.txt │ ├── slice.go │ ├── slice.txt │ ├── sort.go │ ├── sort.txt │ ├── stdlib.go │ ├── stdlib.txt │ ├── string.go │ ├── string.txt │ ├── structs.go │ ├── structs.txt │ ├── testing.go │ ├── testing.txt │ ├── timers.go │ ├── timers.txt │ ├── trivialpanic.go │ ├── wasmexit.go │ ├── wasmexit.js │ ├── wasmexport-noscheduler.go │ ├── wasmexport.go │ ├── wasmexport.js │ ├── wasmexport.txt │ ├── wasmfunc.go │ ├── wasmfunc.js │ ├── wasmfunc.txt │ ├── zeroalloc.go │ └── zeroalloc.txt ├── tests/ │ ├── os/ │ │ └── smoke/ │ │ └── smoke_test.go │ ├── runtime/ │ │ └── memhash_test.go │ ├── runtime_wasi/ │ │ └── malloc_test.go │ ├── testing/ │ │ ├── builderr/ │ │ │ ├── builderr.go │ │ │ └── builderr_test.go │ │ ├── chdir/ │ │ │ └── chdir.go │ │ ├── fail/ │ │ │ └── fail_test.go │ │ ├── nothing/ │ │ │ └── nothing.go │ │ ├── pass/ │ │ │ └── pass_test.go │ │ └── recurse/ │ │ ├── subdir/ │ │ │ └── subdir_test.go │ │ └── top_test.go │ ├── text/ │ │ └── template/ │ │ └── smoke/ │ │ ├── empty.go │ │ └── smoke_test.go │ ├── tinygotest/ │ │ ├── main.go │ │ └── main_test.go │ └── wasm/ │ ├── chan_test.go │ ├── event_test.go │ ├── fmt_test.go │ ├── fmtprint_test.go │ ├── go.mod │ ├── go.sum │ ├── log_test.go │ ├── setup_test.go │ └── testdata/ │ ├── chan.go │ ├── event.go │ ├── fmt.go │ ├── fmtprint.go │ └── log.go ├── tools/ │ ├── gen-critical-atomics/ │ │ └── gen-critical-atomics.go │ ├── gen-device-avr/ │ │ └── gen-device-avr.go │ ├── gen-device-svd/ │ │ └── gen-device-svd.go │ ├── sizediff │ └── tgtestjson.sh ├── transform/ │ ├── allocs.go │ ├── allocs_test.go │ ├── errors.go │ ├── gc.go │ ├── gc_test.go │ ├── interface-lowering.go │ ├── interface-lowering_test.go │ ├── interrupt.go │ ├── interrupt_test.go │ ├── llvm.go │ ├── maps.go │ ├── maps_test.go │ ├── optimizer.go │ ├── rtcalls.go │ ├── rtcalls_test.go │ ├── stacksize.go │ ├── stacksize_test.go │ ├── testdata/ │ │ ├── allocs.ll │ │ ├── allocs.out.ll │ │ ├── allocs2.go │ │ ├── gc-stackslots.ll │ │ ├── gc-stackslots.out.ll │ │ ├── interface.ll │ │ ├── interface.out.ll │ │ ├── interrupt.ll │ │ ├── interrupt.out.ll │ │ ├── maps.ll │ │ ├── maps.out.ll │ │ ├── reflect-implements.ll │ │ ├── reflect-implements.out.ll │ │ ├── reflect.go │ │ ├── stacksize.ll │ │ ├── stacksize.out.ll │ │ ├── stringequal.ll │ │ ├── stringequal.out.ll │ │ ├── stringtobytes.ll │ │ └── stringtobytes.out.ll │ ├── transform.go │ ├── transform_test.go │ └── util.go ├── util_unix.go └── util_windows.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .circleci/config.yml ================================================ version: 2.1 commands: submodules: steps: - run: name: "Pull submodules" command: git submodule update --init llvm-source-linux: steps: - restore_cache: keys: - llvm-source-19-v1 - run: name: "Fetch LLVM source" command: make llvm-source - save_cache: key: llvm-source-19-v1 paths: - llvm-project/clang/lib/Headers - llvm-project/clang/include - llvm-project/compiler-rt - llvm-project/lld/include - llvm-project/llvm/include hack-ninja-jobs: steps: - run: name: "Hack Ninja to use less jobs" command: | echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja chmod +x /go/bin/ninja build-binaryen-linux: steps: - restore_cache: keys: - binaryen-linux-v3 - run: name: "Build Binaryen" command: | make binaryen - save_cache: key: binaryen-linux-v3 paths: - build/wasm-opt test-linux: parameters: llvm: type: string fmt-check: type: boolean default: true steps: - checkout - submodules - run: name: "Install apt dependencies" command: | echo 'deb https://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-<> main' > /etc/apt/sources.list.d/llvm.list wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - apt-get update apt-get install --no-install-recommends -y \ llvm-<>-dev \ clang-<> \ libclang-<>-dev \ lld-<> \ cmake \ ninja-build - hack-ninja-jobs - build-binaryen-linux - restore_cache: keys: - go-cache-v4-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }} - go-cache-v4-{{ checksum "go.mod" }} - llvm-source-linux - run: go install -tags=llvm<> . - when: condition: <> steps: - run: # Do this before gen-device so that it doesn't check the # formatting of generated files. name: Check Go code formatting command: make fmt-check lint - run: make gen-device -j4 # TODO: change this to -skip='TestErrors|TestWasm' with Go 1.20 - run: go test -tags=llvm<> -short -run='TestBuild|TestTest|TestGetList|TestTraceback' - run: make smoketest XTENSA=0 - save_cache: key: go-cache-v4-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} paths: - ~/.cache/go-build - /go/pkg/mod jobs: test-oldest: # This tests our lowest supported versions of Go and LLVM, to make sure at # least the smoke tests still pass. docker: - image: golang:1.22-bullseye steps: - test-linux: llvm: "15" resource_class: large test-newest: # This tests the latest supported LLVM version when linking against system # libraries. docker: - image: golang:1.25-bullseye steps: - test-linux: llvm: "20" resource_class: large workflows: test-all: jobs: - test-oldest # disable this test, since CircleCI seems unable to download due to rate-limits on Dockerhub. # - test-newest ================================================ FILE: .dockerignore ================================================ build/ llvm-*/ .github .circleci ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms open_collective: tinygo ================================================ FILE: .github/workflows/build-macos.yml ================================================ name: macOS on: pull_request: push: branches: - dev - release concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build-macos: name: build-macos strategy: matrix: # macos-14: arm64 (oldest supported version as of 18-11-2025) # macos-15-intel: amd64 (last intel version to be supported by github runners) # See https://github.com/actions/runner-images/issues/13046 os: [macos-14, macos-15-intel] include: - os: macos-14 goarch: arm64 - os: macos-15-intel goarch: amd64 runs-on: ${{ matrix.os }} steps: - name: Install Dependencies run: | HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu binaryen - name: Checkout uses: actions/checkout@v5 with: submodules: true - name: Extract TinyGo version id: version run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT" - name: Install Go uses: actions/setup-go@v6 with: go-version: '1.25.5' cache: true - name: Restore LLVM source cache uses: actions/cache/restore@v4 id: cache-llvm-source with: key: llvm-source-20-${{ matrix.os }}-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include llvm-project/compiler-rt llvm-project/lld/include llvm-project/llvm/include - name: Download LLVM source if: steps.cache-llvm-source.outputs.cache-hit != 'true' run: make llvm-source - name: Save LLVM source cache uses: actions/cache/save@v4 if: steps.cache-llvm-source.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }} path: | llvm-project/clang/lib/Headers llvm-project/clang/include llvm-project/compiler-rt llvm-project/lld/include llvm-project/llvm/include - name: Restore LLVM build cache uses: actions/cache/restore@v4 id: cache-llvm-build with: key: llvm-build-20-${{ matrix.os }}-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' run: | # fetch LLVM source rm -rf llvm-project make llvm-source # install dependencies HOMEBREW_NO_AUTO_UPDATE=1 brew install ninja # build! make llvm-build find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; - name: Save LLVM build cache uses: actions/cache/save@v4 if: steps.cache-llvm-build.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }} path: llvm-build - name: make gen-device run: make -j3 gen-device - name: Test TinyGo run: make test GOTESTFLAGS="-only-current-os" - name: Build TinyGo release tarball run: make release -j3 - name: Test stdlib packages run: make tinygo-test - name: Make release artifact run: cp -p build/release.tar.gz build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz - name: Publish release artifact # Note: this release artifact is double-zipped, see: # https://github.com/actions/upload-artifact/issues/39 # We can essentially pick one of these: # - have a double-zipped artifact when downloaded from the UI # - have a very slow artifact upload # We're doing the former here, to keep artifact uploads fast. uses: actions/upload-artifact@v4 with: name: darwin-${{ matrix.goarch }}-double-zipped-${{ steps.version.outputs.version }} path: build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz - name: Smoke tests run: make smoketest TINYGO=$(PWD)/build/tinygo test-macos-homebrew: name: homebrew-install runs-on: macos-latest strategy: matrix: version: [16, 17, 18, 19, 20] steps: - name: Set up Homebrew uses: Homebrew/actions/setup-homebrew@master - name: Fix Python symlinks run: | # Github runners have broken symlinks, so relink # see: https://github.com/actions/setup-python/issues/577 brew list -1 | grep python | while read formula; do brew unlink $formula; brew link --overwrite $formula; done - name: Install LLVM run: | brew install llvm@${{ matrix.version }} - name: Checkout uses: actions/checkout@v5 - name: Install Go uses: actions/setup-go@v6 with: go-version: '1.25.5' cache: true - name: Build TinyGo (LLVM ${{ matrix.version }}) run: go install -tags=llvm${{ matrix.version }} - name: Check binary run: tinygo version - name: Build TinyGo (default LLVM) if: matrix.version == 20 run: go install - name: Check binary if: matrix.version == 20 run: tinygo version ================================================ FILE: .github/workflows/docker.yml ================================================ # This is the Github action to build and push the tinygo/tinygo-dev Docker image. # If you are looking for the tinygo/tinygo "release" Docker image please see # https://github.com/tinygo-org/docker # name: Docker on: push: branches: [ dev, fix-docker-llvm-build ] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: push_to_registry: name: build-push-dev runs-on: ubuntu-latest permissions: packages: write contents: read steps: - name: Free Disk space shell: bash run: | df -h sudo rm -rf /opt/hostedtoolcache sudo rm -rf /usr/local/lib/android sudo rm -rf /usr/share/dotnet sudo rm -rf /opt/ghc sudo rm -rf /usr/local/graalvm sudo rm -rf /usr/local/share/boost df -h - name: Check out the repo uses: actions/checkout@v5 with: submodules: recursive - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Docker meta id: meta uses: docker/metadata-action@v5 with: images: | tinygo/tinygo-dev ghcr.io/${{ github.repository_owner }}/tinygo-dev tags: | type=sha,format=long type=raw,value=latest - name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Log in to Github Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push uses: docker/build-push-action@v6 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max ================================================ FILE: .github/workflows/linux.yml ================================================ name: Linux on: pull_request: push: branches: - dev - release concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build-linux: # Build Linux binaries, ready for release. # This runs inside an Alpine Linux container so we can more easily create a # statically linked binary. runs-on: ubuntu-latest container: image: golang:1.25-alpine outputs: version: ${{ steps.version.outputs.version }} steps: - name: Install apk dependencies # tar: needed for actions/cache@v4 # git+openssh: needed for checkout (I think?) # ruby: needed to install fpm run: apk add tar git openssh make g++ ruby-dev mold - name: Work around CVE-2022-24765 # We're not on a multi-user machine, so this is safe. run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: Checkout uses: actions/checkout@v5 with: submodules: true - name: Extract TinyGo version id: version run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT" - name: Cache Go uses: actions/cache@v4 with: key: go-cache-linux-alpine-v1-${{ hashFiles('go.mod') }} path: | ~/.cache/go-build ~/go/pkg/mod - name: Restore LLVM source cache uses: actions/cache/restore@v4 id: cache-llvm-source with: key: llvm-source-20-linux-alpine-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include llvm-project/compiler-rt llvm-project/lld/include llvm-project/llvm/include - name: Download LLVM source if: steps.cache-llvm-source.outputs.cache-hit != 'true' run: make llvm-source - name: Save LLVM source cache uses: actions/cache/save@v4 if: steps.cache-llvm-source.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }} path: | llvm-project/clang/lib/Headers llvm-project/clang/include llvm-project/compiler-rt llvm-project/lld/include llvm-project/llvm/include - name: Restore LLVM build cache uses: actions/cache/restore@v4 id: cache-llvm-build with: key: llvm-build-20-linux-alpine-v1 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' run: | # fetch LLVM source rm -rf llvm-project make llvm-source # install dependencies apk add cmake samurai python3 # build! make llvm-build # Remove unnecessary object files (to reduce cache size). find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; - name: Save LLVM build cache uses: actions/cache/save@v4 if: steps.cache-llvm-build.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }} path: llvm-build - name: Cache Binaryen uses: actions/cache@v4 id: cache-binaryen with: key: binaryen-linux-alpine-v1 path: build/wasm-opt - name: Build Binaryen if: steps.cache-binaryen.outputs.cache-hit != 'true' run: | apk add cmake samurai python3 make binaryen STATIC=1 - name: Install fpm run: | gem install --version 4.0.7 public_suffix gem install --version 2.7.6 dotenv gem install --no-document fpm - name: Run linter run: make lint - name: Run spellcheck run: make spell - name: Build TinyGo release run: | make release deb -j3 STATIC=1 cp -p build/release.tar.gz /tmp/tinygo${{ steps.version.outputs.version }}.linux-amd64.tar.gz cp -p build/release.deb /tmp/tinygo_${{ steps.version.outputs.version }}_amd64.deb - name: Publish release artifact uses: actions/upload-artifact@v4 with: name: linux-amd64-double-zipped-${{ steps.version.outputs.version }} path: | /tmp/tinygo${{ steps.version.outputs.version }}.linux-amd64.tar.gz /tmp/tinygo_${{ steps.version.outputs.version }}_amd64.deb test-linux-build: # Test the binaries built in the build-linux job by running the smoke tests. runs-on: ubuntu-latest needs: build-linux steps: - name: Checkout uses: actions/checkout@v5 with: submodules: true - name: Install Go uses: actions/setup-go@v6 with: go-version: '1.25.5' cache: true - name: Install wasmtime uses: bytecodealliance/actions/wasmtime/setup@v1 with: version: "29.0.1" - name: Install wasm-tools uses: bytecodealliance/actions/wasm-tools/setup@v1 - name: Download release artifact uses: actions/download-artifact@v4 with: name: linux-amd64-double-zipped-${{ needs.build-linux.outputs.version }} - name: Extract release tarball run: | mkdir -p ~/lib tar -C ~/lib -xf tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz ln -s ~/lib/tinygo/bin/tinygo ~/go/bin/tinygo - run: make tinygo-test-wasip1-fast - run: make tinygo-test-wasip2-fast - run: make tinygo-test-wasm - run: make smoketest assert-test-linux: # Run all tests that can run on Linux, with LLVM assertions enabled to catch # potential bugs. runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v5 with: submodules: true - name: Install apt dependencies run: | echo "Show cpuinfo; sometimes useful when troubleshooting" cat /proc/cpuinfo sudo apt-get update sudo apt-get install --no-install-recommends \ qemu-system-arm \ qemu-system-riscv32 \ qemu-user \ simavr \ ninja-build - name: Install Go uses: actions/setup-go@v6 with: go-version: '1.25.5' cache: true - name: Install Node.js uses: actions/setup-node@v4 with: node-version: '18' - name: Install wasmtime uses: bytecodealliance/actions/wasmtime/setup@v1 with: version: "29.0.1" - name: Setup `wasm-tools` uses: bytecodealliance/actions/wasm-tools/setup@v1 - name: Restore LLVM source cache uses: actions/cache/restore@v4 id: cache-llvm-source with: key: llvm-source-20-linux-asserts-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include llvm-project/compiler-rt llvm-project/lld/include llvm-project/llvm/include - name: Download LLVM source if: steps.cache-llvm-source.outputs.cache-hit != 'true' run: make llvm-source - name: Save LLVM source cache uses: actions/cache/save@v4 if: steps.cache-llvm-source.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }} path: | llvm-project/clang/lib/Headers llvm-project/clang/include llvm-project/compiler-rt llvm-project/lld/include llvm-project/llvm/include - name: Restore LLVM build cache uses: actions/cache/restore@v4 id: cache-llvm-build with: key: llvm-build-20-linux-asserts-v1 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' run: | # fetch LLVM source rm -rf llvm-project make llvm-source # build! make llvm-build ASSERT=1 # Remove unnecessary object files (to reduce cache size). find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; - name: Save LLVM build cache uses: actions/cache/save@v4 if: steps.cache-llvm-build.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }} path: llvm-build - name: Cache Binaryen uses: actions/cache@v4 id: cache-binaryen with: key: binaryen-linux-asserts-v1 path: build/wasm-opt - name: Build Binaryen if: steps.cache-binaryen.outputs.cache-hit != 'true' run: make binaryen - run: make gen-device -j4 - name: Test TinyGo run: make ASSERT=1 test - name: Build TinyGo run: | make ASSERT=1 echo "$(pwd)/build" >> $GITHUB_PATH - name: Test stdlib packages run: make tinygo-test - run: make smoketest - run: make wasmtest - run: make tinygo-test-baremetal build-linux-cross: # Build ARM Linux binaries, ready for release. # This intentionally uses an older Linux image, so that we compile against # an older glibc version and therefore are compatible with a wide range of # Linux distributions. # It is set to "needs: build-linux" because it modifies the release created # in that process to avoid doing lots of duplicate work and to avoid # complications around precompiled libraries such as compiler-rt shipped as # part of the release tarball. strategy: matrix: goarch: [ arm, arm64 ] include: - goarch: arm64 toolchain: aarch64-linux-gnu libc: arm64 - goarch: arm toolchain: arm-linux-gnueabihf libc: armhf runs-on: ubuntu-22.04 # note: use the oldest image available! (see above) needs: build-linux steps: - name: Checkout uses: actions/checkout@v5 - name: Get TinyGo version id: version run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT" - name: Install apt dependencies run: | sudo apt-get update sudo apt-get install --no-install-recommends \ qemu-user \ g++-${{ matrix.toolchain }} \ libc6-dev-${{ matrix.libc }}-cross - name: Install Go uses: actions/setup-go@v6 with: go-version: '1.25.5' cache: true - name: Restore LLVM source cache uses: actions/cache/restore@v4 id: cache-llvm-source with: key: llvm-source-20-linux-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include llvm-project/compiler-rt llvm-project/lld/include llvm-project/llvm/include - name: Download LLVM source if: steps.cache-llvm-source.outputs.cache-hit != 'true' run: make llvm-source - name: Save LLVM source cache uses: actions/cache/save@v4 if: steps.cache-llvm-source.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }} path: | llvm-project/clang/lib/Headers llvm-project/clang/include llvm-project/compiler-rt llvm-project/lld/include llvm-project/llvm/include - name: Restore LLVM build cache uses: actions/cache/restore@v4 id: cache-llvm-build with: key: llvm-build-20-linux-${{ matrix.goarch }}-v1 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' run: | # fetch LLVM source rm -rf llvm-project make llvm-source # Install build dependencies. sudo apt-get install --no-install-recommends ninja-build # build! make llvm-build CROSS=${{ matrix.toolchain }} # Remove unnecessary object files (to reduce cache size). find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; - name: Save LLVM build cache uses: actions/cache/save@v4 if: steps.cache-llvm-build.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }} path: llvm-build - name: Cache Binaryen uses: actions/cache@v4 id: cache-binaryen with: key: binaryen-linux-${{ matrix.goarch }}-v4 path: build/wasm-opt - name: Build Binaryen if: steps.cache-binaryen.outputs.cache-hit != 'true' run: | sudo apt-get install --no-install-recommends ninja-build git submodule update --init lib/binaryen make CROSS=${{ matrix.toolchain }} binaryen - name: Install fpm run: | sudo gem install --version 4.0.7 public_suffix sudo gem install --version 2.7.6 dotenv sudo gem install --no-document fpm - name: Build TinyGo binary run: | make CROSS=${{ matrix.toolchain }} - name: Download amd64 release uses: actions/download-artifact@v4 with: name: linux-amd64-double-zipped-${{ needs.build-linux.outputs.version }} - name: Extract amd64 release run: | mkdir -p build/release tar -xf tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz -C build/release tinygo - name: Modify release run: | cp -p build/tinygo build/release/tinygo/bin cp -p build/wasm-opt build/release/tinygo/bin - name: Create ${{ matrix.goarch }} release run: | make release deb RELEASEONLY=1 DEB_ARCH=${{ matrix.libc }} cp -p build/release.tar.gz /tmp/tinygo${{ steps.version.outputs.version }}.linux-${{ matrix.goarch }}.tar.gz cp -p build/release.deb /tmp/tinygo_${{ steps.version.outputs.version }}_${{ matrix.libc }}.deb - name: Publish release artifact uses: actions/upload-artifact@v4 with: name: linux-${{ matrix.goarch }}-double-zipped-${{ steps.version.outputs.version }} path: | /tmp/tinygo${{ steps.version.outputs.version }}.linux-${{ matrix.goarch }}.tar.gz /tmp/tinygo_${{ steps.version.outputs.version }}_${{ matrix.libc }}.deb ================================================ FILE: .github/workflows/llvm.yml ================================================ # This is the Github action to build and push the LLVM Docker image # used by the tinygo/tinygo-dev Docker image. # # It only needs to be rebuilt when updating the LLVM version. # # To update, make any needed changes to this file, # then push to the "build-llvm-image" branch. # # The needed image will be rebuilt, which will very likely take at least 1-2 hours. name: LLVM on: push: branches: [ build-llvm-image ] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build-push-llvm: name: build-push-llvm runs-on: ubuntu-latest permissions: packages: write contents: read steps: - name: Check out the repo uses: actions/checkout@v5 with: submodules: recursive - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Docker meta id: meta uses: docker/metadata-action@v5 with: images: | tinygo/llvm-20 ghcr.io/${{ github.repository_owner }}/llvm-20 tags: | type=sha,format=long type=raw,value=latest - name: Log in to Docker Hub uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Log in to Github Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push uses: docker/build-push-action@v6 with: target: tinygo-llvm-build context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max ================================================ FILE: .github/workflows/nix.yml ================================================ name: Nix on: pull_request: push: branches: - dev - release concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: nix-test: runs-on: ubuntu-latest steps: - name: Uninstall system LLVM # Hack to work around issue where we still include system headers for # some reason. # See: https://github.com/tinygo-org/tinygo/pull/4516#issuecomment-2416363668 run: sudo apt-get remove llvm-18 - name: Checkout uses: actions/checkout@v5 - name: Pull musl, bdwgc run: | git submodule update --init lib/musl lib/bdwgc - name: Restore LLVM source cache uses: actions/cache/restore@v4 id: cache-llvm-source with: key: llvm-source-20-linux-nix-v1 path: | llvm-project/compiler-rt - name: Download LLVM source if: steps.cache-llvm-source.outputs.cache-hit != 'true' run: make llvm-source - name: Save LLVM source cache uses: actions/cache/save@v4 if: steps.cache-llvm-source.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }} path: | llvm-project/compiler-rt - uses: cachix/install-nix-action@v22 - name: Test run: | nix develop --ignore-environment --keep HOME --command bash -c "go install && ~/go/bin/tinygo version && ~/go/bin/tinygo build -o test ./testdata/cgo" ================================================ FILE: .github/workflows/sizediff-install-pkgs.sh ================================================ # Command that's part of sizediff.yml. This is put in a separate file so that it # still works after checking out the dev branch (that is, when going from LLVM # 16 to LLVM 17 for example, both Clang 16 and Clang 17 are installed). echo 'deb https://apt.llvm.org/noble/ llvm-toolchain-noble-20 main' | sudo tee /etc/apt/sources.list.d/llvm.list wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - sudo apt-get update sudo apt-get install --no-install-recommends -y \ llvm-20-dev \ clang-20 \ libclang-20-dev \ lld-20 ================================================ FILE: .github/workflows/sizediff.yml ================================================ name: Binary size difference on: pull_request: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: sizediff: # Note: when updating the Ubuntu version, also update the Ubuntu version in # sizediff-install-pkgs.sh runs-on: ubuntu-24.04 permissions: pull-requests: write steps: # Prepare, install tools - name: Add GOBIN to $PATH run: | echo "$HOME/go/bin" >> $GITHUB_PATH - name: Checkout uses: actions/checkout@v5 with: fetch-depth: 0 # fetch all history (no sparse checkout) submodules: true - name: Install apt dependencies run: ./.github/workflows/sizediff-install-pkgs.sh - name: Restore LLVM source cache uses: actions/cache@v4 id: cache-llvm-source with: key: llvm-source-20-sizediff-v1 path: | llvm-project/compiler-rt - name: Download LLVM source if: steps.cache-llvm-source.outputs.cache-hit != 'true' run: make llvm-source - name: Cache Go uses: actions/cache@v4 with: key: go-cache-linux-sizediff-v2-${{ hashFiles('go.mod') }} path: | ~/.cache/go-build ~/go/pkg/mod - run: make gen-device -j4 - name: Download drivers repo run: git clone https://github.com/tinygo-org/drivers.git - name: Save HEAD run: git branch github-actions-saved-HEAD HEAD # Compute sizes for the PR branch - name: Build tinygo binary for the PR branch run: go install - name: Determine binary sizes on the PR branch run: (cd drivers; make smoke-test XTENSA=0 | tee sizes-pr.txt) # Compute sizes for the dev branch - name: Checkout dev branch run: | git reset --hard origin/dev git checkout --no-recurse-submodules `git merge-base HEAD origin/dev` - name: Install apt dependencies on the dev branch # this is only needed on a PR that changes the LLVM version run: ./.github/workflows/sizediff-install-pkgs.sh - name: Build tinygo binary for the dev branch run: go install - name: Determine binary sizes on the dev branch run: (cd drivers; make smoke-test XTENSA=0 | tee sizes-dev.txt) # Create comment # TODO: add a summary, something like: # - overall size difference (percent) # - number of binaries that grew / shrank / remained the same # - don't show the full diff when no binaries changed - name: Calculate size diff run: ./tools/sizediff drivers/sizes-dev.txt drivers/sizes-pr.txt | tee sizediff.txt - name: Create comment run: | echo "Size difference with the dev branch:" > comment.txt echo "
Binary size difference" >> comment.txt echo "
" >> comment.txt
          cat sizediff.txt >> comment.txt
          echo "
" >> comment.txt - name: Comment contents run: cat comment.txt - name: Add comment if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }} uses: thollander/actions-comment-pull-request@v2.3.1 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} filePath: comment.txt comment_tag: sizediff ================================================ FILE: .github/workflows/tinygo-extract-version.sh ================================================ #!/bin/sh # Extract the version string from the source code, to be stored in a variable. grep 'const version' goenv/version.go | sed 's/^const version = "\(.*\)"$/version=\1/g' ================================================ FILE: .github/workflows/windows.yml ================================================ name: Windows on: pull_request: push: branches: - dev - release concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build-windows: runs-on: windows-2022 outputs: version: ${{ steps.version.outputs.version }} steps: - name: Configure pagefile uses: al-cheb/configure-pagefile-action@v1.4 with: minimum-size: 8GB maximum-size: 24GB disk-root: "C:" - uses: MinoruSekine/setup-scoop@v4 with: scoop_update: 'false' - name: Install Dependencies shell: bash run: | scoop install ninja binaryen - name: Checkout uses: actions/checkout@v5 with: submodules: true - name: Extract TinyGo version id: version shell: bash run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT" - name: Install Go uses: actions/setup-go@v6 with: go-version: '1.25.5' cache: true - name: Restore cached LLVM source uses: actions/cache/restore@v4 id: cache-llvm-source with: key: llvm-source-20-windows-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include llvm-project/compiler-rt llvm-project/lld/include llvm-project/llvm/include - name: Download LLVM source if: steps.cache-llvm-source.outputs.cache-hit != 'true' run: make llvm-source - name: Save cached LLVM source uses: actions/cache/save@v4 if: steps.cache-llvm-source.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }} path: | llvm-project/clang/lib/Headers llvm-project/clang/include llvm-project/compiler-rt llvm-project/lld/include llvm-project/llvm/include - name: Restore cached LLVM build uses: actions/cache/restore@v4 id: cache-llvm-build with: key: llvm-build-20-windows-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' shell: bash run: | # fetch LLVM source rm -rf llvm-project make llvm-source # build! make llvm-build CCACHE=OFF # Remove unnecessary object files (to reduce cache size). find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; - name: Save cached LLVM build uses: actions/cache/save@v4 if: steps.cache-llvm-build.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }} path: llvm-build - name: Cache Go cache uses: actions/cache@v4 with: key: go-cache-windows-v1-${{ hashFiles('go.mod') }} path: | C:/Users/runneradmin/AppData/Local/go-build C:/Users/runneradmin/go/pkg/mod - name: Install wasmtime run: | scoop install wasmtime@29.0.1 - name: make gen-device run: make -j3 gen-device - name: Test TinyGo shell: bash run: make test GOTESTFLAGS="-only-current-os" - name: Build TinyGo release tarball shell: bash run: make build/release -j4 - name: Make release artifact shell: bash working-directory: build/release run: 7z -tzip a tinygo${{ steps.version.outputs.version }}.windows-amd64.zip tinygo - name: Publish release artifact # Note: this release artifact is double-zipped, see: # https://github.com/actions/upload-artifact/issues/39 # We can essentially pick one of these: # - have a dobule-zipped artifact when downloaded from the UI # - have a very slow artifact upload # We're doing the former here, to keep artifact uploads fast. uses: actions/upload-artifact@v4 with: name: windows-amd64-double-zipped-${{ steps.version.outputs.version }} path: build/release/tinygo${{ steps.version.outputs.version }}.windows-amd64.zip smoke-test-windows: runs-on: windows-2022 needs: build-windows steps: - name: Configure pagefile uses: al-cheb/configure-pagefile-action@v1.4 with: minimum-size: 8GB maximum-size: 24GB disk-root: "C:" - uses: MinoruSekine/setup-scoop@v4 with: scoop_update: 'false' - name: Install Dependencies shell: bash run: | scoop install binaryen - name: Checkout uses: actions/checkout@v5 - name: Install Go uses: actions/setup-go@v6 with: go-version: '1.25.5' cache: true - name: Download TinyGo build uses: actions/download-artifact@v4 with: name: windows-amd64-double-zipped-${{ needs.build-windows.outputs.version }} path: build/ - name: Unzip TinyGo build shell: bash working-directory: build run: 7z x tinygo*.windows-amd64.zip -r - name: Smoke tests shell: bash run: make smoketest TINYGO=$(PWD)/build/tinygo/bin/tinygo stdlib-test-windows: runs-on: windows-2022 needs: build-windows steps: - name: Configure pagefile uses: al-cheb/configure-pagefile-action@v1.4 with: minimum-size: 8GB maximum-size: 24GB disk-root: "C:" - name: Checkout uses: actions/checkout@v5 - name: Install Go uses: actions/setup-go@v6 with: go-version: '1.25.5' cache: true - name: Download TinyGo build uses: actions/download-artifact@v4 with: name: windows-amd64-double-zipped-${{ needs.build-windows.outputs.version }} path: build/ - name: Unzip TinyGo build shell: bash working-directory: build run: 7z x tinygo*.windows-amd64.zip -r - name: Test stdlib packages run: make tinygo-test TINYGO=$(PWD)/build/tinygo/bin/tinygo stdlib-wasi-test-windows: runs-on: windows-2022 needs: build-windows steps: - name: Configure pagefile uses: al-cheb/configure-pagefile-action@v1.4 with: minimum-size: 8GB maximum-size: 24GB disk-root: "C:" - uses: MinoruSekine/setup-scoop@v4 with: scoop_update: 'false' - name: Install Dependencies shell: bash run: | scoop install binaryen && scoop install wasmtime@29.0.1 - name: Checkout uses: actions/checkout@v5 - name: Install Go uses: actions/setup-go@v6 with: go-version: '1.25.5' cache: true - name: Download TinyGo build uses: actions/download-artifact@v4 with: name: windows-amd64-double-zipped-${{ needs.build-windows.outputs.version }} path: build/ - name: Unzip TinyGo build shell: bash working-directory: build run: 7z x tinygo*.windows-amd64.zip -r - name: Test stdlib packages on wasip1 run: make tinygo-test-wasip1-fast TINYGO=$(PWD)/build/tinygo/bin/tinygo ================================================ FILE: .gitignore ================================================ .DS_Store .vscode go.work go.work.sum docs/_build src/device/avr/*.go src/device/avr/*.ld src/device/avr/*.s src/device/esp/*.go src/device/nrf/*.go src/device/nrf/*.s src/device/nxp/*.go src/device/nxp/*.s src/device/sam/*.go src/device/sam/*.s src/device/sifive/*.go src/device/sifive/*.s src/device/stm32/*.go src/device/stm32/*.s src/device/kendryte/*.go src/device/kendryte/*.s src/device/renesas/*.go src/device/renesas/*.s src/device/rp/*.go src/device/rp/*.s ./vendor llvm-build llvm-project build/* # Ignore files generated by smoketest test test.bin test.elf test.exe test.gba test.hex test.nro test.uf2 test.wasm wasm.wasm *.uf2 *.elf ================================================ FILE: .gitmodules ================================================ [submodule "lib/nrfx"] path = lib/nrfx url = https://github.com/NordicSemiconductor/nrfx.git [submodule "lib/CMSIS"] path = lib/CMSIS url = https://github.com/ARM-software/CMSIS.git [submodule "lib/avr"] path = lib/avr url = https://github.com/avr-rust/avr-mcu.git [submodule "lib/cmsis-svd"] path = lib/cmsis-svd url = https://github.com/cmsis-svd/cmsis-svd-data.git branch = main [submodule "lib/wasi-libc"] path = lib/wasi-libc url = https://github.com/WebAssembly/wasi-libc [submodule "lib/picolibc"] path = lib/picolibc url = https://github.com/picolibc/picolibc.git [submodule "lib/stm32-svd"] path = lib/stm32-svd url = https://github.com/tinygo-org/stm32-svd [submodule "lib/musl"] path = lib/musl url = https://github.com/tinygo-org/musl-libc.git [submodule "lib/binaryen"] path = lib/binaryen url = https://github.com/WebAssembly/binaryen.git [submodule "lib/mingw-w64"] path = lib/mingw-w64 url = https://github.com/mingw-w64/mingw-w64.git [submodule "lib/macos-minimal-sdk"] path = lib/macos-minimal-sdk url = https://github.com/aykevl/macos-minimal-sdk.git [submodule "src/net"] path = src/net url = https://github.com/tinygo-org/net.git [submodule "lib/wasi-cli"] path = lib/wasi-cli url = https://github.com/WebAssembly/wasi-cli [submodule "lib/bdwgc"] path = lib/bdwgc url = https://github.com/ivmai/bdwgc.git ================================================ FILE: BUILDING.md ================================================ # Building TinyGo TinyGo depends on LLVM and libclang, which are both big C++ libraries. It can also optionally use a built-in lld to ease cross compiling. There are two ways these can be linked: dynamically and statically. An install with `go install` is dynamic linking because it is fast and works almost out of the box on Debian-based systems with the right packages installed. This guide describes how to statically link TinyGo against LLVM, libclang and lld so that the binary can be easily moved between systems. It also shows how to build a release tarball that includes this binary and all necessary extra files. **Note**: this documentation describes how to build a statically linked release tarball. If you want to help with development of TinyGo itself, you should follow the guide located at https://tinygo.org/docs/guides/build/ ## Dependencies LLVM, Clang and LLD are quite light on dependencies, requiring only standard build tools to be built. Go is of course necessary to build TinyGo itself. * Go (1.19+) * GNU Make * Standard build tools (gcc/clang) * git * CMake * [Ninja](https://ninja-build.org/) The rest of this guide assumes you're running Linux, but it should be equivalent on a different system like Mac. ## Using GNU Make The static build of TinyGo is driven by GNUmakefile, which provides a help target for quick reference: % make help clean Remove build directory fmt Reformat source fmt-check Warn if any source needs reformatting gen-device Generate microcontroller-specific sources llvm-source Get LLVM sources llvm-build Build LLVM tinygo Build the TinyGo compiler lint Lint source tree spell Spellcheck source tree ## Download the source The first step is to download the TinyGo sources (use `--recursive` if you clone the git repository). Then, inside the directory, download the LLVM source: make llvm-source You can also store LLVM outside of the TinyGo root directory by setting the `LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not covered by this guide. ## Build LLVM, Clang, LLD Before starting the build, you may want to set the following environment variables to speed up the build. Most Linux distributions ship with GCC as the default compiler, but Clang is significantly faster and uses much less memory while producing binaries that are about as fast. export CC=clang export CXX=clang++ The Makefile includes a default configuration that is good for most users. It builds a release version of LLVM (optimized, no asserts) and includes all targets supported by TinyGo: make llvm-build This can take over an hour depending on the speed of your system. ## Build TinyGo The last step of course is to build TinyGo itself. This can again be done with make: make ## Verify TinyGo Try running TinyGo: ./build/tinygo help Also, make sure the `tinygo` binary really is statically linked. The command to check for dynamic dependencies differs depending on your operating system. On Linux, use `ldd` (not to be confused with `lld`): ldd ./build/tinygo On macOS, use otool -L: otool -L ./build/tinygo The result should not contain libclang or libLLVM. ## Make a release tarball Now that we have a working static build, it's time to make a release tarball: make release If you did not clone the repository with the `--recursive` option, you will get errors until you initialize the project submodules: git submodule update --init The release tarball is stored in build/release.tar.gz, and can be extracted with the following command (for example in ~/lib): tar -xvf path/to/release.tar.gz TinyGo will get extracted to a `tinygo` directory. You can then call it with: ./tinygo/bin/tinygo ================================================ FILE: CHANGELOG.md ================================================ 0.40.1 --- * **machine** - nrf: fix flash writes when SoftDevice is enabled * **runtime** - runtime: avoid fixed math/rand sequence on RP2040/RP2350 (#5124) - runtime: add calls to initRand() during run() for all schedulers - runtime: call initRand() before initHeap() during initialization - runtime: use rand_hwrng hardwareRand for RP2040/RP2350 (#5135) * **libs** - picolibc: use updated location for git repo 0.40.0 --- * **general** - all: add full LLVM 20 support - core: feat: enable //go:linkname pragma for globals - core: feature: Add flag to ignore go compatibility matrix (#5078) - chore: update version for 0.40 development cycle * **compiler** - emit an error when the actual arch doesn't match GOARCH - mark string parameters as readonly - use Tarjan's SCC algorithm to detect loops for defer - lower "large stack" limit to 16kb * **core** - shrink bdwgc library - Fix linker errors for runtime.vgetrandom and crypto/internal/sysrand.fatal - fix: add TryLock to sync.RWMutex - fix: correct linter issues exposed by the fix in #4679 - fix: don't hardcode success return state - fix: expand RTT debugger compatibility - internal/task (threads): save stack bounds instead of scanning under a lock - internal/task: create detached threads and fix error handling - interp: better errors when debugging interp - transform (gc): create stack slots in callers of external functions - internal/task: prevent semaphore resource leak for threads scheduler * **machine** - cortexm: optimize code size for the HardFault_Handler - fe310: add I2C pins for the HiFive1b - clarify WriteAt semantics of BlockDevice - fix deprecated AsmFull comment (#5005) - make sure DMA buffers do not escape unnecessarily - only enable USB-CDC when needed - use larger SPI MAXCNT on nrf52833 and nrf52840 - fix: update m.queuedBytes when clamping output to avoid corrupting sentBytes - fix: use int64 in ReadTemperature to avoid overflow - fix(rp2): disable DBGPAUSE on startup - fix(rp2): possible integer overflow while computing factors for SPI baudrate - fix(rp2): reset spinlocks at startup - fix(rp2): switch spinlock busy loop to wfe - fix(rp2): use side-effect-free spinlocks - nrf: add ADC_VDDH which is an ADC pin for VDDH - nrf: don't block SPI transfer - nrf: don't set PSELN, it's ignored in single ended mode anyway - nrf: fix typo in ADC configuration - nrf: refactor SoftDevice enabled check - nrf: rename pwmPin to adcPin - nrf: support flash operations while the SoftDevice is enabled - rp2040: allow writing to the UART inside interrupts - machine,nrf528: stop the bus only once on I2C bus error and ensures the first error is returned * **net** - update submodule to latest commits * **runtime** - (avr): fix infinite longjmp loop if stack is aligned to 256 bytes - (gc_blocks.go): clear full size of allocation - (gc_blocks.go): make sweep branchless - (gc_blocks.go): simplify scanning logic - (gc_blocks.go): use a linked stack to scan marked objects - (gc_blocks.go): use best-fit allocation - (gc_boehm.go): fix world already stopped check - (wasm): scan the system stack - fix sleep duration for long sleeps - remove copied code for nrf52840 - src/syscall: update src buffer after write - wasm: fix C realloc and optimize it a bit * **targets** - add xiao-esp32s3 board target - Add ESP32-S3 support (#5091) - Added Gopher ARCADE board - Create "pico2-ice" target board (#5062) * **build/test** - Add testing.T.Context() and testing.B.Context() - create separate go.mod file for testing dependencies for wasm tests that use Chromium headless browser to avoid use of older incompatible version. - go back to normal scheduler instead of tasks scheduler for macos CI - update CI to use Go 1.25.5 - update macOS GH actions builds to handle sunset of macOS 13 - use task scheduler on macOS builds to avoid test race condition lockups - update all CI builds to use latest stable Go release. Also update some of the actions to their latest releases. - update GH actions builds to use Go 1.25.4 - uninstall cmake before install - fix: point the submodule for musl-lib to a mirror in the TinyGo GitHub org - fix: remove macOS 15 from CI build matrix (conflicts with macOS 14 build) - fix: separate host expected bytes from device intended bytes - fix/typo: makeESPFirmwareImage - make: GNUmakefile: shrink TinyGo binaries on Linux - move the directory list into a variable - several improvements to the macOS GH actions build - Fix for #4678: top-level 'make lint' wasn't working - fix: increase the timeout for chromedp to connect to the headless browser used for running the wasm tests. - testdata: some more packages for the test corpus 0.39.0 --- * **general** - all: add Go 1.25 support - net: update to latest tinygo net package - docs: clarify build verification step for macOS users - Add flag to skip Renesas SVD builds * **build** - Makefile: install missing dlmalloc files - flash: add -o flag support to save built binary (Fixes #4937) (#4942) - fix: update version of clang to 17 to accommodate latest Go 1.25 docker base image * **ci** - chore: update all CI builds to test Go 1.25 release - fix: disable test-newest since CircleCI seems unable to download due to rate-limits on Dockerhub - ci: rename some jobs to avoid churn on every Go/LLVM version bump - ci: make the goroutines test less racy - tests: de-flake goroutines test * **compiler** - compiler: implement internal/abi.Escape * **main** - main: show the compiler error (if any) for `tinygo test -c` - chore: correct GOOS=js name in error messages for WASM * **machine** - machine: add international keys - machine: remove some unnecessary "// peripherals:" comments - machine: add I2C pin comments - machine: standardize I2C errors with "i2c:" prefix - machine: make I2C usable in the simulator - fix: add SPI and I2C to teensy 4.1 (#4943) - `rp2`: use the correct channel mask for rp2350 ADC; hold lock during read (#4938) - `rp2`: disable digital input for analog inputs * **runtime** - runtime: ensure time.Sleep(d) sleeps at least d - runtime: stub out weak pointer support - runtime: implement dummy AddCleanup - runtime: enable multi-core scheduler for rp2350 - `internal/task`: use -stack-size flag when starting a new thread - `internal/task`: add SA_RESTART flag to GC interrupts - `internal/task`: a few small correctness fixes - `internal/gclayout`: make gclayout values constants - darwin: add threading support and use it by default * **standard library** - `sync`: implement sync.Swap - `reflect`: implement Method.IsExported * **testing** - testing: stub out testing.B.Loop * **targets** - `stm32`: add support for the STM32L031G6U6 - add metro-rp2350 board definition (#4989) - `rp2040/rp2350`: set the default stack size to 8k for rp2040/rp2350 based boards where this was not already the case 0.38.0 --- * **general** - `go.*`: upgrade `golang.org/x/tools` to v0.30.0 - `all`: add support for LLVM 20 * **build** - go back to using MinoruSekine/setup-scoop for Windows CI builds - `flake.*`: upgrade to nixpkgs 25.05, LLVM 20 - `Makefile`: only detect ccache command when needed - `Makefile`: create random filename inside rule - `Makefile`: don't set GOROOT - `Makefile`: call uname at most once - `Makefile`: only read NodeJS version when it is needed * **compiler** - add support for `GODEBUG=gotypesalias=1` - `interp`: fix `copy()` from/to external buffers - add `-nobounds` (similar to `-gcflags=-B`) - `compileopts`: add library version to cached library path - `builder`: build wasi-libc inside TinyGo - `builder`: simplify bdwgc libc dependency - `builder`: don't use precompiled libraries - `compileopts`: enable support for `GOARCH=wasm` in `tinygo test` * **fixes** - `rp2350`: Fix DMA to SPI transmits on RP2350 (#4903) - `microbit v2`: use OpenOCD flash method on microbit v2 when using Nordic Semi SoftDevice - `main`: display all of the current GC options for the `-gc` flag - Remove duplicated error handling - `sync`: fix `TestMutexConcurrent` test - fix race condition in `testdata/goroutines.go` - fix build warnings on Windows ARM * **machine** - `usb`: add USB mass storage class support - implement usb receive message throttling - declare usb endpoints per-platform - `samd21`: implement watchdog - `samd51`: write to flash memory in 512 byte long chunks - `samd21`: write to flash memory in 64 byte long chunks - don't inline RTT `WriteByte` everywhere - `rp2`: unexport machine-specific errors - `rp2`: discount scheduling delays in I2C timeouts (#4876) - use pointer receiver in simulated PWM peripherals - add simulated PWM/timer peripherals - `rp2`: expose usb endpoint stall handling - `arm`: clear pending interrupts before enabling them - `rp2`: merge common usb code (#4856) * **main** - add "cores" and "threads" schedulers to help text - add `StartPos` and `EndPos` to `-json` build output - change `-json` flag to match upstream Go * **runtime** - don't lock the print output inside interrupts - don't try to interrupt other cores before they are started - implement `NumCPU` for the multicore scheduler - add support for multicore scheduler - refactor obtaining the system stack - `interrupt`: add `Checkpoint` type - add `exportedFuncPtr` - avoid an allocation in `(*time.Timer).Reset` - stub runtime signal functions for `os/signal` on wasip1 - move `timeUnit` to a single place - implement `NumCPU` for `-scheduler=threads` - move `mainExited` boolean - `internal/task`: rename `tinygo_pause` to `tinygo_task_exit` - map every goroutine to a new OS thread - refactor `timerQueue` - make conservative and precise GC MT-safe - `internal/task`: implement atomic primitives for preemptive scheduling - Use diskutil on macOS to extract volume name and path for FAT mounts #4928 * **standard library** - `net`: update submodule to latest commits - `runtime/debug`: add GC related stubs - `metrics`: flesh out some of the metric types - `reflect`: Chan related stubs - `os`: handle relative and abs paths in `Executable()` - `os`: add `os.Executable()` for Darwin - `sync`: implement `RWMutex` using futexes - `reflect`: Add `SliceOf`, `ArrayOf`, `StructOf`, `MapOf`, `FuncOf` * **targets** - `rp2040`: add multicore support - `riscv32`: use `gdb` binary as a fallback - add target for Microbit v2 with SoftDevice S140 support for both peripheral and central - `windows`: use MSVCRT.DLL instead of UCRT on i386 - `windows`: add windows/386 support - `arm64`: remove unnecessary `.section` directive - `riscv-qemu`: actually sleep in `time.Sleep()` - `riscv`: define CSR constants and use them where possible - `darwin`: support Boehm GC (and use by default) - `windows`: add support for the Boehm-Demers-Weiser GC - `windows`: fix wrong register for first parameter * **wasm** - add Boehm GC support - refactor/modify stub signal handling - don't block `//go:wasmexport` because of running goroutines - use `int64` instead of `float64` for the `timeUnit` * **boards** - Add board support for BigTreeTech SKR Pico (#4842) 0.37.0 --- * **general** - add the Boehm-Demers-Weiser GC on Linux * **ci** - add more tests for wasm and baremetal * **compiler** - crypto/internal/sysrand is allowed to use unsafe signatures * **examples** - add goroutine benchmark to examples * **fixes** - ensure use of pointers for SPI interface on atsam21/atsam51 and other machines/boards that were missing implementation (#4798) - replace loop counter with hw timer for USB SetAddressReq on rp2040 (#4796) * **internal** - update to go.bytecodealliance.org@v0.6.2 in GNUmakefile and internal/wasm-tools - exclude certain files when copying package in internal/cm - update to go.bytecodealliance.org/cm@v0.2.2 in internal/cm - remove old reflect.go in internal/reflectlite * **loader** - use build tags for package iter and iter methods on reflect.Value in loader, iter, reflect - add shim for go1.22 and earlier in loader, iter * **machine** - bump rp2040 to 200MHz (#4768) - correct register address for Pin.SetInterrupt for rp2350 (#4782) - don't block the rp2xxx UART interrupt handler - fix RP2040 Pico board on the playground - add flash support for rp2350 (#4803) * **os** - add stub Symlink for wasm * **refactor** - use *SPI everywhere to make consistent for implementations. Fixes #4663 "in reverse" by making SPI a pointer everywhere, as discussed in the comments. * **reflect** - add Value.SetIter{Key,Value} and MapIter.Reset in reflect, internal/reflectlite - embed reflectlite types into reflect types in reflect, internal/reflectlite - add Go 1.24 iter.Seq[2] methods - copy reflect iter tests from upstream Go - panic on Type.CanSeq[2] instead of returning false - remove strconv.go - remove unused go:linkname functions * **riscv-qemu** - add VirtIO RNG device - increase stack size * **runtime** - only allocate heap memory when needed - remove unused file func.go - use package reflectlite * **transform** - cherry-pick from #4774 0.36.0 --- * **general** - add initial Go 1.24 support - add support for LLVM 19 - update license for 2025 - make small corrections for README regarding wasm - use GOOS and GOARCH for building wasm simulated boards - only infer target for wasm when GOOS and GOARCH are set correctly, not just based on file extension - add test-corpus-wasip2 - use older image for cross-compiling builds - update Linux builds to run on ubuntu-latest since 20.04 is being retired - ensure build output directory is created - add NoSandbox flag to chrome headless that is run during WASM tests, since this is now required for Ubuntu 23+ and we are using Ubuntu 24+ when running Github Actions - update wasmtime used for CI to 29.0.1 to fix issue with install during CI tests - update to use `Get-CimInstance` as `wmic` is being deprecated on WIndows - remove unnecessary executable permissions - `goenv`: update to new v0.36.0 development version * **compiler** - `builder`: fix parsing of external ld.lld error messages - `cgo`: mangle identifier names - `interp`: correctly mark functions as modifying memory - add buildmode=wasi-legacy to support existing base of users who expected the older behavior for wasi modules to not return an exit code as if they were reactors * **standard library** - `crypto/tls`: add Dialer.DialContext() to fix websocket client - `crypto/tls`: add VersionTLS constants and VersionName(version uint16) method that turns it into a string, copied from big go - `internal/syscall/unix`: use our own version of this package - `machine`: replace hard-coded cpu frequencies on rp2xxx - `machine`: bump rp2350 CPUFrequency to 150 MHz - `machine`: compute rp2 clock dividers from crystal and target frequency - `machine`: remove bytes package dependency in flash code - `machine/usb/descriptor`: avoid bytes package - `net`: update to latest submodule with httptest subpackage and ResolveIPAddress implementation - `os`: add File.Chdir support - `os`: implement stub Chdir for non-OS systems - `os/file`: add file.Chmod - `reflect`: implement Value.Equal - `runtime`: add FIPS helper functions - `runtime`: manually initialize xorshift state - `sync`: move Mutex to internal/task - `syscall`: add wasip1 RandomGet - `testing`: add Chdir - `wasip2`: add stubs to get internal/syscall/unix to work * **fixes** - correctly handle calls for GetRNG() when being made from nrf devices with SoftDevice enabled - fix stm32f103 ADC - `wasm`: correctly handle id lookup for finalizeRef call - `wasm`: avoid total failure on wasm finalizer call - `wasm`: convert offset as signed int into unsigned int in syscall/js.stringVal in wasm_exec.js * **targets** - rp2350: add pll generalized solution; fix ADC handles; pwm period fix - rp2350: extending support to include the rp2350b - rp2350: cleanup: unexport internal USB and clock package variable, consts and types - nrf: make ADC resolution changeable - turn on GC for TKey1 device, since it does in fact work - match Pico2 stack size to Pico * **boards** - add support for Pimoroni Pico Plus2 - add target for pico2-w board - add comboat_fw tag for elecrow W5 boards with Combo-AT Wifi firmware - add support for Elecrow Pico rp2350 W5 boards - add support for Elecrow Pico rp2040 W5 boards - add support for NRF51 HW-651 - add support for esp32c3-supermini - add support for waveshare-rp2040-tiny * **examples** - add naive debouncing for pininterrupt example 0.35.0 --- * **general** - update cmsis-svd library - use default UART settings in the echo example - `goenv`: also show git hash with custom build of TinyGo - `goenv`: support parsing development versions of Go - `main`: parse extldflags early so we can report the error message * **compiler** - `builder`: whitelist temporary directory env var for Clang invocation to fix Windows bug - `builder`: fix cache paths in `-size=full` output - `builder`: work around incorrectly escaped DWARF paths on Windows (Clang bug) - `builder`: fix wasi-libc path names on Windows with `-size=full` - `builder`: write HTML size report - `cgo`: support C identifiers only referred to from within macros - `cgo`: support function-like macros - `cgo`: support errno value as second return parameter - `cgo`: add support for `#cgo noescape` lines - `compiler`: fix bug in interrupt lowering - `compiler`: allow panic directly in `defer` - `compiler`: fix wasmimport -> wasmexport in error message - `compiler`: support `//go:noescape` pragma - `compiler`: report error instead of crashing when instantiating a generic function without body - `interp`: align created globals * **standard library** - `machine`: modify i2s interface/implementation to better match specification - `os`: implement `StartProcess` - `reflect`: add `Value.Clear` - `reflect`: add interface support to `NumMethods` - `reflect`: fix `AssignableTo` for named + non-named types - `reflect`: implement `CanConvert` - `reflect`: handle more cases in `Convert` - `reflect`: fix Copy of non-pointer array with size > 64bits - `runtime`: don't call sleepTicks with a negative duration - `runtime`: optimize GC scanning (findHead) - `runtime`: move constants into shared package - `runtime`: add `runtime.fcntl` function for internal/syscall/unix - `runtime`: heapptr only needs to be initialized once - `runtime`: refactor scheduler (this fixes a few bugs with `-scheduler=none`) - `runtime`: rewrite channel implementation to be smaller and more flexible - `runtime`: use `SA_RESTART` when registering a signal for os/signal - `runtime`: implement race-free signals using futexes - `runtime`: run deferred functions in `Goexit` - `runtime`: remove `Cond` which seems to be unused - `runtime`: properly handle unix read on directory - `runtime/trace`: stub all public methods - `sync`: don't use volatile in `Mutex` - `sync`: implement `WaitGroup` using a (pseudo)futex - `sync`: make `Cond` parallelism-safe - `syscall`: use wasi-libc tables for wasm/js target * **targets** - `mips`: fix a bug when scanning the stack - `nintendoswitch`: get this target to compile again - `rp2350`: add support for the new RP2350 - `rp2040/rp2350` : make I2C implementation shared for rp2040/rp2350 - `rp2040/rp2350` : make SPI implementation shared for rp2040/rp2350 - `rp2040/rp2350` : make RNG implementation shared for rp2040/rp2350 - `wasm`: revise and simplify wasmtime argument handling - `wasm`: support `//go:wasmexport` functions after a call to `time.Sleep` - `wasm`: correctly return from run() in wasm_exec.js - `wasm`: call process.exit() when go.run() returns - `windows`: don't return, exit via exit(0) instead to flush stdout buffer * **boards** - add support for the Tillitis TKey - add support for the Raspberry Pi Pico2 (based on the RP2040) - add support for Pimoroni Tiny2350 0.34.0 --- * **general** - fix `GOOS=wasip1` for `tinygo test` - add `-C DIR` flag - add initial documentation for project governance - add `-ldflags='-extldflags=...'` support - improve usage message with `tinygo help` and when passing invalid parameters * **compiler** - `builder`: remove environment variables when invoking Clang, to avoid the environment changing the behavior - `builder`: check for the Go toolchain version used to compile TinyGo - `cgo`: add `C.CBytes` implementation - `compiler`: fix passing weirdly-padded structs as parameters to new goroutines - `compiler`: support pragmas on generic functions - `compiler`: do not let the slice buffer escape when casting a `[]byte` or `[]rune` to a string, to help escape analysis - `compiler`: conform to the latest iteration of the wasm types proposal - `loader`: don't panic when main package is not named 'main' - `loader`: make sure we always return type checker errors even without type errors - `transform`: optimize range over `[]byte(string)` * **standard library** - `crypto/x509`: add package stub to build crypto/x509 on macOS - `machine/usb/adc/midi`: fix `PitchBend` - `os`: add `Truncate` stub for baremetal - `os`: add stubs for `os.File` deadlines - `os`: add internal `net.newUnixFile` for the net package - `runtime`: stub runtime_{Before,After}Exec for linkage - `runtime`: randomize map accesses - `runtime`: support `maps.Clone` - `runtime`: add more fields to `MemStats` - `runtime`: implement newcoro, coroswitch to support package iter - `runtime`: disallow defer in interrupts - `runtime`: add support for os/signal on Linux and MacOS - `runtime`: add gc layout info for some basic types to help the precise GC - `runtime`: bump GC mark stack size to avoid excessive heap rescans * **targets** - `darwin`: use Go standard library syscall package instead of a custom one - `fe310`: support GPIO `PinInput` - `mips`: fix compiler crash with GOMIPS=softfloat and defer - `mips`: add big-endian (GOARCH=mips) support - `mips`: use MIPS32 (instead of MIPS32R2) as the instruction set for wider compatibility - `wasi`: add relative and absolute --dir options to wasmtime args - `wasip2`: add wasmtime -S args to support network interfaces - `wasm`: add `//go:wasmexport` support (for all WebAssembly targets) - `wasm`: use precise instead of conservative GC for WebAssembly (including WASI) - `wasm-unknown`: add bulk memory flags since basically every runtime has it now * **boards** - add RAKwireless RAK4631 - add WaveShare ESP-C3-32S-Kit 0.33.0 --- * **general** - use latest version of x/tools - add chromeos 9p support for flashing - sort compiler error messages by source position in a package - don't include prebuilt libraries in the release to simplify packaging and reduce the release tarball size - show runtime panic addresses for `tinygo run` - support Go 1.23 (including all new language features) - `test`: support GOOS/GOARCH pairs in the `-target` flag - `test`: remove message after test binary built * **compiler** - remove unused registers for x86_64 linux syscalls - remove old atomics workaround for AVR (not necessary in modern LLVM versions) - support `golang.org/x/sys/unix` syscalls - `builder`: remove workaround for generics race condition - `builder`: add package ID to compiler and optimization error messages - `builder`: show better error messages for some common linker errors - `cgo`: support preprocessor macros passed on the command line - `cgo`: use absolute paths for error messages - `cgo`: add support for printf - `loader`: handle `go list` errors inside TinyGo (for better error messages) - `transform`: fix incorrect alignment of heap-to-stack transform - `transform`: use thinlto-pre-link passes (instead of the full pipeline) to speed up compilation speed slightly * **standard library** - `crypto/tls`: add CipherSuiteName and some extra fields to ConnectionSTate - `internal/abi`: implement initial version of this package - `machine`: use new `internal/binary` package - `machine`: rewrite Reply() to fix sending long replies in I2C Target Mode - `machine/usb/descriptor`: Reset joystick physical - `machine/usb/descriptor`: Drop second joystick hat - `machine/usb/descriptor`: Add more HID... functions - `machine/usb/descriptor`: Fix encoding of values - `machine/usb/hid/joystick`: Allow more hat switches - `os`: add `Chown`, `Truncate` - `os/user`: use stdlib version of this package - `reflect`: return correct name for the `unsafe.Pointer` type - `reflect`: implement `Type.Overflow*` functions - `runtime`: implement dummy `getAuxv` to satisfy golang.org/x/sys/ - `runtime`: don't zero out new allocations for `-gc=leaking` when they are already zeroed - `runtime`: simplify slice growing/appending code - `runtime`: print a message when a fatal signal like SIGSEGV happens - `runtime/debug`: add `GoVersion` to `debug.BuildInfo` - `sync`: add `Map.Clear()` - `sync/atomic`: add And* and Or* compiler intrinsics needed for Go 1.23 - `syscall`: add `Fork` and `Execve` - `syscall`: add all MacOS errno values - `testing`: stub out `T.Deadline` - `unique`: implement custom (naive) version of the unique package * **targets** - `arm`: support `GOARM=*,softfloat` (softfloat support for ARM v5, v6, and v7) - `mips`: add linux/mipsle (and experimental linux/mips) support - `mips`: add `GOMIPS=softfloat` support - `wasip2`: add WASI preview 2 support - `wasm/js`: add `node:` prefix in `require()` call of wasm_exec.js - `wasm-unknown`: make sure the `os` package can be imported - `wasm-unknown`: remove import-memory flag 0.32.0 --- * **general** - fix wasi-libc include headers on Nix - apply OpenOCD commands after target configuration - fix a minor race condition when determining the build tags - support UF2 drives with a space in their name on Linux - add LLVM 18 support - drop support for Go 1.18 to be able to stay up to date * **compiler** - move `-panic=trap` support to the compiler/runtime - fix symbol table index for WebAssembly archives - fix ed25519 build errors by adjusting the alias names - add aliases to generic AES functions - fix race condition by temporarily applying a proposed patch - `builder`: keep un-wasm-opt'd .wasm if -work was passed - `builder`: make sure wasm-opt command line is printed if asked - `cgo`: implement shift operations in preprocessor macros - `interp`: checking for methodset existence * **standard library** - `machine`: add `__tinygo_spi_tx` function to simulator - `machine`: fix simulator I2C support - `machine`: add GetRNG support to simulator - `machine`: add `TxFifoFreeLevel` for CAN - `os`: add `Link` - `os`: add `FindProcess` for posix - `os`: add `Process.Release` for unix - `os`: add `SetReadDeadline` stub - `os`, `os/signal`: add signal stubs - `os/user`: add stubs for `Lookup{,Group}` and `Group` - `reflect`: use int in `StringHeader` and `SliceHeader` on non-AVR platforms - `reflect`: fix `NumMethods` for Interface type - `runtime`: skip negative sleep durations in sleepTicks * **targets** - `esp32`: add I2C support - `rp2040`: move UART0 and UART1 to common file - `rp2040`: make all RP2040 boards available for simulation - `rp2040`: fix timeUnit type - `stm32`: add i2c `Frequency` and `SetBaudRate` function for chips that were missing implementation - `wasm-unknown`: add math and memory builtins that LLVM needs - `wasip1`: replace existing `-target=wasi` support with wasip1 as supported in Go 1.21+ * **boards** - `adafruit-esp32-feather-v2`: add the Adafruit ESP32 Feather V2 - `badger2040-w`: add support for the Badger2040 W - `feather-nrf52840-sense`: fix lack of LXFO - `m5paper`: add support for the M5 Paper - `mksnanov3`: limit programming speed to 1800 kHz - `nucleol476rg`: add stm32 nucleol476rg support - `pico-w`: add the Pico W (which is near-idential to the pico target) - `thingplus-rp2040`, `waveshare-rp2040-zero`: add WS2812 definition - `pca10059-s140v7`: add this variant to the PCA10059 board 0.31.2 --- * **general** * update the `net` submodule to updated version with `Buffers` implementation * **compiler** * `syscall`: add wasm_unknown tag to some additional files so it can compile more code * **standard library** * `runtime`: add Frame.Entry field 0.31.1 --- * **general** * fix Binaryen build in make task * update final build stage of Docker `dev` image to go1.22 * only use GHA cache for building Docker `dev` image * update the `net` submodule to latest version * **compiler** * `interp`: make getelementptr offsets signed * `interp`: return a proper error message when indexing out of range 0.31.0 --- * **general** * remove LLVM 14 support * add LLVM 17 support, and use it by default * add Nix flake support * update bundled Binaryen to version 116 * add `ports` subcommand that lists available serial ports for `-port` and `-monitor` * support wasmtime version 14 * add `-serial=rtt` for serial output over SWD * add Go 1.22 support and use it by default * change minimum Node.js version from 16 to 18 * **compiler** * use the new LLVM pass manager * allow systems with more stack space to allocate larger values on the stack * `build`: fix a crash due to sharing GlobalValues between build instances * `cgo`: add `C._Bool` type * `cgo`: fix calling CGo callback inside generic function * `compileopts`: set `purego` build tag by default so that more packages can be built * `compileopts`: force-enable CGo to avoid build issues * `compiler`: fix crash on type assert on interfaces with no methods * `interp`: print LLVM instruction in traceback * `interp`: support runtime times by running them at runtime * `loader`: enforce Go language version in the type checker (this may break existing programs with an incorrect Go version in go.mod) * `transform`: fix bug in StringToBytes optimization pass * **standard library** * `crypto/tls`: stub out a lot of functions * `internal/task`, `machine`: make TinyGo code usable with "big Go" CGo * `machine`: implement `I2C.SetBaudRate` consistently across chips * `machine`: implement `SPI.Configure` consistently across chips * `machine`: add `DeviceID` for nrf, rp2040, sam, stm32 * `machine`: use smaller UART buffer size on atmega chips * `machine/usb`: allow setting a serial number using a linker flag * `math`: support more math functions on baremetal (picolibc) systems * `net`: replace entire net package with a new one based on the netdev driver * `os/user`: add bare-bones implementation of this package * `reflect`: stub `CallSlice` and `FuncOf` * `reflect`: add `TypeFor[T]` * `reflect`: update `IsZero` to Go 1.22 semantics * `reflect`: move indirect values into interface when setting interfaces * `runtime`: stub `Breakpoint` * `sync`: implement trylock * **targets** * `atmega`: use UART double speed mode for fewer errors and higher throughput * `atmega328pb`: refactor to enable extra uart * `avr`: don't compile large parts of picolibc (math, stdio) for LLVM 17 support * `esp32`: switch over to the official SVD file * `esp32c3`: implement USB_SERIAL for USBCDC communication * `esp32c3`: implement I2C * `esp32c3`: implement RNG * `esp32c3`: add more ROM functions and update linker script for the in-progress wifi support * `esp32c3`: update to newer SVD files * `rp2040`: add support for UART hardware flow control * `rp2040`: add definition for `machine.PinToggle` * `rp2040`: set XOSC startup delay multiplier * `samd21`: add support for UART hardware flow control * `samd51`: add support for UART hardware flow control * `wasm`: increase default stack size to 64k for wasi/wasm targets * `wasm`: bump wasi-libc version to SDK 20 * `wasm`: remove line of dead code in wasm_exec.js * **new targets/boards** * `qtpy-esp32c3`: add Adafruit QT Py ESP32-C3 board * `mksnanov3`: add support for the MKS Robin Nano V3.x * `nrf52840-generic`: add generic nrf52840 chip support * `thumby`: add support for Thumby * `wasm`: add new `wasm-unknown` target that doesn't depend on WASI or a browser * **boards** * `arduino-mkrwifi1010`, `arduino-nano33`, `nano-rp2040`, `matrixportal-m4`, `metro-m4-airlift`, `pybadge`, `pyportal`: add `ninafw` build tag and some constants for BLE support * `gopher-badge`: fix typo in USB product name * `nano-rp2040`: add UART1 and correct mappings for NINA via UART * `pico`: bump default stack size from 2kB to 8kB * `wioterminal`: expose UART4 0.30.0 --- * **general** - add LLVM 16 support, use it by default * **compiler** - `build`: work around a race condition by building Go SSA serially - `compiler`: fix a crash by not using the LLVM global context types - `interp`: don't copy unknown values in `runtime.sliceCopy` to fix miscompile - `interp`: fix crash in error report by not returning raw LLVM values * **standard library** - `machine/usb/adc/midi`: various improvements and API changes - `reflect`: add support for `[...]T` → `[]T` in reflect * **targets** - `atsamd21`, `atsamd51`: add support for USB INTERRUPT OUT - `rp2040`: always use the USB device enumeration fix, even in chips that supposedly have the HW fix - `wasm`: increase default stack size to 32k for wasi/wasm * **boards** - `gobadge`: add GoBadge target as alias for PyBadge :) - `gemma-m0`: add support for the Adafruit Gemma M0 0.29.0 --- * **general** - Go 1.21 support - use https for renesas submodule #3856 - ci: rename release-double-zipped to something more useful - ci: update Node.js from version 14 to version 16 - ci: switch GH actions builds to use Go 1.21 final release - docker: update clang to version 15 - docker: use Go 1.21 for Docker dev container build - `main`: add target JSON file in `tinygo info` output - `main`: improve detection of filesystems - `main`: use `go env` instead of doing all detection manually - make: add make task to generate Renesas device wrappers - make: add task to check NodeJS version before running tests - add submodule for Renesas SVD file mirror repo - update to go-serial package v1.6.0 - `testing`: add Testing function - `tools/gen-device-svd`: small changes needed for Renesas MCUs * **compiler** - `builder`: update message for max supported Go version - `compiler,reflect`: NumMethods reports exported methods only - `compiler`: add compiler-rt and wasm symbols to table - `compiler`: add compiler-rt to wasm.json - `compiler`: add min and max builtin support - `compiler`: implement clear builtin for maps - `compiler`: implement clear builtin for slices - `compiler`: improve panic message when a runtime call is unavailable - `compiler`: update .ll test output - `loader`: merge go.env file which is now required starting in Go 1.21 to correctly get required packages * **standard library** - `os`: define ErrNoDeadline - `reflect`: Add FieldByNameFunc - `reflect`: add SetZero - `reflect`: fix iterating over maps with interface{} keys - `reflect`: implement Value.Grow - `reflect`: remove unnecessary heap allocations - `reflect`: use .key() instead of a type assert - `sync`: add implementation from upstream Go for OnceFunc, OnceValue, and OnceValues * **targets** - `machine`: UART refactor (#3832) - `machine/avr`: pin change interrupt - `machine/macropad_rp2040`: add machine.BUTTON - `machine/nrf`: add I2C timeout - `machine/nrf`: wait for stop condition after reading from the I2C bus - `machine/nRF52`: set SPI TX/RX lengths even data is empty. Fixes #3868 (#3877) - `machine/rp2040`: add missing suffix to CMD_READ_STATUS - `machine/rp2040`: add NoPin support - `machine/rp2040`: move flash related functions into separate file from C imports for correct - LSP. Fixes #3852 - `machine/rp2040`: wait for 1000 us after flash reset to avoid issues with busy USB bus - `machine/samd51,rp2040,nrf528xx,stm32`: implement watchdog - `machine/samd51`: fix i2cTimeout was decreasing due to cache activation - `machine/usb`: Add support for HID Keyboard LEDs - `machine/usb`: allow USB Endpoint settings to be changed externally - `machine/usb`: refactor endpoint configuration - `machine/usb`: remove usbDescriptorConfig - `machine/usb/hid,joystick`: fix hidreport (3) (#3802) - `machine/usb/hid`: add RxHandler interface - `machine/usb/hid`: rename Handler() to TxHandler() - `wasi`: allow zero inodes when reading directories - `wasm`: add support for GOOS=wasip1 - `wasm`: fix functions exported through //export - `wasm`: remove i64 workaround, use BigInt instead - `example`: adjust time offset - `example`: simplify pininterrupt * **boards** - `targets`: add AKIZUKI DENSHI AE-RP2040 - `targets`: adding new uf2 target for PCA10056 (#3765) 0.28.0 --- * **general** - fix parallelism in the compiler on Windows by building LLVM with thread support - support qemu-user debugging - make target JSON msd-volume-name an array - print source location when a panic happens in -monitor - `test`: don't print `ok` for a successful compile-only * **compiler** - `builder`: remove non-ThinLTO build mode - `builder`: fail earlier if Go is not available - `builder`: improve `-size=full` in a number of ways - `builder`: implement Nordic DFU file writer in Go - `cgo`: allow `LDFLAGS: --export=...` - `compiler`: support recursive slice types - `compiler`: zero struct padding during map operations - `compiler`: add llvm.ident metadata - `compiler`: remove `unsafe.Pointer(uintptr(v) + idx)` optimization (use `unsafe.Add` instead) - `compiler`: add debug info to `//go:embed` data structures for better `-size` output - `compiler`: add debug info to string constants - `compiler`: fix a minor race condition - `compiler`: emit correct alignment in debug info for global variables - `compiler`: correctly generate reflect data for local named types - `compiler`: add alloc attributes to `runtime.alloc`, reducing flash usage slightly - `compiler`: for interface maps, use the original named type if available - `compiler`: implement most math/bits functions as LLVM intrinsics - `compiler`: ensure all defers have been seen before creating rundefers * **standard library** - `internal/task`: disallow blocking inside an interrupt - `machine`: add `CPUReset` - `machine/usb/hid`: add MediaKey support - `machine/usb/hid/joystick`: move joystick under HID - `machine/usb/hid/joystick`: allow joystick settings override - `machine/usb/hid/joystick`: handle case where we cannot find the correct HID descriptor - `machine/usb/hid/mouse`: add support for mouse back and forward - `machine/usb`: add ability to override default VID, PID, manufacturer name, and product name - `net`: added missing `TCPAddr` and `UDPAddr` implementations - `os`: add IsTimeout function - `os`: fix resource leak in `(*File).Close` - `os`: add `(*File).Sync` - `os`: implement `(*File).ReadDir` for wasi - `os`: implement `(*File).WriteAt` - `reflect`: make sure null bytes are supported in tags - `reflect`: refactor this package to enable many new features - `reflect`: add map type methods: `Elem` and `Key` - `reflect`: add map methods: `MapIndex`, `MapRange`/`MapIter`, `SetMapIndex`, `MakeMap`, `MapKeys` - `reflect`: add slice methods: `Append`, `MakeSlice`, `Slice`, `Slice3`, `Copy`, `Bytes`, `SetLen` - `reflect`: add misc methods: `Zero`, `Addr`, `UnsafeAddr`, `OverflowFloat`, `OverflowInt`, `OverflowUint`, `SetBytes`, `Convert`, `CanInt`, `CanFloat`, `CanComplex`, `Comparable` - `reflect`: add type methods: `String`, `PkgPath`, `FieldByName`, `FieldByIndex`, `NumMethod` - `reflect`: add stubs for `Type.Method`, `CanConvert`, `ArrayOf`, `StructOf`, `MapOf` - `reflect`: add stubs for channel select routines/types - `reflect`: allow nil rawType to call Kind() - `reflect`: ensure all ValueError panics have Kind fields - `reflect`: add support for named types - `reflect`: improve `Value.String()` - `reflect`: set `Index` and `PkgPath` field in `Type.Field` - `reflect`: `Type.AssignableTo`: you can assign anything to `interface{}` - `reflect`: add type check to `Value.Field` - `reflect`: let `TypeOf(nil)` return nil - `reflect`: move `StructField.Anonymous` field to match upstream location - `reflect`: add `UnsafePointer` for Func types - `reflect`: `MapIter.Next` needs to allocate new keys/values every time - `reflect`: fix `IsNil` for interfaces - `reflect`: fix `Type.Name` to return an empty string for non-named types - `reflect`: add `VisibleFields` - `reflect`: properly handle embedded structs - `reflect`: make sure `PointerTo` works for named types - `reflect`: `Set`: convert non-interface to interface - `reflect`: `Set`: fix direction of assignment check - `reflect`: support channel directions - `reflect`: print struct tags in Type.String() - `reflect`: properly handle read-only values - `runtime`: allow custom-gc SetFinalizer and clarify KeepAlive - `runtime`: implement KeepAlive using inline assembly - `runtime`: check for heap allocations inside interrupts - `runtime`: properly turn pointer into empty interface when hashing - `runtime`: improve map size hint usage - `runtime`: zero map key/value on deletion to so GC doesn't see them - `runtime`: print the address where a panic happened - `runtime/debug`: stub `SetGCPercent`, `BuildInfo.Settings` - `runtime/metrics`: add this package as a stub - `syscall`: `Stat_t` timespec fields are Atimespec on darwin - `syscall`: add `Timespec.Unix()` for wasi - `syscall`: add fsync using libc - `testing`: support -test.count - `testing`: make test output unbuffered when verbose - `testing`: add -test.skip - `testing`: move runtime.GC() call to runN to match upstream - `testing`: add -test.shuffle to order randomize test and benchmark order * **targets** - `arm64`: fix register save/restore to include vector registers - `attiny1616`: add support for this chip - `cortexm`: refactor EnableInterrupts and DisableInterrupts to avoid `arm.AsmFull` - `cortexm`: enable functions in RAM for go & cgo - `cortexm`: convert SystemStack from `AsmFull` to C inline assembly - `cortexm`: fix crash due to wrong stack size offset - `nrf`: samd21, stm32: add flash API - `nrf`: fix memory issue in ADC read - `nrf`: new peripheral type for nrf528xx chips - `nrf`: implement target mode - `nrf`: improve ADC and add oversampling, longer sample time, and reference voltage - `rp2040`: change calling order for device enumeration fix to do first - `rp2040`: rtc delayed interrupt - `rp2040`: provide better errors for invalid pins on I2C and SPI - `rp2040`: change uart to allow for a single pin - `rp2040`: implement Flash interface - `rp2040`: remove SPI `DataBits` property - `rp2040`: unify all linker scripts using LDFLAGS - `rp2040`: remove SPI deadline for improved performance - `rp2040`: use 4MHz as default frequency for SPI - `rp2040`: implement target mode - `rp2040`: use DMA for send-only SPI transfers - `samd21`: rearrange switch case for get pin cfg - `samd21`: fix issue with WS2812 driver by making pin accesses faster - `samd51`: enable CMCC cache for greatly improved performance - `samd51`: remove extra BK0RDY clear - `samd51`: implement Flash interface - `samd51`: use correct SPI frequency - `samd51`: remove extra BK0RDY clear - `samd51`: fix ADC multisampling - `wasi`: allow users to set the `runtime_memhash_tsip` or `runtime_memhash_fnv` build tags - `wasi`: set `WASMTIME_BACKTRACE_DETAILS` when running in wasmtime. - `wasm`: implement the `//go:wasmimport` directive * **boards** - `gameboy-advance`: switch to use register definitions in device/gba - `gameboy-advance`: rename display and make pointer receivers - `gopher-badge`: Added Gopher Badge support - `lorae5`: add needed definition for UART2 - `lorae5`: correct mapping for I2C bus, add pin mapping to enable power - `pinetime`: update the target file (rename from pinetime-devkit0) - `qtpy`: fix bad pin assignment - `wioterminal`: fix pin definition of BCM13 - `xiao`: Pins D4 & D5 are I2C1. Use pins D2 & D3 for I2C0. - `xiao`: add DefaultUART 0.27.0 --- * **general** - all: update musl - all: remove "acm:"` prefix for USB vid/pid pair - all: add support for LLVM 15 - all: use DWARF version 4 - all: add initial (incomplete) support for Go 1.20 - all: add `-gc=custom` option - `main`: print ldflags including ThinLTO flags with -x - `main`: fix error message when a serial port can't be accessed - `main`: add `-timeout` flag to allow setting how long TinyGo will try looking for a MSD volume for flashing - `test`: print PASS on pass when running standalone test binaries - `test`: fix printing of benchmark output - `test`: print package name when compilation failed (not just when the test failed) * **compiler** - refactor to support LLVM 15 - `builder`: print compiler commands while building a library - `compiler`: fix stack overflow when creating recursive pointer types (fix for LLVM 15+ only) - `compiler`: allow map keys and values of ≥256 bytes - `cgo`: add support for `C.float` and `C.double` - `cgo`: support anonymous enums included in multiple Go files - `cgo`: add support for bitwise operators - `interp`: add support for constant icmp instructions - `transform`: fix memory corruption issues * **standard library** - `machine/usb`: remove allocs in USB ISR - `machine/usb`: add `Port()` and deprecate `New()` to have the API better match the singleton that is actually being returned - `machine/usb`: change HID usage-maximum to 0xFF - `machine/usb`: add USB HID joystick support - `machine/usb`: change to not send before endpoint initialization - `net`: implement `Pipe` - `os`: add stub for `os.Chtimes` - `reflect`: stub out `Type.FieldByIndex` - `reflect`: add `Value.IsZero` method - `reflect`: fix bug in `.Field` method when the field fits in a pointer but the parent doesn't - `runtime`: switch some `panic()` calls in the gc to `runtimePanic()` for consistency - `runtime`: add xorshift-based fastrand64 - `runtime`: fix alignment for arm64, arm, xtensa, riscv - `runtime`: implement precise GC - `runtime/debug`: stub `PrintStack` - `sync`: implement simple pooling in `sync.Pool` - `syscall`: stubbed `Setuid`, Exec and friends - `syscall`: add more stubs as needed for Go 1.20 support - `testing`: implement `t.Setenv` - `unsafe`: add support for Go 1.20 slice/string functions * **targets** - `all`: do not set stack size per board - `all`: update picolibc to v1.7.9 - `atsame5x`: fix CAN extendedID handling - `atsame5x`: reduce heap allocation - `avr`: drop GNU toolchain dependency - `avr`: fix .data initialization for binaries over 64kB - `avr`: support ThinLTO - `baremetal`: implements calloc - `darwin`: fix `syscall.Open` on darwin/arm64 - `darwin`: fix error with `tinygo lldb` - `esp`: use LLVM Xtensa linker instead of Espressif toolchain - `esp`: use ThinLTO for Xtensa - `esp32c3`: add SPI support - `linux`: include musl `getpagesize` function in release - `nrf51`: add ADC implementation - `nrf52840`: add PDM support - `riscv`: add "target-abi" metadata flag - `rp2040`: remove mem allocation in GPIO ISR - `rp2040`: avoid allocating clock on heap - `rp2040`: add basic GPIO support for PIO - `rp2040`: fix USB interrupt issue - `rp2040`: fix RP2040-E5 USB errata - `stm32`: always set ADC pins to pullups floating - `stm32f1`, `stm32f4`: fix ADC by clearing the correct bit for rank after each read - `stm32wl`: Fix incomplete RNG initialisation - `stm32wlx`: change order for init so clock speeds are set before peripheral start - `wasi`: makes wasmtime "run" explicit - `wasm`: fix GC scanning of allocas - `wasm`: allow custom malloc implementation - `wasm`: remove `-wasm-abi=` flag (use `-target` instead) - `wasm`: fix scanning of the stack - `wasm`: fix panic when allocating 0 bytes using malloc - `wasm`: always run wasm-opt even with `-scheduler=none` - `wasm`: avoid miscompile with ThinLTO - `wasm`: allow the emulator to expand `{tmpDir}` - `wasm`: support ThinLTO - `windows`: update mingw-w64 version to avoid linker warning - `windows`: add ARM64 support * **boards** - Add Waveshare RP2040 Zero - Add Arduino Leonardo support - Add Adafruit KB2040 - Add Adafruit Feather M0 Express - Add Makerfabs ESP32C3SPI35 TFT Touchscreen board - Add Espressif ESP32-C3-DevKit-RUST-1 board - `lgt92`: fix OpenOCD configuration - `xiao-rp2040`: fix D9 and D10 constants - `xiao-rp2040`: add pin definitions 0.26.0 --- * **general** - remove support for LLVM 13 - remove calls to deprecated ioutil package - move from `os.IsFoo` to `errors.Is(err, ErrFoo)` - fix for builds using an Android host - make interp timeout configurable from command line - ignore ports with VID/PID if there is no candidates - drop support for Go 1.16 and Go 1.17 - update serial package to v1.3.5 for latest bugfixes - remove GOARM from `tinygo info` - add flag for setting the goroutine stack size - add serial port monitoring functionality * **compiler** - `cgo`: implement support for static functions - `cgo`: fix panic when FuncType.Results is nil - `compiler`: add aliases for `edwards25519/field.feMul` and `field.feSquare` - `compiler`: fix incorrect DWARF type in some generic parameters - `compiler`: use LLVM math builtins everywhere - `compiler`: replace some math operation bodies with LLVM intrinsics - `compiler`: replace math aliases with intrinsics - `compiler`: fix `unsafe.Sizeof` for chan and map values - `compileopts`: use tags parser from buildutil - `compileopts`: use backticks for regexp to avoid extra escapes - `compileopts`: fail fast on duplicate values in target field slices - `compileopts`: fix windows/arm target triple - `compileopts`: improve error handling when loading target/*.json - `compileopts`: add support for stlink-dap programmer - `compileopts`: do not complain about `-no-debug` on MacOS - `goenv`: support `GOOS=android` - `interp`: fix reading from external global - `loader`: fix link error for `crypto/internal/boring/sig.StandardCrypto` * **standard library** - rename assembly files to .S extension - `machine`: add PWM peripheral comments to pins - `machine`: improve UARTParity slightly - `machine`: do not export DFU_MAGIC_* constants on nrf52840 - `machine`: rename `PinInputPullUp`/`PinInputPullDown` - `machine`: add `KHz`, `MHz`, `GHz` constants, deprecate `TWI_FREQ_*` constants - `machine`: remove level triggered pin interrupts - `machine`: do not expose `RESET_MAGIC_VALUE` - `machine`: use `NoPin` constant where appropriate (instead of `0` for example) - `net`: sync net.go with Go 1.18 stdlib - `os`: add `SyscallError.Timeout` - `os`: add `ErrProcessDone` error - `reflect`: implement `CanInterface` and fix string `Index` - `runtime`: make `MemStats` available to leaking collector - `runtime`: add `MemStats.TotalAlloc` - `runtime`: add `MemStats.Mallocs` and `Frees` - `runtime`: add support for `time.NewTimer` and `time.NewTicker` - `runtime`: implement `resetTimer` - `runtime`: ensure some headroom for the GC to run - `runtime`: make gc and scheduler asserts settable with build tags - `runtime/pprof`: add `WriteHeapProfile` - `runtime/pprof`: `runtime/trace`: stub some additional functions - `sync`: implement `Map.LoadAndDelete` - `syscall`: group WASI consts by purpose - `syscall`: add WASI `{D,R}SYNC`, `NONBLOCK` FD flags - `syscall`: add ENOTCONN on darwin - `testing`: add support for -benchmem * **targets** - remove USB vid/pid pair of bootloader - `esp32c3`: remove unused `UARTStopBits` constants - `nrf`: implement `GetRNG` function - `nrf`: `rp2040`: add `machine.ReadTemperature` - `nrf52`: cleanup s140v6 and s140v7 uf2 targets - `rp2040`: implement semi-random RNG based on ROSC based on pico-sdk - `wasm`: add summary of wasm examples and fix callback bug - `wasm`: do not allow undefined symbols (`--allow-undefined`) - `wasm`: make sure buffers returned by `malloc` are kept until `free` is called - `windows`: save and restore xmm registers when switching goroutines * **boards** - add Pimoroni's Tufty2040 - add XIAO ESP32C3 - add Adafruit QT2040 - add Adafruit QT Py RP2040 - `esp32c3-12f`: `matrixportal-m4`: `p1am-100`: remove duplicate build tags - `hifive1-qemu`: remove this emulated board - `wioterminal`: add UART3 for RTL8720DN - `xiao-ble`: fix usbpid 0.25.0 --- * **command line** - change to ignore PortReset failures * **compiler** - `compiler`: darwin/arm64 is aarch64, not arm - `compiler`: don't clobber X18 and FP registers on darwin/arm64 - `compiler`: fix issue with methods on generic structs - `compiler`: do not try to build generic functions - `compiler`: fix type names for generic named structs - `compiler`: fix multiple defined function issue for generic functions - `compiler`: implement `unsafe.Alignof` and `unsafe.Sizeof` for generic code * **standard library** - `machine`: add DTR and RTS to Serialer interface - `machine`: reorder pin definitions to improve pin list on tinygo.org - `machine/usb`: add support for MIDI - `machine/usb`: adjust buffer alignment (samd21, samd51, nrf52840) - `machine/usb/midi`: add `NoteOn`, `NoteOff`, and `SendCC` methods - `machine/usb/midi`: add definition of MIDI note number - `runtime`: add benchmarks for memhash - `runtime`: add support for printing slices via print/println * **targets** - `avr`: fix some apparent mistake in atmega1280/atmega2560 pin constants - `esp32`: provide hardware pin constants - `esp32`: fix WDT reset on the MCH2022 badge - `esp32`: optimize SPI transmit - `esp32c3`: provide hardware pin constants - `esp8266`: provide hardware pin constants like `GPIO2` - `nrf51`: define and use `P0_xx` constants - `nrf52840`, `samd21`, `samd51`: unify bootloader entry process - `nrf52840`, `samd21`, `samd51`: change usbSetup and sendZlp to public - `nrf52840`, `samd21`, `samd51`: refactor handleStandardSetup and initEndpoint - `nrf52840`, `samd21`, `samd51`: improve usb-device initialization - `nrf52840`, `samd21`, `samd51`: move usbcdc to machine/usb/cdc - `rp2040`: add usb serial vendor/product ID - `rp2040`: add support for usb - `rp2040`: change default for serial to usb - `rp2040`: add support for `machine.EnterBootloader` - `rp2040`: turn off pullup/down when input type is not specified - `rp2040`: make picoprobe default openocd interface - `samd51`: add support for `DAC1` - `samd51`: improve TRNG - `wasm`: stub `runtime.buffered`, `runtime.getchar` - `wasi`: make leveldb runtime hash the default * **boards** - add Challenger RP2040 LoRa - add MCH2022 badge - add XIAO RP2040 - `clue`: remove pins `D21`..`D28` - `feather-rp2040`, `macropad-rp2040`: fix qspi-flash settings - `xiao-ble`: add support for flash-1200-bps-reset - `gopherbot`, `gopherbot2`: add these aliases to simplify for newer users 0.24.0 --- * **command line** - remove support for go 1.15 - remove support for LLVM 11 and LLVM 12 - add initial Go 1.19 beta support - `test`: fix package/... syntax * **compiler** - add support for the embed package - `builder`: improve error message for "command not found" - `builder`: add support for ThinLTO on MacOS and Windows - `builder`: free LLVM objects after use, to reduce memory leaking - `builder`: improve `-no-debug` error messages - `cgo`: be more strict: CGo now requires every Go file to import the headers it needs - `compiler`: alignof(func) is 1 pointer, not 2 - `compiler`: add support for type parameters (aka generics) - `compiler`: implement `recover()` built-in function - `compiler`: support atomic, volatile, and LLVM memcpy-like functions in defer - `compiler`: drop support for macos syscalls via inline assembly - `interp`: do not try to interpret past task.Pause() - `interp`: fix some buggy localValue handling - `interp`: do not unroll loops - `transform`: fix MakeGCStackSlots that caused a possible GC bug on WebAssembly * **standard library** - `os`: enable os.Stdin for baremetal target - `reflect`: add `Value.UnsafePointer` method - `runtime`: scan GC globals conservatively on Windows, MacOS, Linux and Nintendo Switch - `runtime`: add per-map hash seeds - `runtime`: handle nil map write panics - `runtime`: add stronger hash functions - `syscall`: implement `Getpagesize` * **targets** - `atmega2560`: support UART1-3 + example for uart - `avr`: use compiler-rt for improved float64 support - `avr`: simplify timer-based time - `avr`: fix race condition in stack write - `darwin`: add support for `GOARCH=arm64` (aka Apple Silicon) - `darwin`: support `-size=short` and `-size=full` flag - `rp2040`: replace sleep 'busy loop' with timer alarm - `rp2040`: align api for `PortMaskSet`, `PortMaskClear` - `rp2040`: fix GPIO interrupts - `samd21`, `samd51`, `nrf52840`: add support for USBHID (keyboard / mouse) - `wasm`: update wasi-libc version - `wasm`: use newer WebAssembly features * **boards** - add Badger 2040 - `matrixportal-m4`: attach USB DP to the correct pin - `teensy40`: add I2C support - `wioterminal`: fix I2C definition 0.23.0 --- * **command line** - add `-work` flag - add Go 1.18 support - add LLVM 14 support - `run`: add support for command-line parameters - `build`: calculate default output path if `-o` is not specified - `build`: add JSON output - `test`: support multiple test binaries with `-c` - `test`: support flags like `-v` on all targets (including emulated firmware) * **compiler** - add support for ThinLTO - use compiler-rt from LLVM - `builder`: prefer GNU build ID over Go build ID for caching - `builder`: add support for cross compiling to Darwin - `builder`: support machine outlining pass in stacksize calculation - `builder`: disable asynchronous unwind tables - `compileopts`: fix emulator configuration on non-amd64 Linux architectures - `compiler`: move allocations > 256 bytes to the heap - `compiler`: fix incorrect `unsafe.Alignof` on some 32-bit architectures - `compiler`: accept alias for slice `cap` builtin - `compiler`: allow slices of empty structs - `compiler`: fix difference in aliases in interface methods - `compiler`: make `RawSyscall` an alias for `Syscall` - `compiler`: remove support for memory references in `AsmFull` - `loader`: only add Clang header path for CGo - `transform`: fix poison value in heap-to-stack transform * **standard library** - `internal/fuzz`: add this package as a shim - `os`: implement readdir for darwin and linux - `os`: add `DirFS`, which is used by many programs to access readdir. - `os`: isWine: be compatible with older versions of wine, too - `os`: implement `RemoveAll` - `os`: Use a `uintptr` for `NewFile` - `os`: add stubs for `exec.ExitError` and `ProcessState.ExitCode` - `os`: export correct values for `DevNull` for each OS - `os`: improve support for `Signal` by fixing various bugs - `os`: implement `File.Fd` method - `os`: implement `UserHomeDir` - `os`: add `exec.ProcessState` stub - `os`: implement `Pipe` for darwin - `os`: define stub `ErrDeadlineExceeded` - `reflect`: add stubs for more missing methods - `reflect`: rename `reflect.Ptr` to `reflect.Pointer` - `reflect`: add `Value.FieldByIndexErr` stub - `runtime`: fix various small GC bugs - `runtime`: use memzero for leaking collector instead of manually zeroing objects - `runtime`: implement `memhash` - `runtime`: implement `fastrand` - `runtime`: add stub for `debug.ReadBuildInfo` - `runtime`: add stub for `NumCPU` - `runtime`: don't inline `runtime.alloc` with `-gc=leaking` - `runtime`: add `Version` - `runtime`: add stubs for `NumCgoCall` and `NumGoroutine` - `runtime`: stub {Lock,Unlock}OSThread on Windows - `runtime`: be able to deal with a very small heap - `syscall`: make `Environ` return a copy of the environment - `syscall`: implement getpagesize and munmap - `syscall`: `wasi`: define `MAP_SHARED` and `PROT_READ` - `syscall`: stub mmap(), munmap(), MAP_SHARED, PROT_READ, SIGBUS, etc. on nonhosted targets - `syscall`: darwin: more complete list of signals - `syscall`: `wasi`: more complete list of signals - `syscall`: stub `WaitStatus` - `syscall/js`: allow copyBytesTo(Go|JS) to use `Uint8ClampedArray` - `testing`: implement `TempDir` - `testing`: nudge type TB closer to upstream; should be a no-op change. - `testing`: on baremetal platforms, use simpler test matcher * **targets** - `atsamd`: fix usbcdc initialization when `-serial=uart` - `atsamd51`: allow higher frequency when using SPI - `esp`: support CGo - `esp32c3`: add support for input pin - `esp32c3`: add support for GPIO interrupts - `esp32c3`: add support to receive UART data - `rp2040`: fix PWM bug at high frequency - `rp2040`: fix some minor I2C bugs - `rp2040`: fix incorrect inline assembly - `rp2040`: fix spurious i2c STOP during write+read transaction - `rp2040`: improve ADC support - `wasi`: remove `--export-dynamic` linker flag - `wasm`: remove heap allocator from wasi-libc * **boards** - `circuitplay-bluefruit`: move pin mappings so board can be compiled for WASM use in Playground - `esp32-c3-12f`: add the ESP32-C3-12f Kit - `m5stamp-c3`: add pin setting of UART - `macropad-rp2040`: add the Adafruit MacroPad RP2040 board - `nano-33-ble`: typo in LPS22HB peripheral definition and documentation (#2579) - `teensy41`: add the Teensy 4.1 board - `teensy40`: add ADC support - `teensy40`: add SPI support - `thingplus-rp2040`: add the SparkFun Thing Plus RP2040 board - `wioterminal`: add DefaultUART - `wioterminal`: verify written data when flashing through OpenOCD - `xiao-ble`: add XIAO BLE nRF52840 support 0.22.0 --- * **command line** - add asyncify to scheduler flag help - support -run for tests - remove FreeBSD target support - add LLVM 12 and LLVM 13 support, use LLVM 13 by default - add support for ARM64 MacOS - improve help - check /run/media as well as /media on Linux for non-debian-based distros - `test`: set cmd.Dir even when running emulators - `info`: add JSON output using the `-json` flag * **compiler** - `builder`: fix off-by-one in size calculation - `builder`: handle concurrent library header rename - `builder`: use flock to avoid double-compiles - `builder`: use build ID as cache key - `builder`: add -fno-stack-protector to musl build - `builder`: update clang header search path to look in /usr/lib - `builder`: explicitly disable unwind tables for ARM - `cgo`: add support for `C.CString` and related functions - `compiler`: fix ranging over maps with particular map types - `compiler`: add correct debug location to init instructions - `compiler`: fix emission of large object layouts - `compiler`: work around AVR atomics bugs - `compiler`: predeclare runtime.trackPointer - `interp`: work around AVR function pointers in globals - `interp`: run goroutine starts and checks at runtime - `interp`: always run atomic and volatile loads/stores at runtime - `interp`: bump timeout to 180 seconds - `interp`: handle type assertions on nil interfaces - `loader`: eliminate goroot cache inconsistency - `loader`: respect $GOROOT when running `go list` - `transform`: allocate the correct amount of bytes in an alloca - `transform`: remove switched func lowering * **standard library** - `crypto/rand`: show error if platform has no rng - `device/*`: add `*_Msk` field for each bit field and avoid duplicates - `device/*`: provide Set/Get for each register field described in the SVD files - `internal/task`: swap stack chain when switching goroutines - `internal/task`: remove `-scheduler=coroutines` - `machine`: add `Device` string constant - `net`: add bare Interface implementation - `net`: add net.Buffers - `os`: stub out support for some features - `os`: obey TMPDIR on unix, TMP on Windows, etc - `os`: implement `ReadAt`, `Mkdir`, `Remove`, `Stat`, `Lstat`, `CreateTemp`, `MkdirAll`, `Chdir`, `Chmod`, `Clearenv`, `Unsetenv`, `Setenv`, `MkdirTemp`, `Rename`, `Seek`, `ExpandEnv`, `Symlink`, `Readlink` - `os`: implement `File.Stat` - `os`: fix `IsNotExist` on nonexistent path - `os`: fix opening files on WASI in read-only mode - `os`: work around lack of `syscall.seek` on 386 and arm - `reflect`: make sure indirect pointers are handled correctly - `runtime`: allow comparing interfaces - `runtime`: use LLVM intrinsic to read the stack pointer - `runtime`: strengthen hashmap hash function for structs and arrays - `runtime`: fix float/complex hashing - `runtime`: fix nil map dereference - `runtime`: add realloc implementation to GCs - `runtime`: handle negative sleep times - `runtime`: correct GC scan bounds - `runtime`: remove extalloc GC - `rumtime`: implement `__sync` libcalls as critical sections for most microcontrollers - `runtime`: add stubs for `Func.FileLine` and `Frame.PC` - `sync`: fix concurrent read-lock on write-locked RWMutex - `sync`: add a package doc - `sync`: add tests - `syscall`: add support for `Mmap` and `Mprotect` - `syscall`: fix array size for mmap slice creation - `syscall`: enable `Getwd` in wasi - `testing`: add a stub for `CoverMode` - `testing`: support -bench option to run benchmarks matching the given pattern. - `testing`: support b.SetBytes(); implement sub-benchmarks. - `testing`: replace spaces with underscores in test/benchmark names, as upstream does - `testing`: implement testing.Cleanup - `testing`: allow filtering subbenchmarks with the `-bench` flag - `testing`: implement `-benchtime` flag - `testing`: print duration - `testing`: allow filtering of subtests using `-run` * **targets** - `all`: change LLVM features to match vanilla Clang - `avr`: use interrupt-based timer which is much more accurate - `nrf`: fix races in I2C - `samd51`: implement TRNG for randomness - `stm32`: pull-up on I2C lines - `stm32`: fix timeout for i2c comms - `stm32f4`, `stm32f103`: initial implementation for ADC - `stm32f4`, `stm32f7`, `stm32l0x2`, `stm32l4`, `stm32l5`, `stm32wl`: TRNG implementation in crypto/rand - `stm32wl`: add I2C support - `windows`: add support for the `-size=` flag - `wasm`: add support for `tinygo test` - `wasi`, `wasm`: raise default stack size to 16 KiB * **boards** - add M5Stack - add lorae5 (stm32wle) support - add Generic Node Sensor Edition - add STM32F469 Discovery - add M5Stamp C3 - add Blues Wireless Swan - `bluepill`: add definitions for ADC pins - `stm32f4disco`: add definitions for ADC pins - `stm32l552ze`: use supported stlink interface - `microbit-v2`: add some pin definitions 0.21.0 --- * **command line** - drop support for LLVM 10 - `build`: drop support for LLVM targets in the -target flag - `build`: fix paths in error messages on Windows - `build`: add -p flag to set parallelism - `lldb`: implement `tinygo lldb` subcommand - `test`: use emulator exit code instead of parsing test output - `test`: pass testing arguments to wasmtime * **compiler** - use -opt flag for optimization level in CFlags (-Os, etc) - `builder`: improve accuracy of the -size=full flag - `builder`: hardcode some more frame sizes for __aeabi_* functions - `builder`: add support for -size= flag for WebAssembly - `cgo`: fix line/column reporting in syntax error messages - `cgo`: support function definitions in CGo headers - `cgo`: implement rudimentary C array decaying - `cgo`: add support for stdio in picolibc and wasi-libc - `cgo`: run CGo parser per file, not per CGo fragment - `compiler`: fix unintentionally exported math functions - `compiler`: properly implement div and rem operations - `compiler`: add support for recursive function types - `compiler`: add support for the `go` keyword on interface methods - `compiler`: add minsize attribute for -Oz - `compiler`: add "target-cpu" and "target-features" attributes - `compiler`: fix indices into strings and arrays - `compiler`: fix string compare functions - `interp`: simplify some code to avoid some errors - `interp`: support recursive globals (like linked lists) in globals - `interp`: support constant globals - `interp`: fix reverting of extractvalue/insertvalue with multiple indices - `transform`: work around renamed return type after merging LLVM modules * **standard library** - `internal/bytealg`: fix indexing error in Compare() - `machine`: support Pin.Get() function when the pin is configured as output - `net`, `syscall`: Reduce code duplication by switching to internal/itoa. - `os`: don't try to read executable path on baremetal - `os`: implement Getwd - `os`: add File.WriteString and File.WriteAt - `reflect`: fix type.Size() to account for struct padding - `reflect`: don't construct an interface-in-interface value - `reflect`: implement Value.Elem() for interface values - `reflect`: fix Value.Index() in a specific case - `reflect`: add support for DeepEqual - `runtime`: add another set of invalid unicode runes to encodeUTF8() - `runtime`: only initialize os.runtime_args when needed - `runtime`: only use CRLF on baremetal systems for println - `runtime/debug`: stub `debug.SetMaxStack` - `runtime/debug`: stub `debug.Stack` - `testing`: add a stub for t.Parallel() - `testing`: add support for -test.short flag - `testing`: stub B.ReportAllocs() - `testing`: add `testing.Verbose` - `testing`: stub `testing.AllocsPerRun` * **targets** - fix gen-device-svd to handle 64-bit values - add CPU and Features property to all targets - match LLVM triple to the one Clang uses - `atsam`: simplify definition of SERCOM UART, I2C and SPI peripherals - `atsam`: move I2S0 to machine file - `esp32`: fix SPI configuration - `esp32c3`: add support for GDB debugging - `esp32c3`: add support for CPU interrupts - `esp32c3`: use tasks scheduler by default - `fe310`: increase CPU frequency from 16MHz to 320MHz - `fe310`: add support for bit banging drivers - `linux`: build static binaries using musl - `linux`: reduce binary size by calling `write` instead of `putchar` - `linux`: add support for GOARM - `riscv`: implement 32-bit atomic operations - `riscv`: align the heap to 16 bytes - `riscv`: switch to tasks-based scheduler - `rp2040`: add CPUFrequency() - `rp2040`: improve I2C baud rate configuration - `rp2040`: add pin interrupt API - `rp2040`: refactor PWM code and fix Period calculation - `stm32f103`: fix SPI - `stm32f103`: make SPI frequency selection more flexible - `qemu`: signal correct exit code to QEMU - `wasi`: run C/C++ constructors at startup - `wasm`: ensure heapptr is aligned - `wasm`: update wasi-libc dependency - `wasm`: wasi: use asyncify - `wasm`: support `-scheduler=none` - `windows`: add support for Windows (amd64 only for now) * **boards** - `feather-stm32f405`, `feather-rp2040`: add I2C pin names - `m5stack-core2`: add M5Stack Core2 - `nano-33-ble`: SoftDevice s140v7 support - `nano-33-ble`: add constants for more on-board pins 0.20.0 --- * **command line** - add support for Go 1.17 - improve Go version detection - add support for the Black Magic Probe (BMP) - add a flag for creating cpu profiles * **compiler** - `builder:` list libraries at the end of the linker command - `builder:` strip debug information at link time instead of at compile time - `builder:` add missing error check for `ioutil.TempFile()` - `builder:` simplify running of jobs - `compiler:` move LLVM math builtin support into the compiler - `compiler:` move math aliases from the runtime to the compiler - `compiler:` add aliases for many hashing packages - `compiler:` add `*ssa.MakeSlice` bounds tests - `compiler:` fix max possible slice - `compiler:` add support for new language features of Go 1.17 - `compiler:` fix equally named structs in different scopes - `compiler:` avoid zero-sized alloca in channel operations - `interp:` don't ignore array indices for untyped objects - `interp:` keep reverted package initializers in order - `interp:` fix bug in compiler-time/run-time package initializers - `loader:` fix panic in CGo files with syntax errors - `transform:` improve GC stack slot pass to work around a bug * **standard library** - `crypto/rand`: switch to `arc4random_buf` - `math:` fix `math.Max` and `math.Min` - `math/big`: fix undefined symbols error - `net:` add MAC address implementation - `os:` implement `os.Executable` - `os:` add `SEEK_SET`, `SEEK_CUR`, and `SEEK_END` - `reflect:` add StructField.IsExported method - `runtime:` reset heapptr to heapStart after preinit() - `runtime:` add `subsections_via_symbols` to assembly files on darwin - `testing:` add subset implementation of Benchmark - `testing:` test testing package using `tinygo test` - `testing:` add support for the `-test.v` flag * **targets** - `386:` bump minimum requirement to the Pentium 4 - `arm:` switch to Thumb instruction set on ARM - `atsamd:` fix copy-paste error for atsamd21/51 calibTrim block - `baremetal`,`wasm`: support command line params and environment variables - `cortexm:` fix stack overflow because of unaligned stacks - `esp32c3:` add support for the ESP32-C3 from Espressif - `nrf52840:` fix ram size - `nxpmk66f18:` fix a suspicious bitwise operation - `rp2040:` add SPI support - `rp2040:` add I2C support - `rp2040:` add PWM implementation - `rp2040:` add openocd configuration - `stm32:` add support for PortMask* functions for WS2812 support - `unix:` fix time base for time.Now() - `unix:` check for mmap error and act accordingly - `wasm:` override dlmalloc heap implementation from wasi-libc - `wasm:` align heap to 16 bytes - `wasm:` add support for the crypto/rand package * **boards** - add `DefaultUART` to adafruit boards - `arduino-mkrwifi1010:` add board definition for Arduino MKR WiFi 1010 - `arduino-mkrwifi1010:` fix pin definition of `NINA_RESETN` - `feather-nrf52:` fix pin definition of uart - `feather-rp2040:` add pin name definition - `gameboy-advance:` fix ROM header - `mdbt50qrx-uf2:` add Raytac MDBT50Q-RX Dongle with TinyUF2 - `nano-rp2040:` define `NINA_SPI` and fix wifinina pins - `teensy40:` enable hardware UART reconfiguration, fix receive watermark interrupt 0.19.0 --- * **command line** - don't consider compile-only tests as failing - add -test flag for `tinygo list` - escape commands while printing them with the -x flag - make flash-command portable and safer to use - use `extended-remote` instead of `remote` in GDB - detect specific serial port IDs based on USB vid/pid - add a flag to the command line to select the serial implementation * **compiler** - `cgo`: improve constant parser - `compiler`: support chained interrupt handlers - `compiler`: add support for running a builtin in a goroutine - `compiler`: do not emit nil checks for loading closure variables - `compiler`: skip context parameter when starting regular goroutine - `compiler`: refactor method names - `compiler`: add function and global section pragmas - `compiler`: implement `syscall.rawSyscallNoError` in inline assembly - `interp`: ignore inline assembly in markExternal - `interp`: fix a bug in pointer cast workaround - `loader`: fix testing a main package * **standard library** - `crypto/rand`: replace this package with a TinyGo version - `machine`: make USBCDC global a pointer - `machine`: make UART objects pointer receivers - `machine`: define Serial as the default output - `net`: add initial support for net.IP - `net`: add more net compatibility - `os`: add stub for os.ReadDir - `os`: add FileMode constants from Go 1.16 - `os`: add stubs required for net/http - `os`: implement process related functions - `reflect`: implement AppendSlice - `reflect`: add stubs required for net/http - `runtime`: make task.Data a 64-bit integer to avoid overflow - `runtime`: expose memory stats - `sync`: implement NewCond - `syscall`: fix int type in libc version * **targets** - `cortexm`: do not disable interrupts on abort - `cortexm`: bump default stack size to 2048 bytes - `nrf`: avoid heap allocation in waitForEvent - `nrf`: don't trigger a heap allocation in SPI.Transfer - `nrf52840`: add support for flashing with the BOSSA tool - `rp2040`: add support for GPIO input - `rp2040`: add basic support for ADC - `rp2040`: gpio and adc pin definitions - `rp2040`: implement UART - `rp2040`: patch elf to checksum 2nd stage boot - `stm32`: add PWM for most chips - `stm32`: add support for pin interrupts - `stm32f103`: add support for PinInputPullup / PinInputPulldown - `wasi`: remove wasm build tag * **boards** - `feather-rp2040`: add support for this board - `feather-nrf52840-sense`: add board definition for this board - `pca10059`: support flashing from Windows - `nano-rp2040`: add this board - `nano-33-ble`: add support for this board - `pico`: add the Raspberry Pi Pico board with the new RP2040 chip - `qtpy`: add pin for neopixels - all: add definition for ws2812 for supported boards 0.18.0 --- * **command line** - drop support for Go 1.11 and 1.12 - throw an error when no target is specified on Windows - improve error messages in `getDefaultPort()`, support for multiple ports - remove `-cflags` and `-ldflags` flags - implement `-ldflags="-X ..."` - add `-print-allocs` flag that lets you print all heap allocations - openocd commands in tinygo command line - add `-llvm-features` parameter - match `go test` output - discover USB ports only, this will ignore f.ex. bluetooth - use physicmal path instead of cached GOROOT in function getGoroot - add goroot for snap installs * **compiler** - `builder`: add support for `-opt=0` - `builder`, `compiler`: compile and cache packages in parallel - `builder`: run interp per package - `builder`: cache C and assembly file outputs - `builder`: add support for `-x` flag to print commands - `builder`: add optsize attribute while building the package - `builder`: run function passes per package - `builder`: hard code Clang compiler - `compiler`: do not use `llvm.GlobalContext()` - `compiler`: remove SimpleDCE pass - `compiler`: do not emit nil checks for `*ssa.Alloc` instructions - `compiler`: merge `runtime.typecodeID` and runtime.typeInInterface - `compiler`: do not check for impossible type asserts - `compiler`: fix use of global context: `llvm.Int32Type()` - `compiler`: add interface IR test - `compiler`: fix lack of method name in interface matching - `compiler`: fix "fragment covers entire variable" bug - `compiler`: optimize string literals and globals - `compiler`: decouple func lowering from interface type codes - `compiler`: add function attributes to some runtime calls - `compiler`: improve position information in error messages - `cgo`: add support for CFLAGS in .c files - `interp`: support GEP on fixed (MMIO) addresses - `interp`: handle `(reflect.Type).Elem()` - `interp`: add support for runtime.interfaceMethod - `interp`: make toLLVMValue return an error instead of panicking - `interp`: add support for switch statement - `interp`: fix phi instruction - `interp`: remove map support - `interp`: support extractvalue/insertvalue with multiple operands - `transform`: optimize string comparisons against "" - `transform`: optimize `reflect.Type` `Implements()` method - `transform`: fix bug in interface lowering when signatures are renamed - `transform`: don't rely on struct name of `runtime.typecodeID` - `transform`: use IPSCCP pass instead of the constant propagation pass - `transform`: fix func lowering assertion failure - `transform`: do not lower zero-sized alloc to alloca - `transform`: split interface and reflect lowering * **standard library** - `runtime`: add dummy debug package - `machine`: fix data shift/mask in newUSBSetup - `machine`: make `machine.I2C0` and similar objects pointers - `machine`: unify usbcdc code - `machine`: refactor PWM support - `machine`: avoid heap allocations in USB code - `reflect`: let `reflect.Type` be of interface type - `reflect`: implement a number of stub functions - `reflect`: check for access in the `Interface` method call - `reflect`: fix `AssignableTo` and `Implements` methods - `reflect`: implement `Value.CanAddr` - `reflect`: implement `Sizeof` and `Alignof` for func values - `reflect`: implement `New` function - `runtime`: implement command line arguments in hosted environments - `runtime`: implement environment variables for Linux - `runtime`: improve timers on nrf, and samd chips * **targets** - all: use -Qunused-arguments only for assembly files - `atmega1280`: add PWM support - `attiny`: remove dummy UART - `atsamd21`: improve SPI - `atsamd51`: fix PWM support in atsamd51p20 - `atsamd5x`: improve SPI - `atsamd51`, `atsame5x`: unify samd51 and same5x - `atsamd51`, `atsamd21`: fix `ADC.Get()` value at 8bit and 10bit - `atsame5x`: add support for CAN - `avr`: remove I2C stubs from attiny support - `cortexm`: check for `arm-none-eabi-gdb` and `gdb-multiarch` commands - `cortexm`: add `__isr_vector` symbol - `cortexm`: disable FPU on Cortex-M4 - `cortexm`: clean up Cortex-M target files - `fe310`: fix SPI read - `gameboy-advance`: Fix RGBA color interpretation - `nrf52833`: add PWM support - `stm32l0`: use unified UART logic - `stm32`: move f103 (bluepill) to common i2c code - `stm32`: separate altfunc selection for UART Tx/Rx - `stm32`: i2c implementation for F7, L5 and L4 MCUs - `stm32`: make SPI CLK fast to fix data issue - `stm32`: support SPI on L4 series - `unix`: avoid possible heap allocation with `-opt=0` - `unix`: use conservative GC by default - `unix`: use the tasks scheduler instead of coroutines - `wasi`: upgrade WASI version to wasi_snapshot_preview1 - `wasi`: darwin: support basic file io based on libc - `wasm`: only export explicitly exported functions - `wasm`: use WASI ABI for exit function - `wasm`: scan globals conservatively * **boards** - `arduino-mega1280`: add support for the Arduino Mega 1280 - `arduino-nano-new`: Add Arduino Nano w/ New Bootloader target - `atsame54-xpro`: add initial support this board - `feather-m4-can`: add initial support for this board - `grandcentral-m4`: add board support for Adafruit Grand Central M4 (SAMD51) - `lgt92`: update to new UART structure - `microbit`: remove LED constant - `microbit-v2`: add support for S113 SoftDevice - `nucleol432`: add support for this board - `nucleo-l031k6`: add this board - `pca10059`: initial support for this board - `qtpy`: fix msd-volume-name - `qtpy`: fix i2c setting - `teensy40`: move txBuffer allocation to UART declaration - `teensy40`: add UART0 as alias for UART1 0.17.0 --- * **command line** - switch to LLVM 11 for static builds - support gdb debugging with AVR - add support for additional openocd commands - add `-x` flag to print commands - use LLVM 11 by default when linking LLVM dynamically - update go-llvm to use LLVM 11 on macOS - bump go.bug.st/serial to version 1.1.2 - do not build LLVM with libxml to work around a bugo on macOS - add support for Go 1.16 - support gdb daemonization on Windows - remove support for LLVM 9, to fix CI - kill OpenOCD if it does not exit with a regular quit signal - support `-ocd-output` on Windows * **compiler** - `builder`: parallelize most of the build - `builder`: remove unused cacheKey parameter - `builder`: add -mcpu flag while building libraries - `builder`: wait for running jobs to finish - `cgo`: add support for variadic functions - `compiler`: fix undefined behavior in wordpack - `compiler`: fix incorrect "exported function" panic - `compiler`: fix non-int integer constants (fixing a crash) - `compiler`: refactor and add tests - `compiler`: emit a nil check when slicing an array pointer - `compiler`: saturate float-to-int conversions - `compiler`: test float to int conversions and fix upper-bound calculation - `compiler`: support all kinds of deferred builtins - `compiler`: remove ir package - `compiler`: remove unnecessary main.main call workaround - `compiler`: move the setting of attributes to getFunction - `compiler`: create runtime types lazily when needed - `compiler`: move settings to a separate Config struct - `compiler`: work around an ARM backend bug in LLVM - `interp`: rewrite entire package - `interp`: fix alignment of untyped globals - `loader`: use name "main" for the main package - `loader`: support imports from vendor directories - `stacksize`: add support for DW_CFA_offset_extended - `transform`: show better error message in coroutines lowering * **standard library** - `machine`: accept configuration struct for ADC parameters - `machine`: make I2C.Configure signature consistent - `reflect`: implement PtrTo - `runtime`: refactor to simplify stack switching - `runtime`: put metadata at the top end of the heap * **targets** - `atsam`: add a length check to findPinPadMapping - `atsam`: improve USBCDC - `atsam`: avoid infinite loop when USBCDC is disconnected - `avr`: add SPI support for Atmega based chips - `avr`: use Clang for compiling C and assembly files - `esp32`: implement task based scheduler - `esp32`: enable the FPU - `esp8266`: implement task based scheduler - `esp`: add compiler-rt library - `esp`: add picolibc - `nrf`: refactor code a bit to reduce duplication - `nrf`: use SPIM peripheral instead of the legacy SPI peripheral - `nrf`: update nrfx submodule to latest commit - `nrf52840`: ensure that USB CDC interface is only initialized once - `nrf52840`: improve USBCDC - `stm32`: use stm32-rs SVDs which are of much higher quality - `stm32`: harmonization of UART logic - `stm32`: replace I2C addressable interface with simpler type - `stm32`: fix i2c and add stm32f407 i2c - `stm32`: revert change that adds support for channels in interrupts - `wasm`: implement a growable heap - `wasm`: fix typo in wasm_exec.js, syscall/js.valueLoadString() - `wasm`: Namespaced Wasm Imports so they don't conflict across modules, or reserved LLVM IR - `wasi`: support env variables based on libc - `wasi`: specify wasi-libc in a different way, to improve error message * **boards** - `matrixportal-m4`: add support for board Adafruit Matrix Portal M4 - `mkr1000`: add this board - `nucleo-f722ze`: add this board - `clue`: correct volume name and add alias for release version of Adafruit Clue board - `p1am-100`: add support for the P1AM-100 (similar to Arduino MKR) - `microbit-v2`: add initial support based on work done by @alankrantas thank you! - `lgt92`: support for STM32L0 MCUs and Dragino LGT92 device - `nicenano`: nice!nano board support - `circuitplay-bluefruit`: correct internal I2C pin mapping - `clue`: correct for lack of low frequency crystal - `digispark`: split off attiny85 target - `nucleo-l552ze`: implementation with CLOCK, LED, and UART - `nrf52840-mdk-usb-dongle`: add this board 0.16.0 --- * **command-line** - add initial support for LLVM 11 - make lib64 clang include path check more robust - `build`: improve support for GOARCH=386 and add tests - `gdb`: add support for qemu-user targets - `test`: support non-host tests - `test`: add support for -c and -o flags - `test`: implement some benchmark stubs * **compiler** - `builder`: improve detection of clang on Fedora - `compiler`: fix floating point comparison bugs - `compiler`: implement negate for complex numbers - `loader`: fix linkname in test binaries - `transform`: add missing return pointer restore for regular coroutine tail calls * **standard library** - `machine`: switch default frequency to 4MHz - `machine`: clarify caller's responsibility in `SetInterrupt` - `os`: add `LookupEnv()` stub - `reflect`: implement `Swapper` - `runtime`: fix UTF-8 decoding - `runtime`: gc: use raw stack access whenever possible - `runtime`: use dedicated printfloat32 - `runtime`: allow ranging over a nil map - `runtime`: avoid device/nxp dependency in HardFault handler - `testing`: implement dummy Helper method - `testing`: add Run method * **targets** - `arm64`: add support for SVCall intrinsic - `atsamd51`: avoid panic when configuring SPI with SDI=NoPin - `avr`: properly support the `.rodata` section - `esp8266`: implement `Pin.Get` function - `nintendoswitch`: fix crash when printing long lines (> 120) - `nintendoswitch`: add env parser and removed unused stuff - `nrf`: add I2C error checking - `nrf`: give more flexibility in picking SPI speeds - `nrf`: fix nrf52832 flash size - `stm32f103`: support wakeups from interrupts - `stm32f405`: add SPI support - `stm32f405`: add I2C support - `wasi`: add support for this target - `wasi`: use 'generic' ABI by default - `wasi`: remove --no-threads flag from wasm-ld - `wasm`: add instanceof support for WebAssembly - `wasm`: use fixed length buffer for putchar * **boards** - `d1mini`: add this ESP8266 based board - `esp32`: use board definitions instead of chip names - `qtpy`: add board definition for Adafruit QTPy - `teensy40`: add this board 0.15.0 --- * **command-line** - add cached GOROOT to info subcommand - embed git-hash in tinygo-dev executable - implement tinygo targets to list usable targets - use simpler file copy instead of file renaming to avoid issues on nrf52840 UF2 bootloaders - use ToSlash() to specify program path - support flashing esp32/esp8266 directly from tinygo - when flashing call PortReset only on other than openocd * **compiler** - `compileopts`: add support for custom binary formats - `compiler`: improve display of goroutine wrappers - `interp`: don't panic in the Store method - `interp`: replace some panics with error messages - `interp`: show error line in first line of the traceback - `loader`: be more robust when creating the cached GOROOT - `loader`: rewrite/refactor much of the code to use go list directly - `loader`: use ioutil.TempDir to create a temporary directory - `stacksize`: deal with DW_CFA_advance_loc1 * **standard library** - `runtime`: use waitForEvents when appropriate * **wasm** - `wasm`: Remove --no-threads from wasm-ld calls. - `wasm`: update wasi-libc dependency * **targets** - `arduino-mega2560`: fix flashing on Windows - `arm`: automatically determine stack sizes - `arm64`: make dynamic loader structs and constants private - `avr`: configure emulator in board files - `cortexm`: fix stack size calculation with interrupts - `flash`: add openocd settings to atsamd21 / atsamd51 - `flash`: add openocd settings to nrf5 - `microbit`: reelboard: flash using OpenOCD when needed - `nintendoswitch`: Add dynamic loader for runtime loading PIE sections - `nintendoswitch`: fix import cycle on dynamic_arm64.go - `nintendoswitch`: Fix invalid memory read / write in print calls - `nintendoswitch`: simplified assembly code - `nintendoswitch`: support outputting .nro files directly * **boards** - `arduino-zero`: Adding support for the Arduino Zero (#1365) - `atsamd2x`: fix BAUD value - `atsamd5x`: fix BAUD value - `bluepill`: Enable stm32's USART2 for the board and map it to UART1 tinygo's device - `device/atsamd51x`: add all remaining bitfield values for PCHCTRLm Mapping - `esp32`: add libgcc ROM functions to linker script - `esp32`: add SPI support - `esp32`: add support for basic GPIO - `esp32`: add support for the Espressif ESP32 chip - `esp32`: configure the I/O matrix for GPIO pins - `esp32`: export machine.PortMask* for bitbanging implementations - `esp8266`: add support for this chip - `machine/atsamd51x,runtime/atsamd51x`: fixes needed for full support for all PWM pins. Also adds some useful constants to clarify peripheral clock usage - `machine/itsybitsy-nrf52840`: add support for Adafruit Itsybitsy nrf52840 (#1243) - `machine/stm32f4`: refactor common code and add new build tag stm32f4 (#1332) - `nrf`: add SoftDevice support for the Circuit Playground Bluefruit - `nrf`: call sd_app_evt_wait when the SoftDevice is enabled - `nrf52840`: add build tags for SoftDevice support - `nrf52840`: use higher priority for USB-CDC code - `runtime/atsamd51x`: use PCHCTRL_GCLK_SERCOMX_SLOW for setting clocks on all SERCOM ports - `stm32f405`: add basic UART handler - `stm32f405`: add STM32F405 machine/runtime, and new board/target feather-stm32f405 * **build** - `all`: run test binaries in the correct directory - `build`: Fix arch release job - `ci`: run `tinygo test` for known-working packages - `ci`: set git-fetch-depth to 1 - `docker`: fix the problem with the wasm build (#1357) - `Makefile`: check whether submodules have been downloaded in some common cases * **docs** - add ESP32, ESP8266, and Adafruit Feather STM32F405 to list of supported boards 0.14.1 --- * **command-line** - support for Go 1.15 * **compiler** - loader: work around Windows symlink limitation 0.14.0 --- * **command-line** - fix `getDefaultPort()` on non-English Windows locales - compileopts: improve error reporting of unsupported flags - fix test subcommand - use auto-retry to locate MSD for UF2 and HEX flashing - fix touchSerialPortAt1200bps on Windows - support package names with backslashes on Windows * **compiler** - fix a few crashes due to named types - add support for atomic operations - move the channel blocked list onto the stack - fix -gc=none - fix named string to `[]byte` slice conversion - implement func value and builtin defers - add proper parameter names to runtime.initAll, to fix a panic - builder: fix picolibc include path - builder: use newer version of gohex - builder: try to determine stack size information at compile time - builder: remove -opt=0 - interp: fix sync/atomic.Value load/store methods - loader: add Go module support - transform: fix debug information in func lowering pass - transform: do not special-case zero or one implementations of a method call - transform: introduce check for method calls on nil interfaces - transform: gc: track 0-index GEPs to fix miscompilation * **cgo** - Add LDFlags support * **standard library** - extend stdlib to allow import of more packages - replace master/slave terminology with appropriate alternatives (MOSI->SDO etc) - `internal/bytealg`: reimplement bytealg in pure Go - `internal/task`: fix nil panic in (*internal/task.Stack).Pop - `os`: add Args and stub it with mock data - `os`: implement virtual filesystem support - `reflect`: add Cap and Len support for map and chan - `runtime`: fix return address in scheduler on RISC-V - `runtime`: avoid recursion in printuint64 function - `runtime`: replace ReadRegister with AsmFull inline assembly - `runtime`: fix compilation errors when using gc.extalloc - `runtime`: add cap and len support for chans - `runtime`: refactor time handling (improving accuracy) - `runtime`: make channels work in interrupts - `runtime/interrupt`: add cross-chip disable/restore interrupt support - `sync`: implement `sync.Cond` - `sync`: add WaitGroup * **targets** - `arm`: allow nesting in DisableInterrupts and EnableInterrupts - `arm`: make FPU configuration consistent - `arm`: do not mask fault handlers in critical sections - `atmega2560`: fix pin mapping for pins D2, D5 and the L port - `atsamd`: return an error when an incorrect PWM pin is used - `atsamd`: add support for pin change interrupts - `atsamd`: add DAC support - `atsamd21`: add more ADC pins - `atsamd51`: fix ROM / RAM size on atsamd51j20 - `atsamd51`: add more pins - `atsamd51`: add more ADC pins - `atsamd51`: add pin change interrupt settings - `atsamd51`: extend pinPadMapping - `arduino-nano33`: use (U)SB flag to ensure that device can be found when not on default port - `arduino-nano33`: remove (d)ebug flag to reduce console noise when flashing - `avr`: use standard pin numbering - `avr`: unify GPIO pin/port code - `avr`: add support for PinInputPullup - `avr`: work around codegen bug in LLVM 10 - `avr`: fix target triple - `fe310`: remove extra println left in by mistake - `feather-nrf52840`: add support for the Feather nRF52840 - `maixbit`: add board definition and dummy runtime - `nintendoswitch`: Add experimental Nintendo Switch support without CRT - `nrf`: expose the RAM base address - `nrf`: add support for pin change interrupts - `nrf`: add microbit-s110v8 target - `nrf`: fix bug in SPI.Tx - `nrf`: support debugging the PCA10056 - `pygamer`: add Adafruit PyGamer support - `riscv`: fix interrupt configuration bug - `riscv`: disable linker relaxations during gp init - `stm32f4disco`: add new target with ST-Link v2.1 debugger - `teensy36`: add Teensy 3.6 support - `wasm`: fix event handling - `wasm`: add --no-demangle linker option - `wioterminal`: add support for the Seeed Wio Terminal - `xiao`: add support for the Seeed XIAO 0.13.1 --- * **standard library** - `runtime`: do not put scheduler and GC code in the same section - `runtime`: copy stack scan assembly for GBA * **boards** - `gameboy-advance`: always use ARM mode instead of Thumb mode 0.13.0 --- * **command line** - use `gdb-multiarch` for debugging Cortex-M chips - support `tinygo run` with simavr - support LLVM 10 - support Go 1.14 - retry 3 times when attempting to do a 1200-baud reset * **compiler** - mark the `abort` function as noreturn - fix deferred calls to exported functions - add debug info for local variables - check for channel size limit - refactor coroutine lowering - add `dereferenceable_or_null` attribute to pointer parameters - do not perform nil checking when indexing slices and on `unsafe.Pointer` - remove `runtime.isnil` hack - use LLVM builtins for runtime `memcpy`/`memmove`/`memzero` functions - implement spec-compliant shifts on negative/overflow - support anonymous type asserts - track pointer result of string concatenation for GC - track PHI nodes for GC - add debug info to goroutine start wrappers - optimize comparing interface values against nil - fix miscompilation when deferring an interface call - builder: include picolibc for most baremetal targets - builder: run tools (clang, lld) as separate processes - builder: use `-fshort-enums` consistently - interp: add support for constant type asserts - interp: better support for interface operations - interp: include backtrace with error - transform: do not track const globals for GC - transform: replace panics with source locations - transform: fix error in interface lowering pass - transform: make coroutine lowering deterministic - transform: fix miscompilation in func lowering * **cgo** - make `-I` and `-L` paths absolute * **standard library** - `machine`: set the USB VID and PID to the manufacturer values - `machine`: correct USB CDC composite descriptors - `machine`: move `errors.New` calls to globals - `runtime`: support operations on nil maps - `runtime`: fix copy builtin return value on AVR - `runtime`: refactor goroutines - `runtime`: support `-scheduler=none` on most platforms - `runtime`: run package initialization in the main goroutine - `runtime`: export `malloc` / `free` for use from C - `runtime`: add garbage collector that uses an external allocator - `runtime`: scan callee-saved registers while marking the stack - `runtime`: remove recursion from conservative GC - `runtime`: fix blocking select on nil channel - `runtime/volatile`: include `ReplaceBits` method - `sync`: implement trivial `sync.Map` * **targets** - `arm`: use `-fomit-frame-pointer` - `atmega1284`: support this chip for testing purposes - `atsamd51`: make QSPI available on all boards - `atsamd51`: add support for ADC1 - `atsamd51`: use new interrupt registration in UART code - `attiny`: clean up pin definitions - `avr`: use the correct RAM start address - `avr`: pass the correct `-mmcu` flag to the linker - `avr`: add support for tasks scheduler (disabled by default) - `avr`: fix linker problem with overlapping program/data areas - `nrf`: fix typo in pin configuration options - `nrf`: add lib/nrfx/mdk to include dirs - `nrf52840`: implement USB-CDC - `riscv`: implement VirtIO target and add RISC-V integration test - `riscv`: add I2C support for the HiFive1 rev B board - `stm32`: refactor GPIO pin handling - `stm32`: refactor UART code - `stm32f4`: add SPI - `wasm`: support Go 1.14 (breaking previous versions) - `wasm`: support `syscall/js.CopyBytesToJS` - `wasm`: sync polyfills from Go 1.14. * **boards** - `arduino-mega2560`: add the Arduino Mega 2560 - `clue-alpha`: add the Adafruit CLUE Alpha - `gameboy-advance`: enable debugging with GDB - `particle-argon`: add the Particle Argon board - `particle-boron`: add the Particle Boron board - `particle-xenon`: add the Particle Xenon board - `reelboard`: add `reelboard-s140v7` SoftDevice target 0.12.0 --- * **command line** - add initial FreeBSD support - remove getting a serial port in gdb subcommand - add support for debugging through JLinkGDBServer - fix CGo when cross compiling - remove default port check for Digispark as micronucleus communicates directly using HID - differentiate between various serial/USB error messages * **builder** - improve detection of Clang headers * **compiler** - fix assertion on empty interface - don't crash when encountering `types.Invalid` - revise defer to use heap allocations when running a variable number of times - improve error messages for failed imports - improve "function redeclared" error - add globaldce pass to start of optimization pipeline - add support for debugging globals - implement RISC-V CSR operations as intrinsics - add support for CGO_ENABLED environment variable - do not emit debug info for extern globals (bugfix) - add support for interrupts - implement maps for arbitrary keys - interp: error location for "unknown GEP" error - wasm-abi: create temporary allocas in the entry block * **cgo** - add support for symbols in `#define` - fix a bug in number tokenization * **standard library** - `machine`: avoid bytes package in USB logic - `runtime`: fix external address declarations - `runtime`: provide implementation for `internal/bytealg.IndexByte` * **targets** - `atsamd51`: fix volatile usage - `atsamd51`: fix ADC, updating to 12-bits precision - `atsamd51`: refactor SPI pin configuration to only look at pin numbers - `atsamd51`: switch UART to use new pin configuration - `atsamd51`: fix obvious bug in I2C code - `atsamd51`: use only the necessary UART interrupts - `atsamd51`: refactor I2C pin handling to auto-detect pin mode - `avr`: use a garbage collector - `fe310`: use CLINT peripheral for timekeeping - `fe310`: add support for PLIC interrupts - `fe310`: implement UART receive interrupts - `riscv`: support sleeping in QEMU - `riscv`: add bare-bones interrupt support - `riscv`: print exception PC and code - `wasm`: implement memcpy and memset - `wasm`: include wasi-libc - `wasm`: use wasi ABI for basic startup/stdout * **boards** - `arduino`: make avrdude command line compatible with Windows - `arduino-nano`: add this board - `arduino-nano33`: fix UART1 and UART2 - `circuitplay-bluefruit`: add this board - `digispark`: add clock speed and pin mappings - `gameboy-advance`: include compiler-rt in build - `gameboy-advance`: implement interrupt handler - `hifive1b`: add support for gdb subcommand - `pyportal`: add this board - `pyportal`: remove manual SPI pin mapping as now handled by default 0.11.0 --- * **command line** - add support for QEMU in `gdb` subcommand - use builtin Clang when building statically, dropping the clang-9 dependency - search for default serial port on both macOS and Linux - windows: support `tinygo flash` directly by using win32 wmi * **compiler** - add location information to the IR checker - make reflection sidetables constant globals - improve error locations in goroutine lowering - interp: improve support for maps with string keys - interp: add runtime fallback for mapassign operations * **standard library** - `machine`: add support for `SPI.Tx()` on play.tinygo.org - `machine`: rename `CPU_FREQUENCY` to `CPUFrequency()` * **targets** - `adafruit-pybadge`: add Adafruit Pybadge - `arduino-nano33`: allow simulation on play.tinygo.org - `arduino-nano33`: fix default SPI pin numbers to be D13/D11/D12 - `circuitplay-express`: allow simulation on play.tinygo.org - `hifive1-qemu`: add target for testing RISC-V bare metal in QEMU - `riscv`: fix heap corruption due to changes in LLVM 9 - `riscv`: add support for compiler-rt - `qemu`: rename to `cortex-m-qemu` 0.10.0 --- * **command line** - halt GDB after flashing with `gdb` subcommand - fix a crash when using `-ocd-output` - add `info` subcommand - add `-programmer` flag * **builder** - macos: use llvm@8 instead of just llvm in paths - add `linkerscript` key to target JSON files - write a symbol table when writing out the compiler-rt lib - make Clang header detection more robust - switch to LLVM 9 * **compiler** - fix interface miscompilation with reflect - fix miscompile of static goroutine calls to closures - fix `todo: store` panic - fix incorrect starting value for optimized allocations in a loop - optimize coroutines on non-Cortex-M targets - fix crash for programs which have heap allocations but never hit the GC - add support for async interface calls - fix inserting non-const values in a const global - interp: improve error reporting - interp: implement comparing ptrtoint to 0 * **cgo** - improve diagnostics - implement the constant parser (for `#define`) as a real parser - rename reserved field names such as `type` - avoid `"unsafe" imported but not used` error - include all enums in the CGo Go AST - add support for nested structs and unions - implement `#cgo CFLAGS` * **standard library** - `reflect`: add implementation of array alignment - `runtime`: improve scheduler performance when no goroutines are queued - `runtime`: add blocking select - `runtime`: implement interface equality in non-trivial cases - `runtime`: add AdjustTimeOffset to update current time - `runtime`: only implement CountString for required platforms - `runtime`: use MSP/PSP registers for scheduling on Cortex-M * **targets** - `arm`: add system timer registers - `atmega`: add port C GPIO support - `atsamd21`: correct handling of pins >= 32 - `atsamd21`: i2s initialization fixes - `atsamd51`: fix clock init code - `atsamd51`: correct initialization for RTC - `atsamd51`: fix pin function selection - `atsamd51`: pin method cleanup - `atsamd51`: allow setting pin mode for each of the SPI pins - `atsamd51`: correct channel init and pin map for ADC based on ItsyBitsy-M4 - `feather-m4`: add Adafruit Feather M4 board - `hifive1b`: add support for SPI1 - `hifive1b`: fix compiling in simulation - `linux`: fix time on arm32 - `metro-m4`: add support for Adafruit Metro M4 Express Airlift board - `metro-m4`: fixes for UART2 - `pinetime-devkit0`: add support for the PineTime dev kit - `x9pro`: add support for this smartwatch - `pca10040-s132v6`: add support for SoftDevice - `pca10056-s140v7`: add support for SoftDevice - `arduino-nano33`: added SPI1 connected to NINA-W102 chip on Arduino Nano 33 IOT 0.9.0 --- * **command line** - implement 1200-baud UART bootloader reset when flashing boards that support it - flash using mass-storage device for boards that support it - implement `tinygo env` - add support for Windows (but not yet producing Windows binaries) - add Go version to `tinygo env` - update SVD files for up-to-date peripheral interfaces * **compiler** - add `//go:align` pragma - fix bug related to type aliases - add support for buffered channels - remove incorrect reflect optimization - implement copying slices in init interpretation - add support for constant indices with a named type - add support for recursive types like linked lists - fix miscompile of function nil panics - fix bug related to goroutines * **standard library** - `machine`: do not check for nil slices in `SPI.Tx` - `reflectlite`: add support for Go 1.13 - `runtime`: implement `internal/bytealg.CountString` - `sync`: properly handle nil `New` func in `sync.Pool` * **targets** - `arduino`: fix .bss section initialization - `fe310`: implement `Pin.Get` - `gameboy-advance`: support directly outputting .gba files - `samd`: reduce code size by avoiding reflection - `samd21`: do not hardcode pin numbers for peripherals - `stm32f103`: avoid issue with `time.Sleep` less than 200µs 0.8.0 --- * **command line** - fix parsing of beta Go versions - check the major/minor installed version of Go before compiling - validate `-target` flag better to not panic on an invalid target * **compiler** - implement full slice expression: `s[:2:4]` - fix a crash when storing a linked list in an interface - fix comparing struct types by making type IDs more unique - fix some bugs in IR generation - add support for linked lists in reflect data - implement `[]rune` to string conversion - implement support for `go` on func values * **standard library** - `reflect`: add support for named types - `reflect`: add support for `t.Bits()` - `reflect`: add basic support for `t.AssignableTo()` - `reflect`: implement `t.Align()` - `reflect`: add support for struct types - `reflect`: fix bug in `v.IsNil` and `v.Pointer` for addressable values - `reflect`: implement support for array types - `reflect`: implement `t.Comparable()` - `runtime`: implement stack-based scheduler - `runtime`: fix bug in the sleep queue of the scheduler - `runtime`: implement `memcpy` for Cortex-M - `testing`: implement stub `testing.B` struct - `testing`: add common test logging methods such as Errorf/Fatalf/Printf * **targets** - `386`: add support for linux/386 syscalls - `atsamd21`: make SPI pins configurable so that multiple SPI ports can be used - `atsamd21`: correct issue with invalid first reading coming from ADC - `atsamd21`: add support for reset-to-bootloader using 1200baud over USB-CDC - `atsamd21`: make pin selection more flexible for peripherals - `atsamd21`: fix minimum delay in `time.Sleep` - `atsamd51`: fix minimum delay in `time.Sleep` - `nrf`: improve SPI write-only speed, by making use of double buffering - `stm32f103`: fix SPI frequency selection - `stm32f103`: add machine.Pin.Get method for reading GPIO values - `stm32f103`: allow board specific UART usage - `nucleo-f103rb`: add support for NUCLEO-F103RB board - `itsybitsy-m4`: add support for this board with a SAMD51 family chip - `cortex-m`: add support for `arm.SystemReset()` - `gameboy-advance`: add initial support for the GameBoy Advance - `wasm`: add `//go:wasm-module` magic comment to set the wasm module name - `wasm`: add syscall/js.valueSetIndex support - `wasm`: add syscall/js.valueInvoke support 0.7.1 --- * **targets** - `atsamd21`: add support for the `-port` flag in the flash subcommand 0.7.0 --- * **command line** - try more locations to find Clang built-in headers - add support for `tinygo test` - build current directory if no package is specified - support custom .json target spec with `-target` flag - use zversion.go to detect version of GOROOT version - make initial heap size configurable for some targets (currently WebAssembly only) * **cgo** - add support for bitfields using generated getters and setters - add support for anonymous structs * **compiler** - show an error instead of panicking on duplicate function definitions - allow packages like github.com/tinygo-org/tinygo/src/\* by aliasing it - remove `//go:volatile` support It has been replaced with the runtime/volatile package. - allow pointers in map keys - support non-constant syscall numbers - implement non-blocking selects - add support for the `-tags` flag - add support for `string` to `[]rune` conversion - implement a portable conservative garbage collector (with support for wasm) - add the `//go:noinline` pragma * **standard library** - `os`: add `os.Exit` and `syscall.Exit` - `os`: add several stubs - `runtime`: fix heap corruption in conservative GC - `runtime`: add support for math intrinsics where supported, massively speeding up some benchmarks - `testing`: add basic support for testing * **targets** - add support for a generic target that calls `__tinygo_*` functions for peripheral access - `arduino-nano33`: add support for this board - `hifive1`: add support for this RISC-V board - `reelboard`: add e-paper pins - `reelboard`: add `PowerSupplyActive` to enable voltage for on-board devices - `wasm`: put the stack at the start of linear memory, to detect stack overflows 0.6.0 --- * **command line** - some portability improvements - make `$GOROOT` more robust and configurable - check for Clang at the Homebrew install location as fallback * **compiler driver** - support multiple variations of LLVM commands, for non-Debian distributions * **compiler** - improve code quality in multiple ways - make panic configurable, adding trap on panic - refactor many internal parts of the compiler - print all errors encountered during compilation - implement calling function values of a named type - implement returning values from blocking functions - allow larger-than-int values to be sent across a channel - implement complex arithmetic - improve hashmap support - add debuginfo for function arguments - insert nil checks on stores (increasing code size) - implement volatile operations as compiler builtins - add `//go:inline` pragma - add build tags for the Go stdlib version * **cgo** - implement `char`, `enum` and `void*` types - support `#include` for builtin headers - improve typedef/struct/enum support - only include symbols that are necessary, for broader support - mark external function args as `nocapture` - implement support for some `#define` constants - implement support for multiple CGo files in a single package - **standard library** - `machine`: remove microbit matrix (moved to drivers repository) - `machine`: refactor pins to use `Pin` type instead of `GPIO` - `runtime`: print more interface types on panic, including `error` * **targets** - `arm`: print an error on HardFault (including stack overflows) - `atsamd21`: fix a bug in the ADC peripheral - `atsamd21`: add support for I2S - `feather-m0`: add support for this board - `nrf51`: fix a bug in I2C - `stm32f103xx`: fix a bug in I2C - `syscall`: implement `Exit` on unix - `trinket-m0`: add support for this board - `wasm`: make _main_ example smaller - `wasm`: don't cache wasm file in the server, for ease of debugging - `wasm`: work around bug #41508 that caused a deadlock while linking - `wasm`: add support for `js.FuncOf` 0.5.0 --- - **compiler driver** - use `wasm-ld` instead of `wasm-ld-8` on macOS - drop dependency on `llvm-ar` - fix linker script includes when running outside `TINYGOROOT` - **compiler** - switch to LLVM 8 - add support for the Go 1.12 standard library (Go 1.11 is still supported) - work around lack of escape analysis due to nil checks - implement casting named structs and pointers to them - fix int casting to use the source signedness - fix some bugs around `make([]T, …)` with uncommon index types - some other optimizations - support interface asserts in interp for "math/rand" support - resolve all func value targets at compile time (wasm-only at the moment) - **cgo** - improve diagnostics - implement C `struct`, `union`, and arrays - fix CGo-related crash in libclang - implement `C.struct_` types - **targets** - all baremetal: pretend to be linux/arm instead of js/wasm - `avr`: improve `uintptr` support - `cortexm`: implement memmove intrinsic generated by LLVM - `cortexm`: use the lld linker instead of `arm-none-eabi-ld` - `darwin`: use custom syscall package that links to libSystem.dylib - `microbit`: add blink example - `samd21`: support I2C1 - `samd21`: machine/atsamd21: correct pad/pin handling when using both UART and USBCDC interfaces at same time - `stm32f4discovery`: add support for this board - `wasm`: support async func values - `wasm`: improve documentation and add extra example 0.4.1 --- - **compiler** - fix `objcopy` replacement to include the .data section in the firmware image - use `llvm-ar-7` on Linux to fix the Docker image 0.4.0 --- - **compiler** - switch to the hardfloat ABI on ARM, which is more widely used - avoid a dependency on `objcopy` (`arm-none-eabi-objcopy` etc.) - fix a bug in `make([]T, n)` where `n` is 64-bits on a 32-bit platform - adapt to a change in the AVR backend in LLVM 8 - directly support the .uf2 firmware format as used on Adafruit boards - fix a bug when calling `panic()` at init time outside of the main package - implement nil checks, which results in a ~5% increase in code size - inline slice bounds checking, which results in a ~1% decrease in code size - **targets** - `samd21`: fix a bug in port B pins - `samd21`: implement SPI peripheral - `samd21`: implement ADC peripheral - `stm32`: fix a bug in timekeeping - `wasm`: fix a bug in `wasm_exec.js` that caused corruption in linear memory when running on Node.js. 0.3.0 --- - **compiler** - remove old `-initinterp` flag - add support for macOS - **cgo** - add support for bool/float/complex types - **standard library** - `device/arm`: add support to disable/enable hardware interrupts - `machine`: add CPU frequency for nrf-based boards - `syscall`: add support for darwin/amd64 - **targets** - `circuitplay_express`: add support for this board - `microbit`: add regular pin constants - `samd21`: fix time function for goroutine support - `samd21`: add support for USB-CDC (serial over USB) - `samd21`: add support for pins in port B - `samd21`: add support for pullup and pulldown pins - `wasm`: add support for Safari in example 0.2.0 --- - **command line** - add version subcommand - **compiler** - fix a bug in floating point comparisons with NaN values - fix a bug when calling `panic` in package initialization code - add support for comparing `complex64` and `complex128` - **cgo** - add support for external globals - add support for pointers and function pointers - **standard library** - `fmt`: initial support, `fmt.Println` works - `math`: support for most/all functions - `os`: initial support (only stdin/stdout/stderr) - `reflect`: initial support - `syscall`: add support for amd64, arm, and arm64 ================================================ FILE: CODE-OF-CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [conduct@tinygo.org](mailto:conduct@tinygo.org). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: CONTRIBUTING.md ================================================ Please take a look at our [Contributing](https://tinygo.org/docs/guides/contributing/) page on our web site for details. Thank you. ================================================ FILE: CONTRIBUTORS ================================================ # This is the official list of TinyGo authors for copyright purposes. # # This file is not actively maintained. # To be included, send a change adding the individual or # company who owns a contribution's copyright. # # Names should be added to this file as one of # Organization's name # Individual's name # Individual's name # # Please keep the list sorted. Ayke van Laethem Daniel Esteban Loon, LLC. Ron Evans Nia Weiss ================================================ FILE: Dockerfile ================================================ # tinygo-llvm stage obtains the llvm source for TinyGo FROM golang:1.25 AS tinygo-llvm RUN apt-get update && \ apt-get install -y apt-utils make cmake clang-17 ninja-build && \ rm -rf \ /var/lib/apt/lists/* \ /var/log/* \ /var/tmp/* \ /tmp/* COPY ./GNUmakefile /tinygo/GNUmakefile RUN cd /tinygo/ && \ make llvm-source # tinygo-llvm-build stage build the custom llvm with xtensa support FROM tinygo-llvm AS tinygo-llvm-build RUN cd /tinygo/ && \ make llvm-build # tinygo-compiler-build stage builds the compiler itself FROM tinygo-llvm-build AS tinygo-compiler-build COPY . /tinygo # build the compiler and tools RUN cd /tinygo/ && \ git submodule update --init && \ make gen-device -j4 && \ make build/release # tinygo-compiler copies the compiler build over to a base Go container (without # all the build tools etc). FROM golang:1.25 AS tinygo-compiler # Copy tinygo build. COPY --from=tinygo-compiler-build /tinygo/build/release/tinygo /tinygo # Configure the container. ENV PATH="${PATH}:/tinygo/bin" CMD ["tinygo"] ================================================ FILE: GNUmakefile ================================================ # aliases all: tinygo # Default build and source directories, as created by `make llvm-build`. LLVM_BUILDDIR ?= llvm-build LLVM_PROJECTDIR ?= llvm-project CLANG_SRC ?= $(LLVM_PROJECTDIR)/clang LLD_SRC ?= $(LLVM_PROJECTDIR)/lld ifeq ($(OS),Windows_NT) # avoid calling uname on Windows uname := Windows_NT else uname := $(shell uname -s) endif # Try to autodetect LLVM build tools. # Versions are listed here in descending priority order. LLVM_VERSIONS = 19 18 17 16 15 errifempty = $(if $(1),$(1),$(error $(2))) detect = $(shell which $(call errifempty,$(firstword $(foreach p,$(2),$(shell command -v $(p) 2> /dev/null && echo $(p)))),failed to locate $(1) at any of: $(2))) toolSearchPathsVersion = $(1)-$(2) ifeq ($(uname),Darwin) # Also explicitly search Brew's copy, which is not in PATH by default. BREW_PREFIX := $(shell brew --prefix) toolSearchPathsVersion += $(BREW_PREFIX)/opt/llvm@$(2)/bin/$(1)-$(2) $(BREW_PREFIX)/opt/llvm@$(2)/bin/$(1) endif # First search for a custom built copy, then move on to explicitly version-tagged binaries, then just see if the tool is in path with its normal name. findLLVMTool = $(call detect,$(1),$(abspath llvm-build/bin/$(1)) $(foreach ver,$(LLVM_VERSIONS),$(call toolSearchPathsVersion,$(1),$(ver))) $(1)) CLANG ?= $(call findLLVMTool,clang) LLVM_AR ?= $(call findLLVMTool,llvm-ar) LLVM_NM ?= $(call findLLVMTool,llvm-nm) # Go binary and GOROOT to select GO ?= go # Flags to pass to go test. GOTESTFLAGS ?= GOTESTPKGS ?= ./builder ./cgo ./compileopts ./compiler ./interp ./transform . # tinygo binary for tests TINYGO ?= $(call detect,tinygo,tinygo $(CURDIR)/build/tinygo) # Check for ccache if the user hasn't set it to on or off. ifeq (, $(CCACHE)) LLVM_OPTION += '-DLLVM_CCACHE_BUILD=$(if $(shell command -v ccache 2> /dev/null),ON,OFF)' else LLVM_OPTION += '-DLLVM_CCACHE_BUILD=$(CCACHE)' endif # Allow enabling LLVM assertions ifeq (1, $(ASSERT)) LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=ON' else LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF' endif # Enable AddressSanitizer ifeq (1, $(ASAN)) LLVM_OPTION += -DLLVM_USE_SANITIZER=Address CGO_LDFLAGS += -fsanitize=address endif ifeq (1, $(STATIC)) # Build TinyGo as a fully statically linked binary (no dynamically loaded # libraries such as a libc). This is not supported with glibc which is used # on most major Linux distributions. However, it is supported in Alpine # Linux with musl. CGO_LDFLAGS += -static # Also set the thread stack size to 1MB. This is necessary on musl as the # default stack size is 128kB and LLVM uses more than that. # For more information, see: # https://wiki.musl-libc.org/functional-differences-from-glibc.html#Thread-stack-size CGO_LDFLAGS += -Wl,-z,stack-size=1048576 # Build wasm-opt with static linking. # For details, see: # https://github.com/WebAssembly/binaryen/blob/version_102/.github/workflows/ci.yml#L181 BINARYEN_OPTION += -DCMAKE_CXX_FLAGS="-static" -DCMAKE_C_FLAGS="-static" endif # Optimize the binary size for Linux. # These flags may work on other platforms, but have only been tested on Linux. ifeq ($(uname),Linux) HAS_MOLD := $(shell command -v ld.mold 2> /dev/null) HAS_LLD := $(shell command -v ld.lld 2> /dev/null) LLVM_CFLAGS := -ffunction-sections -fdata-sections -fvisibility=hidden LLVM_LDFLAGS := -Wl,--gc-sections ifneq ($(HAS_MOLD),) # Mold might be slightly faster. LLVM_LDFLAGS += -fuse-ld=mold -Wl,--icf=all else ifneq ($(HAS_LLD),) # LLD is more commonly available. LLVM_LDFLAGS += -fuse-ld=lld -Wl,--icf=all endif LLVM_OPTION += \ -DCMAKE_C_FLAGS="$(LLVM_CFLAGS)" \ -DCMAKE_CXX_FLAGS="$(LLVM_CFLAGS)" CGO_LDFLAGS += $(LLVM_LDFLAGS) endif # Cross compiling support. ifneq ($(CROSS),) CC = $(CROSS)-gcc CXX = $(CROSS)-g++ LLVM_OPTION += \ -DCMAKE_C_COMPILER=$(CC) \ -DCMAKE_CXX_COMPILER=$(CXX) \ -DLLVM_DEFAULT_TARGET_TRIPLE=$(CROSS) \ -DCROSS_TOOLCHAIN_FLAGS_NATIVE="-UCMAKE_C_COMPILER;-UCMAKE_CXX_COMPILER" ifeq ($(CROSS), arm-linux-gnueabihf) # Assume we're building on a Debian-like distro, with QEMU installed. LLVM_CONFIG_PREFIX = qemu-arm -L /usr/arm-linux-gnueabihf/ # The CMAKE_SYSTEM_NAME flag triggers cross compilation mode. LLVM_OPTION += \ -DCMAKE_SYSTEM_NAME=Linux \ -DLLVM_TARGET_ARCH=ARM GOENVFLAGS = GOARCH=arm CC=$(CC) CXX=$(CXX) CGO_ENABLED=1 BINARYEN_OPTION += -DCMAKE_C_COMPILER=$(CC) -DCMAKE_CXX_COMPILER=$(CXX) else ifeq ($(CROSS), aarch64-linux-gnu) # Assume we're building on a Debian-like distro, with QEMU installed. LLVM_CONFIG_PREFIX = qemu-aarch64 -L /usr/aarch64-linux-gnu/ # The CMAKE_SYSTEM_NAME flag triggers cross compilation mode. LLVM_OPTION += \ -DCMAKE_SYSTEM_NAME=Linux \ -DLLVM_TARGET_ARCH=AArch64 GOENVFLAGS = GOARCH=arm64 CC=$(CC) CXX=$(CXX) CGO_ENABLED=1 BINARYEN_OPTION += -DCMAKE_C_COMPILER=$(CC) -DCMAKE_CXX_COMPILER=$(CXX) else $(error Unknown cross compilation target: $(CROSS)) endif endif .PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr gen-device-rp LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf debuginfopdb executionengine frontenddriver frontendhlsl frontendopenmp instrumentation interpreter ipo irreader libdriver linker lto mc mcjit objcarcopts option profiledata scalaropts support target windowsdriver windowsmanifest ifeq ($(OS),Windows_NT) EXE = .exe START_GROUP = -Wl,--start-group END_GROUP = -Wl,--end-group # PIC needs to be disabled for libclang to work. LLVM_OPTION += -DLLVM_ENABLE_PIC=OFF CGO_CPPFLAGS += -DCINDEX_NO_EXPORTS CGO_LDFLAGS += -static -static-libgcc -static-libstdc++ CGO_LDFLAGS_EXTRA += -lversion USE_SYSTEM_BINARYEN ?= 1 else ifeq ($(uname),Darwin) MD5SUM ?= md5 CGO_LDFLAGS += -lxar USE_SYSTEM_BINARYEN ?= 1 else ifeq ($(uname),FreeBSD) MD5SUM ?= md5 START_GROUP = -Wl,--start-group END_GROUP = -Wl,--end-group else START_GROUP = -Wl,--start-group END_GROUP = -Wl,--end-group endif # md5sum binary default, can be overridden by an environment variable MD5SUM ?= md5sum # Libraries that should be linked in for the statically linked Clang. CLANG_LIB_NAMES = clangAnalysis clangAPINotes clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangExtractAPI clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangInstallAPI clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangSupport clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++ # Libraries that should be linked in for the statically linked LLD. LLD_LIB_NAMES = lldCOFF lldCommon lldELF lldMachO lldMinGW lldWasm LLD_LIBS = $(START_GROUP) $(addprefix -l,$(LLD_LIB_NAMES)) $(END_GROUP) # Other libraries that are needed to link TinyGo. EXTRA_LIB_NAMES = LLVMInterpreter LLVMMCA LLVMRISCVTargetMCA LLVMX86TargetMCA # All libraries to be built and linked with the tinygo binary (lib/lib*.a). LIB_NAMES = clang $(CLANG_LIB_NAMES) $(LLD_LIB_NAMES) $(EXTRA_LIB_NAMES) # These build targets appear to be the only ones necessary to build all TinyGo # dependencies. Only building a subset significantly speeds up rebuilding LLVM. # The Makefile rules convert a name like lldELF to lib/liblldELF.a to match the # library path (for ninja). # This list also includes a few tools that are necessary as part of the full # TinyGo build. NINJA_BUILD_TARGETS = clang llvm-config llvm-ar llvm-nm lld $(addprefix lib/lib,$(addsuffix .a,$(LIB_NAMES))) # For static linking. ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","") CGO_CPPFLAGS+=$(shell $(LLVM_CONFIG_PREFIX) $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(LLVM_BUILDDIR))/tools/clang/include -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include CGO_CXXFLAGS=-std=c++17 CGO_LDFLAGS+=-L$(abspath $(LLVM_BUILDDIR)/lib) -lclang $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_CONFIG_PREFIX) $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA) endif clean: ## Remove build directory @rm -rf build FMT_PATHS = ./*.go builder cgo/*.go compiler interp loader src transform fmt: ## Reformat source @gofmt -l -w $(FMT_PATHS) fmt-check: ## Warn if any source needs reformatting @unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1 gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp gen-device-rp ## Generate microcontroller-specific sources ifneq ($(RENESAS), 0) gen-device: gen-device-renesas endif ifneq ($(STM32), 0) gen-device: gen-device-stm32 endif gen-device-avr: @if [ ! -e lib/avr/README.md ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi $(GO) build -o ./build/gen-device-avr ./tools/gen-device-avr/ ./build/gen-device-avr lib/avr/packs/atmega src/device/avr/ ./build/gen-device-avr lib/avr/packs/tiny src/device/avr/ @GO111MODULE=off $(GO) fmt ./src/device/avr build/gen-device-svd: ./tools/gen-device-svd/*.go $(GO) build -o $@ ./tools/gen-device-svd/ gen-device-esp: build/gen-device-svd ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif-Community -interrupts=software lib/cmsis-svd/data/Espressif-Community/ src/device/esp/ ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif -interrupts=software lib/cmsis-svd/data/Espressif/ src/device/esp/ GO111MODULE=off $(GO) fmt ./src/device/esp gen-device-nrf: build/gen-device-svd ./build/gen-device-svd -source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk lib/nrfx/mdk/ src/device/nrf/ GO111MODULE=off $(GO) fmt ./src/device/nrf gen-device-nxp: build/gen-device-svd ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/NXP lib/cmsis-svd/data/NXP/ src/device/nxp/ GO111MODULE=off $(GO) fmt ./src/device/nxp gen-device-sam: build/gen-device-svd ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel lib/cmsis-svd/data/Atmel/ src/device/sam/ GO111MODULE=off $(GO) fmt ./src/device/sam gen-device-sifive: build/gen-device-svd ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/SiFive-Community -interrupts=software lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/ GO111MODULE=off $(GO) fmt ./src/device/sifive gen-device-kendryte: build/gen-device-svd ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Kendryte-Community -interrupts=software lib/cmsis-svd/data/Kendryte-Community/ src/device/kendryte/ GO111MODULE=off $(GO) fmt ./src/device/kendryte gen-device-stm32: build/gen-device-svd ./build/gen-device-svd -source=https://github.com/tinygo-org/stm32-svd lib/stm32-svd/svd src/device/stm32/ GO111MODULE=off $(GO) fmt ./src/device/stm32 gen-device-rp: build/gen-device-svd ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/RaspberryPi lib/cmsis-svd/data/RaspberryPi/ src/device/rp/ GO111MODULE=off $(GO) fmt ./src/device/rp gen-device-renesas: build/gen-device-svd ./build/gen-device-svd -source=https://github.com/cmsis-svd/cmsis-svd-data/tree/master/data/Renesas lib/cmsis-svd/data/Renesas/ src/device/renesas/ GO111MODULE=off $(GO) fmt ./src/device/renesas $(LLVM_PROJECTDIR)/llvm: git clone -b tinygo_20.x --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR) llvm-source: $(LLVM_PROJECTDIR)/llvm ## Get LLVM sources # Configure LLVM. TINYGO_SOURCE_DIR=$(shell pwd) $(LLVM_BUILDDIR)/build.ninja: mkdir -p $(LLVM_BUILDDIR) && cd $(LLVM_BUILDDIR) && cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;AVR;Mips;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_ZSTD=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF -DCLANG_ENABLE_STATIC_ANALYZER=OFF -DCLANG_ENABLE_ARCMT=OFF $(LLVM_OPTION) $(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja ## Build LLVM cd $(LLVM_BUILDDIR) && ninja $(NINJA_BUILD_TARGETS) ifneq ($(USE_SYSTEM_BINARYEN),1) # Build Binaryen .PHONY: binaryen binaryen: build/wasm-opt$(EXE) build/wasm-opt$(EXE): mkdir -p build cd lib/binaryen && cmake -G Ninja . -DBUILD_STATIC_LIB=ON -DBUILD_TESTS=OFF -DENABLE_WERROR=OFF $(BINARYEN_OPTION) && ninja bin/wasm-opt$(EXE) cp lib/binaryen/bin/wasm-opt$(EXE) build/wasm-opt$(EXE) endif # Generate WASI syscall bindings WASM_TOOLS_MODULE=go.bytecodealliance.org .PHONY: wasi-syscall wasi-syscall: wasi-cm rm -rf ./src/internal/wasi/* go run -modfile ./internal/wasm-tools/go.mod $(WASM_TOOLS_MODULE)/cmd/wit-bindgen-go generate --versioned -o ./src/internal -p internal --cm internal/cm ./lib/wasi-cli/wit # Copy package cm into src/internal/cm .PHONY: wasi-cm wasi-cm: rm -rf ./src/internal/cm/* rsync -rv --delete --exclude go.mod --exclude '*_test.go' --exclude '*_json.go' --exclude '*.md' --exclude LICENSE $(shell go list -modfile ./internal/wasm-tools/go.mod -m -f {{.Dir}} $(WASM_TOOLS_MODULE)/cm)/ ./src/internal/cm # Check for Node.js used during WASM tests. MIN_NODEJS_VERSION=18 .PHONY: check-nodejs-version check-nodejs-version: @# Check whether NodeJS is available. @if ! command -v node 2>&1 >/dev/null; then echo "Install NodeJS version ${MIN_NODEJS_VERSION}+ to run tests."; exit 1; fi @# Check whether the version is high enough. @if [ "`node -v | sed 's/v\([0-9]\+\).*/\\1/g'`" -lt $(MIN_NODEJS_VERSION) ]; then echo "Install NodeJS version $(MIN_NODEJS_VERSION)+ to run tests."; exit 1; fi tinygo: ## Build the TinyGo compiler @if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " $(MAKE) llvm-source"; echo " $(MAKE) $(LLVM_BUILDDIR)"; exit 1; fi CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" . test: check-nodejs-version CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags "byollvm osusergo" $(GOTESTPKGS) # Standard library packages that pass tests on darwin, linux, wasi, and windows, but take over a minute in wasi TEST_PACKAGES_SLOW = \ compress/bzip2 \ crypto/dsa \ index/suffixarray \ # Standard library packages that pass tests quickly on darwin, linux, wasi, and windows TEST_PACKAGES_FAST = \ cmp \ compress/lzw \ compress/zlib \ container/heap \ container/list \ container/ring \ crypto/ecdsa \ crypto/elliptic \ crypto/md5 \ crypto/sha1 \ crypto/sha256 \ crypto/sha512 \ database/sql/driver \ debug/macho \ embed/internal/embedtest \ encoding \ encoding/ascii85 \ encoding/asn1 \ encoding/base32 \ encoding/base64 \ encoding/csv \ encoding/hex \ go/ast \ go/format \ go/scanner \ go/version \ hash \ hash/adler32 \ hash/crc64 \ hash/fnv \ html \ internal/itoa \ internal/profile \ math \ math/cmplx \ net/http/internal/ascii \ net/mail \ os \ path \ reflect \ sync \ testing \ testing/iotest \ text/scanner \ unicode \ unicode/utf16 \ unicode/utf8 \ unique \ $(nil) # archive/zip requires os.ReadAt, which is not yet supported on windows # bytes requires mmap # compress/flate appears to hang on wasi # crypto/aes fails on wasi, needs panic()/recover() # crypto/des fails on wasi, needs panic()/recover() # crypto/hmac fails on wasi, it exits with a "slice out of range" panic # debug/plan9obj requires os.ReadAt, which is not yet supported on windows # image requires recover(), which is not yet supported on wasi # io/ioutil requires os.ReadDir, which is not yet supported on windows or wasi # mime: fail on wasi; neds panic()/recover() # mime/multipart: needs wasip1 syscall.FDFLAG_NONBLOCK # mime/quotedprintable requires syscall.Faccessat # net/mail: needs wasip1 syscall.FDFLAG_NONBLOCK # net/ntextproto: needs wasip1 syscall.FDFLAG_NONBLOCK # regexp/syntax: fails on wasip1; needs panic()/recover() # strconv requires recover() which is not yet supported on wasi # text/tabwriter requires recover(), which is not yet supported on wasi # text/template/parse requires recover(), which is not yet supported on wasi # testing/fstest requires os.ReadDir, which is not yet supported on windows or wasi # Additional standard library packages that pass tests on individual platforms TEST_PACKAGES_LINUX := \ archive/zip \ compress/flate \ crypto/aes \ crypto/des \ crypto/hmac \ debug/dwarf \ debug/plan9obj \ image \ io/ioutil \ mime \ mime/multipart \ mime/quotedprintable \ net \ net/mail \ net/textproto \ os/user \ regexp/syntax \ strconv \ text/tabwriter \ text/template/parse TEST_PACKAGES_DARWIN := $(TEST_PACKAGES_LINUX) # os/user requires t.Skip() support TEST_PACKAGES_WINDOWS := \ compress/flate \ crypto/des \ crypto/hmac \ strconv \ text/template/parse \ $(nil) # These packages cannot be tested on wasm, mostly because these tests assume a # working filesystem. This could perhaps be fixed, by supporting filesystem # access when running inside Node.js. TEST_PACKAGES_WASM = $(filter-out $(TEST_PACKAGES_NONWASM), $(TEST_PACKAGES_FAST)) TEST_PACKAGES_NONWASM = \ compress/lzw \ compress/zlib \ crypto/ecdsa \ debug/macho \ embed/internal/embedtest \ go/format \ os \ testing \ $(nil) # These packages cannot be tested on baremetal. # # Some reasons why the tests don't pass on baremetal: # # * No filesystem is available, so packages like compress/zlib can't be tested # (just like wasm). # * picolibc math functions apparently are less precise, the math package # fails on baremetal. TEST_PACKAGES_BAREMETAL = $(filter-out $(TEST_PACKAGES_NONBAREMETAL), $(TEST_PACKAGES_FAST)) TEST_PACKAGES_NONBAREMETAL = \ $(TEST_PACKAGES_NONWASM) \ math \ $(nil) # Report platforms on which each standard library package is known to pass tests report-stdlib-tests-pass: $(eval jointmp := $(shell echo /tmp/join.$$$$)) @for t in $(TEST_PACKAGES_DARWIN); do echo "$$t darwin"; done | sort > $(jointmp).darwin @for t in $(TEST_PACKAGES_LINUX); do echo "$$t linux"; done | sort > $(jointmp).linux @for t in $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW); do echo "$$t darwin linux wasi windows"; done | sort > $(jointmp).portable @join -a1 -a2 $(jointmp).darwin $(jointmp).linux | \ join -a1 -a2 - $(jointmp).portable @rm $(jointmp).* # Standard library packages that pass tests quickly on the current platform ifeq ($(uname),Darwin) TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_DARWIN) TEST_IOFS := true endif ifeq ($(uname),Linux) TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_LINUX) TEST_IOFS := true endif ifeq ($(OS),Windows_NT) TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_WINDOWS) TEST_IOFS := false endif TEST_SKIP_FLAG := -skip='TestExtraMethods|TestParseAndBytesRoundTrip/P256/Generic' TEST_ADDITIONAL_FLAGS ?= # Test known-working standard library packages. # TODO: parallelize, and only show failing tests (no implied -v flag). .PHONY: tinygo-test tinygo-test: @# TestExtraMethods: used by many crypto packages and uses reflect.Type.Method which is not implemented. @# TestParseAndBytesRoundTrip/P256/Generic: relies on t.Skip() which is not implemented $(TINYGO) test $(TEST_ADDITIONAL_FLAGS) $(TEST_SKIP_FLAG) $(TEST_PACKAGES_HOST) $(TEST_PACKAGES_SLOW) @# io/fs requires os.ReadDir, not yet supported on windows or wasi. It also @# requires a large stack-size. Hence, io/fs is only run conditionally. @# For more details, see the comments on issue #3143. ifeq ($(TEST_IOFS),true) $(TINYGO) test -stack-size=6MB io/fs endif tinygo-test-fast: $(TINYGO) test $(TEST_SKIP_FLAG) $(TEST_PACKAGES_HOST) tinygo-bench: $(TINYGO) test -bench . $(TEST_PACKAGES_HOST) $(TEST_PACKAGES_SLOW) tinygo-bench-fast: $(TINYGO) test -bench . $(TEST_PACKAGES_HOST) # Same thing, except for wasi rather than the current platform. tinygo-test-wasm: $(TINYGO) test -target wasm $(TEST_SKIP_FLAG) $(TEST_PACKAGES_WASM) tinygo-test-wasi: $(TINYGO) test -target wasip1 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi tinygo-test-wasip1: GOOS=wasip1 GOARCH=wasm $(TINYGO) test $(TEST_SKIP_FLAG) $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi tinygo-test-wasip1-fast: $(TINYGO) test -target=wasip1 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_FAST) ./tests/runtime_wasi tinygo-test-wasip2-slow: $(TINYGO) test -target=wasip2 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_SLOW) tinygo-test-wasip2-fast: $(TINYGO) test -target=wasip2 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_FAST) ./tests/runtime_wasi tinygo-test-wasip2-sum-slow: TINYGO=$(TINYGO) \ TARGET=wasip2 \ TESTOPTS="-x -work" \ PACKAGES="$(TEST_PACKAGES_SLOW)" \ gotestsum --raw-command -- ./tools/tgtestjson.sh tinygo-test-wasip2-sum-fast: TINYGO=$(TINYGO) \ TARGET=wasip2 \ TESTOPTS="-x -work" \ PACKAGES="$(TEST_PACKAGES_FAST)" \ gotestsum --raw-command -- ./tools/tgtestjson.sh tinygo-bench-wasip1: $(TINYGO) test -target wasip1 -bench . $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) tinygo-bench-wasip1-fast: $(TINYGO) test -target wasip1 -bench . $(TEST_PACKAGES_FAST) tinygo-bench-wasip2: $(TINYGO) test -target wasip2 -bench . $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) tinygo-bench-wasip2-fast: $(TINYGO) test -target wasip2 -bench . $(TEST_PACKAGES_FAST) # Run tests on riscv-qemu since that one provides a large amount of memory. tinygo-test-baremetal: $(TINYGO) test -target riscv-qemu $(TEST_SKIP_FLAG) $(TEST_PACKAGES_BAREMETAL) # Test external packages in a large corpus. test-corpus: CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml test-corpus-fast: CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus -short . -corpus=testdata/corpus.yaml test-corpus-wasi: CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasip1 test-corpus-wasip2: CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasip2 .PHONY: testchdir testchdir: # test 'build' command with{,out} -C argument $(TINYGO) build -C tests/testing/chdir chdir.go && rm tests/testing/chdir/chdir $(TINYGO) build ./tests/testing/chdir/chdir.go && rm chdir # test 'run' command with{,out} -C argument EXPECT_DIR=$(PWD)/tests/testing/chdir $(TINYGO) run -C tests/testing/chdir chdir.go EXPECT_DIR=$(PWD) $(TINYGO) run ./tests/testing/chdir/chdir.go .PHONY: smoketest smoketest: testchdir $(TINYGO) version $(TINYGO) targets > /dev/null # regression test for #2892 cd tests/testing/recurse && ($(TINYGO) test ./... > recurse.log && cat recurse.log && test $$(wc -l < recurse.log) = 2 && rm recurse.log) # compile-only platform-independent examples cd tests/text/template/smoke && $(TINYGO) test -c && rm -f smoke.test # regression test for #2563 cd tests/os/smoke && $(TINYGO) test -c -target=pybadge && rm smoke.test # test all examples (except pwm) $(TINYGO) build -size short -o test.hex -target=pga2350 examples/echo @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/adc @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinkm @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky2 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/button @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/button2 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/echo @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/echo2 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/mcp3008 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/memstats @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/rtcinterrupt @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/machinetest @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/systick @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/test @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/time-offset @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=wioterminal examples/hid-mouse @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=wioterminal examples/hid-keyboard @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/i2c-target @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/watchdog @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/device-id @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pico2-ice examples/blinky1 @$(MD5SUM) test.hex # test simulated boards on play.tinygo.org ifneq ($(WASM), 0) GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1 @$(MD5SUM) test.wasm GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=hifive1b examples/blinky1 @$(MD5SUM) test.wasm GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=reelboard examples/blinky1 @$(MD5SUM) test.wasm GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=microbit examples/microbit-blink @$(MD5SUM) test.wasm GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=circuitplay_express examples/blinky1 @$(MD5SUM) test.wasm GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=circuitplay_bluefruit examples/blinky1 @$(MD5SUM) test.wasm GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=mch2022 examples/machinetest @$(MD5SUM) test.wasm GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=gopher_badge examples/blinky1 @$(MD5SUM) test.wasm GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=pico examples/blinky1 @$(MD5SUM) test.wasm endif # test all targets/boards $(TINYGO) build -size short -o test.hex -target=pca10040-s132v6 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=microbit examples/echo @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=microbit-s110v8 examples/echo @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=microbit-v2 examples/microbit-blink @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=microbit-v2-s113v7 examples/microbit-blink @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=microbit-v2-s140v7 examples/microbit-blink @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=nrf52840-mdk examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=btt-skr-pico examples/uart @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky2 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10056 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10056 examples/blinky2 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10059 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10059 examples/blinky2 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=bluemicro840 examples/blinky2 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=feather-m0 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=trinket-m0 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=gemma-m0 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=circuitplay-bluefruit examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=clue-alpha examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.gba -target=gameboy-advance examples/gba-display @$(MD5SUM) test.gba $(TINYGO) build -size short -o test.hex -target=grandcentral-m4 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=feather-m4 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=matrixportal-m4 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pybadge examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=metro-m4-airlift examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pyportal examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=particle-argon examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=particle-boron examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=particle-xenon examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pinetime examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=x9pro examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10056-s140v7 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10059-s140v7 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=reelboard-s140v7 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=wioterminal examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pygamer examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=xiao examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=rak4631 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/dac @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pyportal examples/dac @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=feather-nrf52840-sense examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=itsybitsy-nrf52840 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=qtpy examples/machinetest @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=teensy41 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=teensy40 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=teensy36 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=p1am-100 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=atsame54-xpro examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=atsame54-xpro examples/can @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=feather-m4-can examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=feather-m4-can examples/caninterrupt @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=arduino-nano33 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=arduino-mkrwifi1010 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pico examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=nano-33-ble examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=qtpy-rp2040 examples/echo @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=kb2040 examples/echo @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=macropad-rp2040 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=badger2040 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=badger2040-w examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=tufty2040 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=thingplus-rp2040 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=xiao-rp2040 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=waveshare-rp2040-zero examples/echo @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=challenger-rp2040 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=trinkey-qt2040 examples/temp @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=gopher-badge examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=gopher-arcade examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=ae-rp2040 examples/echo @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=thumby examples/echo @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pico2 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=tiny2350 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pico-plus2 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=metro-rp2350 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=waveshare-rp2040-tiny examples/echo @$(MD5SUM) test.hex # test pwm $(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/pwm @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=feather-m4 examples/pwm @$(MD5SUM) test.hex # test usb $(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/hid-keyboard @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/hid-keyboard @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/usb-midi @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pico examples/usb-storage @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pico2 examples/usb-storage @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=nrf52840-s140v6-uf2-generic examples/machinetest @$(MD5SUM) test.hex ifneq ($(STM32), 0) $(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=feather-stm32f405 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=lgt92 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=nucleo-f103rb examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=nucleo-f722ze examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=nucleo-l031k6 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=nucleo-l432kc examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=nucleo-l476rg examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=nucleo-l552ze examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=nucleo-wl55jc examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky2 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/pwm @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=stm32f469disco examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=lorae5 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=swan examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=mksnanov3 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=stm32l0x1 examples/serial @$(MD5SUM) test.hex endif $(TINYGO) build -size short -o test.hex -target=atmega328pb examples/blinkm @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=atmega1284p examples/machinetest @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=arduino-leonardo examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=arduino examples/pwm @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=arduino-mega1280 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=arduino-mega1280 examples/pwm @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=attiny1616 examples/empty @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=digispark -gc=leaking examples/blinky1 @$(MD5SUM) test.hex ifneq ($(XTENSA), 0) $(TINYGO) build -size short -o test.bin -target=esp32-mini32 examples/blinky1 @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/blinky1 @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1 @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target m5stack-core2 examples/machinetest @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target m5stack examples/machinetest @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target m5stick-c examples/machinetest @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target m5paper examples/machinetest @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target mch2022 examples/machinetest @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/blinky1 @$(MD5SUM) test.bin endif $(TINYGO) build -size short -o test.bin -target=esp-c3-32s-kit examples/blinky1 @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=qtpy-esp32c3 examples/machinetest @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=m5stamp-c3 examples/machinetest @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=xiao-esp32c3 examples/machinetest @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=esp32-c3-devkit-rust-1 examples/blinky1 @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=esp32c3-12f examples/blinky1 @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=makerfabs-esp32c3spi35 examples/machinetest @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=maixbit examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=tkey examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=elecrow-rp2040 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=elecrow-rp2350 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=hw-651 examples/machinetest @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=hw-651-s110v8 examples/machinetest @$(MD5SUM) test.hex ifneq ($(WASM), 0) $(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/export $(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/main $(TINYGO) build -size short -o wasm.wasm -target=wasm-unknown examples/hello-wasm-unknown endif # test various compiler flags $(TINYGO) build -size short -o test.hex -target=pca10040 -gc=none -scheduler=none examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 -opt=1 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 -serial=none examples/echo @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 -serial=rtt examples/echo @$(MD5SUM) test.hex $(TINYGO) build -o test.nro -target=nintendoswitch examples/echo2 @$(MD5SUM) test.nro $(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go @$(MD5SUM) test.hex GOOS=linux GOARCH=arm $(TINYGO) build -size short -o test.elf ./testdata/cgo GOOS=linux GOARCH=mips $(TINYGO) build -size short -o test.elf ./testdata/cgo GOOS=windows GOARCH=amd64 $(TINYGO) build -size short -o test.exe ./testdata/cgo GOOS=windows GOARCH=arm64 $(TINYGO) build -size short -o test.exe ./testdata/cgo GOOS=darwin GOARCH=amd64 $(TINYGO) build -size short -o test ./testdata/cgo GOOS=darwin GOARCH=arm64 $(TINYGO) build -size short -o test ./testdata/cgo ifneq ($(OS),Windows_NT) # TODO: this does not yet work on Windows. Somehow, unused functions are # not garbage collected. $(TINYGO) build -o test.elf -gc=leaking -scheduler=none examples/serial endif wasmtest: cd ./tests/wasm && $(GO) test . build/release: tinygo gen-device $(if $(filter 1,$(USE_SYSTEM_BINARYEN)),,binaryen) @mkdir -p build/release/tinygo/bin @mkdir -p build/release/tinygo/lib/bdwgc @mkdir -p build/release/tinygo/lib/clang/include @mkdir -p build/release/tinygo/lib/CMSIS/CMSIS @mkdir -p build/release/tinygo/lib/macos-minimal-sdk @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/crt @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/math @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults @mkdir -p build/release/tinygo/lib/musl/arch @mkdir -p build/release/tinygo/lib/musl/crt @mkdir -p build/release/tinygo/lib/musl/src @mkdir -p build/release/tinygo/lib/nrfx @mkdir -p build/release/tinygo/lib/picolibc/newlib/libc @mkdir -p build/release/tinygo/lib/picolibc/newlib/libm @mkdir -p build/release/tinygo/lib/wasi-libc/dlmalloc @mkdir -p build/release/tinygo/lib/wasi-libc/libc-bottom-half @mkdir -p build/release/tinygo/lib/wasi-libc/libc-top-half/musl/arch @mkdir -p build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @mkdir -p build/release/tinygo/lib/wasi-cli/ @echo copying source files @cp -p build/tinygo$(EXE) build/release/tinygo/bin ifneq ($(USE_SYSTEM_BINARYEN),1) @cp -p build/wasm-opt$(EXE) build/release/tinygo/bin endif @cp -rp lib/bdwgc/* build/release/tinygo/lib/bdwgc @cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include @cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS @cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS @cp -rp lib/macos-minimal-sdk/* build/release/tinygo/lib/macos-minimal-sdk @cp -rp lib/musl/arch/aarch64 build/release/tinygo/lib/musl/arch @cp -rp lib/musl/arch/arm build/release/tinygo/lib/musl/arch @cp -rp lib/musl/arch/generic build/release/tinygo/lib/musl/arch @cp -rp lib/musl/arch/i386 build/release/tinygo/lib/musl/arch @cp -rp lib/musl/arch/mips build/release/tinygo/lib/musl/arch @cp -rp lib/musl/arch/x86_64 build/release/tinygo/lib/musl/arch @cp -rp lib/musl/crt/crt1.c build/release/tinygo/lib/musl/crt @cp -rp lib/musl/COPYRIGHT build/release/tinygo/lib/musl @cp -rp lib/musl/include build/release/tinygo/lib/musl @cp -rp lib/musl/src/conf build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/ctype build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/env build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/errno build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/exit build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/fcntl build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/include build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/internal build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/legacy build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/locale build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/linux build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/malloc build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/mman build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/math build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/misc build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/multibyte build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/sched build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/signal build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/stdio build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/stdlib build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/string build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/thread build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/time build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/unistd build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/process build/release/tinygo/lib/musl/src @cp -rp lib/mingw-w64/mingw-w64-crt/crt/pseudo-reloc.c build/release/tinygo/lib/mingw-w64/mingw-w64-crt/crt @cp -rp lib/mingw-w64/mingw-w64-crt/def-include build/release/tinygo/lib/mingw-w64/mingw-w64-crt @cp -rp lib/mingw-w64/mingw-w64-crt/gdtoa build/release/tinygo/lib/mingw-w64/mingw-w64-crt @cp -rp lib/mingw-w64/mingw-w64-crt/include build/release/tinygo/lib/mingw-w64/mingw-w64-crt @cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/api-ms-win-crt-* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common @cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/advapi32.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common @cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/kernel32.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common @cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/msvcrt.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common @cp -rp lib/mingw-w64/mingw-w64-crt/math/x86 build/release/tinygo/lib/mingw-w64/mingw-w64-crt/math @cp -rp lib/mingw-w64/mingw-w64-crt/misc build/release/tinygo/lib/mingw-w64/mingw-w64-crt @cp -rp lib/mingw-w64/mingw-w64-crt/stdio build/release/tinygo/lib/mingw-w64/mingw-w64-crt @cp -rp lib/mingw-w64/mingw-w64-headers/crt/ build/release/tinygo/lib/mingw-w64/mingw-w64-headers @cp -rp lib/mingw-w64/mingw-w64-headers/defaults/include build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults @cp -rp lib/mingw-w64/mingw-w64-headers/include build/release/tinygo/lib/mingw-w64/mingw-w64-headers @cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx @cp -rp lib/picolibc/newlib/libc/ctype build/release/tinygo/lib/picolibc/newlib/libc @cp -rp lib/picolibc/newlib/libc/include build/release/tinygo/lib/picolibc/newlib/libc @cp -rp lib/picolibc/newlib/libc/locale build/release/tinygo/lib/picolibc/newlib/libc @cp -rp lib/picolibc/newlib/libc/string build/release/tinygo/lib/picolibc/newlib/libc @cp -rp lib/picolibc/newlib/libc/tinystdio build/release/tinygo/lib/picolibc/newlib/libc @cp -rp lib/picolibc/newlib/libm/common build/release/tinygo/lib/picolibc/newlib/libm @cp -rp lib/picolibc/newlib/libm/math build/release/tinygo/lib/picolibc/newlib/libm @cp -rp lib/picolibc-stdio.c build/release/tinygo/lib @cp -rp lib/wasi-libc/dlmalloc/src build/release/tinygo/lib/wasi-libc/dlmalloc @cp -rp lib/wasi-libc/libc-bottom-half/cloudlibc build/release/tinygo/lib/wasi-libc/libc-bottom-half @cp -rp lib/wasi-libc/libc-bottom-half/headers build/release/tinygo/lib/wasi-libc/libc-bottom-half @cp -rp lib/wasi-libc/libc-bottom-half/sources build/release/tinygo/lib/wasi-libc/libc-bottom-half @cp -rp lib/wasi-libc/libc-top-half/headers build/release/tinygo/lib/wasi-libc/libc-top-half @cp -rp lib/wasi-libc/libc-top-half/musl/arch/generic build/release/tinygo/lib/wasi-libc/libc-top-half/musl/arch @cp -rp lib/wasi-libc/libc-top-half/musl/arch/wasm32 build/release/tinygo/lib/wasi-libc/libc-top-half/musl/arch @cp -rp lib/wasi-libc/libc-top-half/musl/include build/release/tinygo/lib/wasi-libc/libc-top-half/musl @cp -rp lib/wasi-libc/libc-top-half/musl/src/conf build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/dirent build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/env build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/errno build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/exit build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/fcntl build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/fenv build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/include build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/internal build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/legacy build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/locale build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/math build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/misc build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/multibyte build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/network build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/stat build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/stdio build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/stdlib build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/string build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/thread build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/time build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/unistd build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/sources build/release/tinygo/lib/wasi-libc/libc-top-half @cp -rp lib/wasi-cli/wit build/release/tinygo/lib/wasi-cli/wit @cp -rp llvm-project/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt-builtins @cp -rp llvm-project/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt-builtins @cp -rp src build/release/tinygo/src @cp -rp targets build/release/tinygo/targets release: tar -czf build/release.tar.gz -C build/release tinygo DEB_ARCH ?= native deb: @mkdir -p build/release-deb/usr/local/bin @mkdir -p build/release-deb/usr/local/lib cp -ar build/release/tinygo build/release-deb/usr/local/lib/tinygo ln -sf ../lib/tinygo/bin/tinygo build/release-deb/usr/local/bin/tinygo fpm -f -s dir -t deb -n tinygo -a $(DEB_ARCH) -v $(shell grep "const version = " goenv/version.go | awk '{print $$NF}') -m '@tinygo-org' --description='TinyGo is a Go compiler for small places.' --license='BSD 3-Clause' --url=https://tinygo.org/ --deb-changelog CHANGELOG.md -p build/release.deb -C ./build/release-deb ifneq ($(RELEASEONLY), 1) release: build/release deb: build/release endif .PHONY: tools tools: cd internal/tools && go generate -tags tools ./ LINTDIRS=src/os/ src/reflect/ .PHONY: lint lint: tools ## Lint source tree revive -version # TODO: lint more directories! # revive.toml isn't flexible enough to filter out just one kind of error from a checker, so do it with grep here. # Can't use grep with friendly formatter. Plain output isn't too bad, though. # Use 'grep .' to get rid of stray blank line revive -config revive.toml compiler/... $$( find $(LINTDIRS) -type f -name '*.go' ) \ | grep -v "should have comment or be unexported" \ | grep '.' \ | awk '{print}; END {exit NR>0}' SPELLDIRSCMD=find . -depth 1 -type d | egrep -wv '.git|lib|llvm|src'; find src -depth 1 | egrep -wv 'device|internal|net|vendor'; find src/internal -depth 1 -type d | egrep -wv src/internal/wasi .PHONY: spell spell: tools ## Spellcheck source tree misspell -error --dict misspell.csv -i 'ackward,devided,extint,rela' $$( $(SPELLDIRSCMD) ) *.go *.md .PHONY: spellfix spellfix: tools ## Same as spell, but fixes what it finds misspell -w --dict misspell.csv -i 'ackward,devided,extint,rela' $$( $(SPELLDIRSCMD) ) *.go *.md # https://www.client9.com/self-documenting-makefiles/ .PHONY: help help: @awk -F ':|##' '/^[^\t].+?:.*?##/ {\ gsub(/\$$\(LLVM_BUILDDIR\)/, "$(LLVM_BUILDDIR)"); \ printf "\033[36m%-30s\033[0m %s\n", $$1, $$NF \ }' $(MAKEFILE_LIST) #.DEFAULT_GOAL=help ================================================ FILE: GOVERNANCE.md ================================================ TinyGo Team Members =================== The team of humans who maintain TinyGo. * **Purpose**: To maintain the community, code, documentation, and tools for the TinyGo compiler. * **Board**: The group of people who share responsibility for key decisions for the TinyGo organization. * **Majority Voting**: The board makes decisions by majority vote. * **Membership**: The board elects its own members. * **Do-ocracy**: Those who step forward to do a given task propose how it should be done. Then other interested people can make comments. * **Proof of Work**: Power in decision-making is slightly weighted based on a participant's labor for the community. * **Initiation**: We need to establish a procedure for how people join the team of maintainers. * **Transparency**: Important information should be made publicly available, ideally in a way that allows for public comment. * **Code of Conduct**: Participants agree to abide by the current project Code of Conduct. ## Members * Ayke van Laethem (@aykevl) * Daniel Esteban (@conejoninja) * Ron Evans (@deadprogram) * Damian Gryski (@dgryski) * Masaaki Takasago (@sago35) * Patricio Whittingslow (@soypat) * Yurii Soldak (@ysoldak) ## Experimental * **Monthly Meeting**: A monthly meeting for the team and any other interested participants. Duration: 1 hour Facilitation: @deadprogram Schedule: See https://github.com/tinygo-org/tinygo/wiki/Meetings for more information ================================================ FILE: LICENSE ================================================ Copyright (c) 2018-2025 The TinyGo Authors. All rights reserved. TinyGo includes portions of the Go standard library. Copyright (c) 2009-2024 The Go Authors. All rights reserved. TinyGo includes portions of LLVM, which is under the Apache License v2.0 with LLVM Exceptions. See https://llvm.org/LICENSE.txt for license information. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: README.md ================================================ # TinyGo - Go compiler for small places [![Linux](https://github.com/tinygo-org/tinygo/actions/workflows/linux.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/linux.yml) [![macOS](https://github.com/tinygo-org/tinygo/actions/workflows/build-macos.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/build-macos.yml) [![Windows](https://github.com/tinygo-org/tinygo/actions/workflows/windows.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/windows.yml) [![Docker](https://github.com/tinygo-org/tinygo/actions/workflows/docker.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/docker.yml) [![Nix](https://github.com/tinygo-org/tinygo/actions/workflows/nix.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/nix.yml) [![CircleCI](https://circleci.com/gh/tinygo-org/tinygo/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/tinygo/tree/dev) TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (wasm/wasi), and command-line tools. It reuses libraries used by the [Go language tools](https://golang.org/pkg/go/) alongside [LLVM](http://llvm.org) to provide an alternative way to compile programs written in the Go programming language. ## Embedded Here is an example program that blinks the built-in LED when run directly on any supported board with onboard LED: ```go package main import ( "machine" "time" ) func main() { led := machine.LED led.Configure(machine.PinConfig{Mode: machine.PinOutput}) for { led.Low() time.Sleep(time.Millisecond * 1000) led.High() time.Sleep(time.Millisecond * 1000) } } ``` The above program can be compiled and run without modification on an Arduino Uno, an Adafruit ItsyBitsy M0, or any of the supported boards that have a built-in LED, just by setting the correct TinyGo compiler target. For example, this compiles and flashes an Arduino Uno: ```shell tinygo flash -target arduino examples/blinky1 ``` ## WebAssembly TinyGo is very useful for compiling programs both for use in browsers (WASM) as well as for use on servers and other edge devices (WASI). TinyGo programs can run in [Fastly Compute](https://www.fastly.com/documentation/guides/compute/go/), [Fermyon Spin](https://developer.fermyon.com/spin/go-components), [wazero](https://wazero.io/languages/tinygo/) and many other WebAssembly runtimes. Here is a small TinyGo program for use by a WASI host application: ```go package main //go:wasmexport add func add(x, y uint32) uint32 { return x + y } ``` This compiles the above TinyGo program for use on any WASI Preview 1 runtime: ```shell tinygo build -buildmode=c-shared -o add.wasm -target=wasip1 add.go ``` You can also use the same syntax as Go 1.24+: ```shell GOARCH=wasip1 GOOS=wasm tinygo build -buildmode=c-shared -o add.wasm add.go ``` ## Installation See the [getting started instructions](https://tinygo.org/getting-started/) for information on how to install TinyGo, as well as how to run the TinyGo compiler using our Docker container. ## Supported targets ### Embedded You can compile TinyGo programs for over 94 different microcontroller boards. For more information, please see https://tinygo.org/docs/reference/microcontrollers/ ### WebAssembly TinyGo programs can be compiled for both WASM and WASI targets. For more information, see https://tinygo.org/docs/guides/webassembly/ ### Operating Systems You can also compile programs for Linux, macOS, and Windows targets. For more information: - Linux https://tinygo.org/docs/guides/linux/ - macOS https://tinygo.org/docs/guides/macos/ - Windows https://tinygo.org/docs/guides/windows/ ## Currently supported features: For a description of currently supported Go language features, please see [https://tinygo.org/lang-support/](https://tinygo.org/lang-support/). ## Documentation Documentation is located on our web site at [https://tinygo.org/](https://tinygo.org/). You can find the web site code at [https://github.com/tinygo-org/tinygo-site](https://github.com/tinygo-org/tinygo-site). ## Getting help If you're looking for a more interactive way to discuss TinyGo usage or development, we have a [#TinyGo channel](https://gophers.slack.com/messages/CDJD3SUP6/) on the [Gophers Slack](https://gophers.slack.com). If you need an invitation for the Gophers Slack, you can generate one here which should arrive fairly quickly (under 1 min): https://invite.slack.golangbridge.org ## Contributing Your contributions are welcome! Please take a look at our [Contributing](https://tinygo.org/docs/guides/contributing/) page on our web site for details. ## Project Scope Goals: * Have very small binary sizes. Don't pay for what you don't use. * Support for most common microcontroller boards. * Be usable on the web using WebAssembly. * Good CGo support, with no more overhead than a regular function call. * Support most standard library packages and compile most Go code without modification. Non-goals: * Be efficient while using zillions of goroutines. However, good goroutine support is certainly a goal. * Be as fast as `gc`. However, LLVM will probably be better at optimizing certain things so TinyGo might actually turn out to be faster for number crunching. * Be able to compile every Go program out there. ## Why this project exists > We never expected Go to be an embedded language and so its got serious problems... -- Rob Pike, [GopherCon 2014 Opening Keynote](https://www.youtube.com/watch?v=VoS7DsT1rdM&feature=youtu.be&t=2799) TinyGo is a project to bring Go to microcontrollers and small systems with a single processor core. It is similar to [emgo](https://github.com/ziutek/emgo) but a major difference is that we want to keep the Go memory model (which implies garbage collection of some sort). Another difference is that TinyGo uses LLVM internally instead of emitting C, which hopefully leads to smaller and more efficient code and certainly leads to more flexibility. The original reasoning was: if [Python](https://micropython.org/) can run on microcontrollers, then certainly [Go](https://golang.org/) should be able to run on even lower level micros. ## License This project is licensed under the BSD 3-clause license, just like the [Go project](https://golang.org/LICENSE) itself. Some code has been copied from the LLVM project and is therefore licensed under [a variant of the Apache 2.0 license](http://releases.llvm.org/11.0.0/LICENSE.TXT). This has been clearly indicated in the header of these files. Some code has been copied and/or ported from Paul Stoffregen's Teensy libraries and is therefore licensed under PJRC's license. This has been clearly indicated in the header of these files. ================================================ FILE: bin/.keep ================================================ ================================================ FILE: builder/ar.go ================================================ package builder import ( "bytes" "debug/elf" "debug/macho" "debug/pe" "encoding/binary" "errors" "fmt" "io" "os" "path/filepath" "time" wasm "github.com/aykevl/go-wasm" "github.com/blakesmith/ar" ) // makeArchive creates an archive for static linking from a list of object files // given as a parameter. It is equivalent to the following command: // // ar -rcs func makeArchive(arfile *os.File, objs []string) error { // Open the archive file. arwriter := ar.NewWriter(arfile) err := arwriter.WriteGlobalHeader() if err != nil { return &os.PathError{Op: "write ar header", Path: arfile.Name(), Err: err} } // Open all object files and read the symbols for the symbol table. symbolTable := []struct { name string // symbol name fileIndex int // index into objfiles }{} archiveOffsets := make([]int32, len(objs)) for i, objpath := range objs { objfile, err := os.Open(objpath) if err != nil { return err } // Read the symbols and add them to the symbol table. if dbg, err := elf.NewFile(objfile); err == nil { symbols, err := dbg.Symbols() if err != nil { return err } for _, symbol := range symbols { bind := elf.ST_BIND(symbol.Info) if bind != elf.STB_GLOBAL && bind != elf.STB_WEAK { // Don't include local symbols (STB_LOCAL). continue } if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC && elf.ST_TYPE(symbol.Info) != elf.STT_OBJECT { // Not a function. continue } // Include in archive. symbolTable = append(symbolTable, struct { name string fileIndex int }{symbol.Name, i}) } } else if dbg, err := macho.NewFile(objfile); err == nil { for _, symbol := range dbg.Symtab.Syms { // See mach-o/nlist.h if symbol.Type&0x0e != 0xe { // (symbol.Type & N_TYPE) != N_SECT continue // undefined symbol } if symbol.Type&0x01 == 0 { // (symbol.Type & N_EXT) == 0 continue // internal symbol (static, etc) } symbolTable = append(symbolTable, struct { name string fileIndex int }{symbol.Name, i}) } } else if dbg, err := pe.NewFile(objfile); err == nil { for _, symbol := range dbg.Symbols { if symbol.StorageClass != 2 { continue } if symbol.SectionNumber == 0 { continue } symbolTable = append(symbolTable, struct { name string fileIndex int }{symbol.Name, i}) } } else if dbg, err := wasm.Parse(objfile); err == nil { for _, s := range dbg.Sections { switch section := s.(type) { case *wasm.SectionLinking: for _, symbol := range section.Symbols { if symbol.Flags&wasm.LinkingSymbolFlagUndefined != 0 { // Don't list undefined functions. continue } if symbol.Flags&wasm.LinkingSymbolFlagBindingLocal != 0 { // Don't include local symbols. continue } if symbol.Kind != wasm.LinkingSymbolKindFunction && symbol.Kind != wasm.LinkingSymbolKindData { // Link functions and data symbols. // Some data symbols need to be included, such as // __log_data. continue } // Include in the archive. symbolTable = append(symbolTable, struct { name string fileIndex int }{symbol.Name, i}) } } } } else { return fmt.Errorf("failed to open file %s as WASM, ELF or PE/COFF: %w", objpath, err) } // Close file, to avoid issues with too many open files (especially on // MacOS X). objfile.Close() } // Create the symbol table buffer. // For some (sparse) details on the file format: // https://en.wikipedia.org/wiki/Ar_(Unix)#System_V_(or_GNU)_variant buf := &bytes.Buffer{} binary.Write(buf, binary.BigEndian, int32(len(symbolTable))) for range symbolTable { // This is a placeholder index, it will be updated after all files have // been written to the archive (see the end of this function). err = binary.Write(buf, binary.BigEndian, int32(0)) if err != nil { return err } } for _, sym := range symbolTable { _, err := buf.Write([]byte(sym.name + "\x00")) if err != nil { return err } } for buf.Len()%2 != 0 { // The symbol table must be aligned. // This appears to be required by lld. buf.WriteByte(0) } // Write the symbol table. err = arwriter.WriteHeader(&ar.Header{ Name: "/", ModTime: time.Unix(0, 0), Uid: 0, Gid: 0, Mode: 0, Size: int64(buf.Len()), }) if err != nil { return err } // Keep track of the start of the symbol table. symbolTableStart, err := arfile.Seek(0, io.SeekCurrent) if err != nil { return err } // Write symbol table contents. _, err = arfile.Write(buf.Bytes()) if err != nil { return err } // Add all object files to the archive. var copyBuf bytes.Buffer for i, objpath := range objs { objfile, err := os.Open(objpath) if err != nil { return err } defer objfile.Close() // Store the start index, for when we'll update the symbol table with // the correct file start indices. offset, err := arfile.Seek(0, io.SeekCurrent) if err != nil { return err } if int64(int32(offset)) != offset { return errors.New("large archives (4GB+) not supported: " + arfile.Name()) } archiveOffsets[i] = int32(offset) // Write the file header. st, err := objfile.Stat() if err != nil { return err } err = arwriter.WriteHeader(&ar.Header{ Name: filepath.Base(objfile.Name()), ModTime: time.Unix(0, 0), Uid: 0, Gid: 0, Mode: 0644, Size: st.Size(), }) if err != nil { return err } // Copy the file contents into the archive. // First load all contents into a buffer, then write it all in one go to // the archive file. This is a bit complicated, but is necessary because // io.Copy can't deal with files that are of an odd size. copyBuf.Reset() n, err := io.Copy(©Buf, objfile) if err != nil { return fmt.Errorf("could not copy object file into ar file: %w", err) } if n != st.Size() { return errors.New("file modified during ar creation: " + arfile.Name()) } _, err = arwriter.Write(copyBuf.Bytes()) if err != nil { return fmt.Errorf("could not copy object file into ar file: %w", err) } // File is not needed anymore. objfile.Close() } // Create symbol indices. indicesBuf := &bytes.Buffer{} for _, sym := range symbolTable { err = binary.Write(indicesBuf, binary.BigEndian, archiveOffsets[sym.fileIndex]) if err != nil { return err } } // Overwrite placeholder indices. _, err = arfile.WriteAt(indicesBuf.Bytes(), symbolTableStart+4) return err } ================================================ FILE: builder/bdwgc.go ================================================ package builder // The well-known conservative Boehm-Demers-Weiser GC. // This file provides a way to compile this GC for use with TinyGo. import ( "path/filepath" "strings" "github.com/tinygo-org/tinygo/goenv" ) var BoehmGC = Library{ name: "bdwgc", cflags: func(target, headerPath string) []string { libdir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/bdwgc") flags := []string{ // use a modern environment "-DUSE_MMAP", // mmap is available "-DUSE_MUNMAP", // return memory to the OS using munmap "-DGC_BUILTIN_ATOMIC", // use compiler intrinsics for atomic operations "-DNO_EXECUTE_PERMISSION", // don't make the heap executable // specific flags for TinyGo "-DALL_INTERIOR_POINTERS", // scan interior pointers (needed for Go) "-DIGNORE_DYNAMIC_LOADING", // we don't support dynamic loading at the moment "-DNO_GETCONTEXT", // musl doesn't support getcontext() "-DGC_DISABLE_INCREMENTAL", // don't mess with SIGSEGV and such // Use a minimal environment. "-DNO_MSGBOX_ON_ERROR", // don't call MessageBoxA on Windows "-DDONT_USE_ATEXIT", "-DNO_GETENV", // smaller binary, more predictable configuration "-DNO_CLOCK", // don't use system clock "-DNO_DEBUGGING", // reduce code size "-DGC_NO_FINALIZATION", // finalization is not used at the moment // Special flag to work around the lack of __data_start in ld.lld. // TODO: try to fix this in LLVM/lld directly so we don't have to // work around it anymore. "-DGC_DONT_REGISTER_MAIN_STATIC_DATA", // Do not scan the stack. We have our own mechanism to do this. "-DSTACK_NOT_SCANNED", "-DNO_PROC_STAT", // we scan the stack manually (don't read /proc/self/stat on Linux) "-DSTACKBOTTOM=0", // dummy value, we scan the stack manually // Assertions can be enabled while debugging GC issues. //"-DGC_ASSERTIONS", // We use our own way of dealing with threads (that is a bit hacky). // See src/runtime/gc_boehm.go. //"-DGC_THREADS", //"-DTHREAD_LOCAL_ALLOC", "-I" + libdir + "/include", } return flags }, needsLibc: true, sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/bdwgc") }, librarySources: func(target string, _ bool) ([]string, error) { sources := []string{ "allchblk.c", "alloc.c", "blacklst.c", "dbg_mlc.c", "dyn_load.c", "headers.c", "mach_dep.c", "malloc.c", "mark.c", "mark_rts.c", "misc.c", "new_hblk.c", "os_dep.c", "reclaim.c", } if strings.Split(target, "-")[2] == "windows" { // Due to how the linker on Windows works (that doesn't allow // undefined functions), we need to include these extra files. sources = append(sources, "mallocx.c", "ptr_chck.c", ) } return sources, nil }, } ================================================ FILE: builder/build.go ================================================ // Package builder is the compiler driver of TinyGo. It takes in a package name // and an output path, and outputs an executable. It manages the entire // compilation pipeline in between. package builder import ( "crypto/sha256" "crypto/sha512" "debug/elf" "encoding/binary" "encoding/hex" "encoding/json" "errors" "fmt" "go/types" "hash/crc32" "math/bits" "os" "os/exec" "path/filepath" "runtime" "sort" "strconv" "strings" "github.com/gofrs/flock" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compiler" "github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/interp" "github.com/tinygo-org/tinygo/loader" "github.com/tinygo-org/tinygo/stacksize" "github.com/tinygo-org/tinygo/transform" "tinygo.org/x/go-llvm" ) // BuildResult is the output of a build. This includes the binary itself and // some other metadata that is obtained while building the binary. type BuildResult struct { // The executable directly from the linker, usually including debug // information. Used for GDB for example. Executable string // A path to the output binary. It is stored in the tmpdir directory of the // Build function, so if it should be kept it must be copied or moved away. // It is often the same as Executable, but differs if the output format is // .hex for example (instead of the usual ELF). Binary string // The directory of the main package. This is useful for testing as the test // binary must be run in the directory of the tested package. MainDir string // The root of the Go module tree. This is used for running tests in emulator // that restrict file system access to allow them to grant access to the entire // source tree they're likely to need to read testdata from. ModuleRoot string // ImportPath is the import path of the main package. This is useful for // correctly printing test results: the import path isn't always the same as // the path listed on the command line. ImportPath string // Map from path to package name. It is needed to attribute binary size to // the right Go package. PackagePathMap map[string]string } // packageAction is the struct that is serialized to JSON and hashed, to work as // a cache key of compiled packages. It should contain all the information that // goes into a compiled package to avoid using stale data. // // Right now it's still important to include a hash of every import, because a // dependency might have a public constant that this package uses and thus this // package will need to be recompiled if that constant changes. In the future, // the type data should be serialized to disk which can then be used as cache // key, avoiding the need for recompiling all dependencies when only the // implementation of an imported package changes. type packageAction struct { ImportPath string CompilerBuildID string TinyGoVersion string LLVMVersion string Config *compiler.Config CFlags []string FileHashes map[string]string // hash of every file that's part of the package EmbeddedFiles map[string]string // hash of all the //go:embed files in the package Imports map[string]string // map from imported package to action ID hash OptLevel string // LLVM optimization level (O0, O1, O2, Os, Oz) UndefinedGlobals []string // globals that are left as external globals (no initializer) } // Build performs a single package to executable Go build. It takes in a package // name, an output path, and set of compile options and from that it manages the // whole compilation process. // // The error value may be of type *MultiError. Callers will likely want to check // for this case and print such errors individually. func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildResult, error) { // Read the build ID of the tinygo binary. // Used as a cache key for package builds. compilerBuildID, err := ReadBuildID() if err != nil { return BuildResult{}, err } if config.Options.Work { fmt.Printf("WORK=%s\n", tmpdir) } // Look up the build cache directory, which is used to speed up incremental // builds. cacheDir := goenv.Get("GOCACHE") if cacheDir == "off" { // Use temporary build directory instead, effectively disabling the // build cache. cacheDir = tmpdir } // Create default global values. globalValues := map[string]map[string]string{ "runtime": { "buildVersion": goenv.Version(), }, "testing": {}, } if config.TestConfig.CompileTestBinary { // The testing.testBinary is set to "1" when in a test. // This is needed for testing.Testing() to work correctly. globalValues["testing"]["testBinary"] = "1" } // Copy over explicitly set global values, like // -ldflags="-X main.Version="1.0" for pkgPath, vals := range config.Options.GlobalValues { if _, ok := globalValues[pkgPath]; !ok { globalValues[pkgPath] = map[string]string{} } for k, v := range vals { globalValues[pkgPath][k] = v } } // Check for a libc dependency. // As a side effect, this also creates the headers for the given libc, if // the libc needs them. root := goenv.Get("TINYGOROOT") var libcDependencies []*compileJob switch config.Target.Libc { case "darwin-libSystem": libcJob := makeDarwinLibSystemJob(config, tmpdir) libcDependencies = append(libcDependencies, libcJob) case "musl": var unlock func() libcJob, unlock, err := libMusl.load(config, tmpdir) if err != nil { return BuildResult{}, err } defer unlock() libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(libcJob.result), "crt1.o"))) libcDependencies = append(libcDependencies, libcJob) case "picolibc": libcJob, unlock, err := libPicolibc.load(config, tmpdir) if err != nil { return BuildResult{}, err } defer unlock() libcDependencies = append(libcDependencies, libcJob) case "wasi-libc": libcJob, unlock, err := libWasiLibc.load(config, tmpdir) if err != nil { return BuildResult{}, err } defer unlock() libcDependencies = append(libcDependencies, libcJob) case "wasmbuiltins": libcJob, unlock, err := libWasmBuiltins.load(config, tmpdir) if err != nil { return BuildResult{}, err } defer unlock() libcDependencies = append(libcDependencies, libcJob) case "mingw-w64": libcJob, unlock, err := libMinGW.load(config, tmpdir) if err != nil { return BuildResult{}, err } defer unlock() libcDependencies = append(libcDependencies, libcJob) libcDependencies = append(libcDependencies, makeMinGWExtraLibs(tmpdir, config.GOARCH())...) case "": // no library specified, so nothing to do default: return BuildResult{}, fmt.Errorf("unknown libc: %s", config.Target.Libc) } optLevel, speedLevel, sizeLevel := config.OptLevel() compilerConfig := &compiler.Config{ Triple: config.Triple(), CPU: config.CPU(), Features: config.Features(), ABI: config.ABI(), GOOS: config.GOOS(), GOARCH: config.GOARCH(), BuildMode: config.BuildMode(), CodeModel: config.CodeModel(), RelocationModel: config.RelocationModel(), SizeLevel: sizeLevel, TinyGoVersion: goenv.Version(), Scheduler: config.Scheduler(), AutomaticStackSize: config.AutomaticStackSize(), DefaultStackSize: config.StackSize(), MaxStackAlloc: config.MaxStackAlloc(), NeedsStackObjects: config.NeedsStackObjects(), Debug: !config.Options.SkipDWARF, // emit DWARF except when -internal-nodwarf is passed Nobounds: config.Options.Nobounds, PanicStrategy: config.PanicStrategy(), } // Load the target machine, which is the LLVM object that contains all // details of a target (alignment restrictions, pointer size, default // address spaces, etc). machine, err := compiler.NewTargetMachine(compilerConfig) if err != nil { return BuildResult{}, err } defer machine.Dispose() // Load entire program AST into memory. lprogram, err := loader.Load(config, pkgName, types.Config{ Sizes: compiler.Sizes(machine), }) if err != nil { return BuildResult{}, err } result := BuildResult{ ModuleRoot: lprogram.MainPkg().Module.Dir, MainDir: lprogram.MainPkg().Dir, ImportPath: lprogram.MainPkg().ImportPath, } if result.ModuleRoot == "" { // If there is no module root, just the regular root. result.ModuleRoot = lprogram.MainPkg().Root } err = lprogram.Parse() if err != nil { return result, err } // Store which filesystem paths map to which package name. result.PackagePathMap = make(map[string]string, len(lprogram.Packages)) for _, pkg := range lprogram.Sorted() { result.PackagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path() } // Create the *ssa.Program. This does not yet build the entire SSA of the // program so it's pretty fast and doesn't need to be parallelized. program := lprogram.LoadSSA() // Add jobs to compile each package. // Packages that have a cache hit will not be compiled again. var packageJobs []*compileJob packageActionIDJobs := make(map[string]*compileJob) var embedFileObjects []*compileJob for _, pkg := range lprogram.Sorted() { pkg := pkg // necessary to avoid a race condition var undefinedGlobals []string for name := range globalValues[pkg.Pkg.Path()] { undefinedGlobals = append(undefinedGlobals, name) } sort.Strings(undefinedGlobals) // Make compile jobs to load files to be embedded in the output binary. var actionIDDependencies []*compileJob allFiles := map[string][]*loader.EmbedFile{} for _, files := range pkg.EmbedGlobals { for _, file := range files { allFiles[file.Name] = append(allFiles[file.Name], file) } } for name, files := range allFiles { name := name files := files job := &compileJob{ description: "make object file for " + name, run: func(job *compileJob) error { // Read the file contents in memory. path := filepath.Join(pkg.Dir, name) data, err := os.ReadFile(path) if err != nil { return err } // Hash the file. sum := sha256.Sum256(data) hexSum := hex.EncodeToString(sum[:16]) for _, file := range files { file.Size = uint64(len(data)) file.Hash = hexSum if file.NeedsData { file.Data = data } } job.result, err = createEmbedObjectFile(string(data), hexSum, name, pkg.OriginalDir(), tmpdir, compilerConfig) return err }, } actionIDDependencies = append(actionIDDependencies, job) embedFileObjects = append(embedFileObjects, job) } // Action ID jobs need to know the action ID of all the jobs the package // imports. var importedPackages []*compileJob for _, imported := range pkg.Pkg.Imports() { job, ok := packageActionIDJobs[imported.Path()] if !ok { return result, fmt.Errorf("package %s imports %s but couldn't find dependency", pkg.ImportPath, imported.Path()) } importedPackages = append(importedPackages, job) actionIDDependencies = append(actionIDDependencies, job) } // Create a job that will calculate the action ID for a package compile // job. The action ID is the cache key that is used for caching this // package. packageActionIDJob := &compileJob{ description: "calculate cache key for package " + pkg.ImportPath, dependencies: actionIDDependencies, run: func(job *compileJob) error { // Create a cache key: a hash from the action ID below that contains all // the parameters for the build. actionID := packageAction{ ImportPath: pkg.ImportPath, CompilerBuildID: string(compilerBuildID), LLVMVersion: llvm.Version, Config: compilerConfig, CFlags: pkg.CFlags, FileHashes: make(map[string]string, len(pkg.FileHashes)), EmbeddedFiles: make(map[string]string, len(allFiles)), Imports: make(map[string]string, len(pkg.Pkg.Imports())), OptLevel: optLevel, UndefinedGlobals: undefinedGlobals, } for filePath, hash := range pkg.FileHashes { actionID.FileHashes[filePath] = hex.EncodeToString(hash) } for name, files := range allFiles { actionID.EmbeddedFiles[name] = files[0].Hash } for i, imported := range pkg.Pkg.Imports() { actionID.Imports[imported.Path()] = importedPackages[i].result } buf, err := json.Marshal(actionID) if err != nil { return err // shouldn't happen } hash := sha512.Sum512_224(buf) job.result = hex.EncodeToString(hash[:]) return nil }, } packageActionIDJobs[pkg.ImportPath] = packageActionIDJob // Now create the job to actually build the package. It will exit early // if the package is already compiled. job := &compileJob{ description: "compile package " + pkg.ImportPath, dependencies: []*compileJob{packageActionIDJob}, run: func(job *compileJob) error { job.result = filepath.Join(cacheDir, "pkg-"+packageActionIDJob.result+".bc") // Acquire a lock (if supported). unlock := lock(job.result + ".lock") defer unlock() if _, err := os.Stat(job.result); err == nil { // Already cached, don't recreate this package. return nil } // Compile AST to IR. The compiler.CompilePackage function will // build the SSA as needed. mod, errs := compiler.CompilePackage(pkg.ImportPath, pkg, program.Package(pkg.Pkg), machine, compilerConfig, config.DumpSSA()) defer mod.Context().Dispose() defer mod.Dispose() if errs != nil { return newMultiError(errs, pkg.ImportPath) } if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { return errors.New("verification error after compiling package " + pkg.ImportPath) } // Load bitcode of CGo headers and join the modules together. // This may seem vulnerable to cache problems, but this is not // the case: the Go code that was just compiled already tracks // all C files that are read and hashes them. // These headers could be compiled in parallel but the benefit // is so small that it's probably not worth parallelizing. // Packages are compiled independently anyway. for _, cgoHeader := range pkg.CGoHeaders { // Store the header text in a temporary file. f, err := os.CreateTemp(tmpdir, "cgosnippet-*.c") if err != nil { return err } _, err = f.Write([]byte(cgoHeader)) if err != nil { return err } f.Close() // Compile the code (if there is any) to bitcode. flags := append([]string{"-c", "-emit-llvm", "-o", f.Name() + ".bc", f.Name()}, pkg.CFlags...) if config.Options.PrintCommands != nil { config.Options.PrintCommands("clang", flags...) } err = runCCompiler(flags...) if err != nil { return &commandError{"failed to build CGo header", "", err} } // Load and link the bitcode. // This makes it possible to optimize the functions defined // in the header together with the Go code. In particular, // this allows inlining. It also ensures there is only one // file per package to cache. headerMod, err := mod.Context().ParseBitcodeFile(f.Name() + ".bc") if err != nil { return fmt.Errorf("failed to load bitcode file: %w", err) } err = llvm.LinkModules(mod, headerMod) if err != nil { return fmt.Errorf("failed to link module: %w", err) } } // Erase all globals that are part of the undefinedGlobals list. // This list comes from the -ldflags="-X pkg.foo=val" option. // Instead of setting the value directly in the AST (which would // mean the value, which may be a secret, is stored in the build // cache), the global itself is left external (undefined) and is // only set at the end of the compilation. for _, name := range undefinedGlobals { globalName := pkg.Pkg.Path() + "." + name global := mod.NamedGlobal(globalName) if global.IsNil() { return errors.New("global not found: " + globalName) } globalType := global.GlobalValueType() if globalType.TypeKind() != llvm.StructTypeKind || globalType.StructName() != "runtime._string" { // Verify this is indeed a string. This is needed so // that makeGlobalsModule can just create the right // globals of string type without checking. return fmt.Errorf("%s: not a string", globalName) } name := global.Name() newGlobal := llvm.AddGlobal(mod, globalType, name+".tmp") global.ReplaceAllUsesWith(newGlobal) global.EraseFromParentAsGlobal() newGlobal.SetName(name) } // Try to interpret package initializers at compile time. // It may only be possible to do this partially, in which case // it is completed after all IR files are linked. pkgInit := mod.NamedFunction(pkg.Pkg.Path() + ".init") if pkgInit.IsNil() { panic("init not found for " + pkg.Pkg.Path()) } err := interp.RunFunc(pkgInit, config.Options.InterpTimeout, config.DumpSSA()) if err != nil { return err } if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { return errors.New("verification error after interpreting " + pkgInit.Name()) } transform.OptimizePackage(mod, config) // Serialize the LLVM module as a bitcode file. // Write to a temporary path that is renamed to the destination // file to avoid race conditions with other TinyGo invocatiosn // that might also be compiling this package at the same time. f, err := os.CreateTemp(filepath.Dir(job.result), filepath.Base(job.result)) if err != nil { return err } if runtime.GOOS == "windows" { // Work around a problem on Windows. // For some reason, WriteBitcodeToFile causes TinyGo to // exit with the following message: // LLVM ERROR: IO failure on output stream: Bad file descriptor buf := llvm.WriteBitcodeToMemoryBuffer(mod) defer buf.Dispose() _, err = f.Write(buf.Bytes()) } else { // Otherwise, write bitcode directly to the file (probably // faster). err = llvm.WriteBitcodeToFile(mod, f) } if err != nil { // WriteBitcodeToFile doesn't produce a useful error on its // own, so create a somewhat useful error message here. return fmt.Errorf("failed to write bitcode for package %s to file %s", pkg.ImportPath, job.result) } err = f.Close() if err != nil { return err } return os.Rename(f.Name(), job.result) }, } packageJobs = append(packageJobs, job) } // Add job that links and optimizes all packages together. var mod llvm.Module defer func() { if !mod.IsNil() { ctx := mod.Context() mod.Dispose() ctx.Dispose() } }() var stackSizeLoads []string programJob := &compileJob{ description: "link+optimize packages (LTO)", dependencies: packageJobs, run: func(*compileJob) error { // Load and link all the bitcode files. This does not yet optimize // anything, it only links the bitcode files together. ctx := llvm.NewContext() mod = ctx.NewModule("main") for _, pkgJob := range packageJobs { pkgMod, err := ctx.ParseBitcodeFile(pkgJob.result) if err != nil { return fmt.Errorf("failed to load bitcode file: %w", err) } err = llvm.LinkModules(mod, pkgMod) if err != nil { return fmt.Errorf("failed to link module: %w", err) } } // Insert values from -ldflags="-X ..." into the IR. // This is a separate module, so that the "runtime._string" type // doesn't need to match precisely. LLVM tends to rename that type // sometimes, leading to errors. But linking in a separate module // works fine. See: // https://github.com/tinygo-org/tinygo/issues/4810 globalsMod := makeGlobalsModule(ctx, globalValues, machine) llvm.LinkModules(mod, globalsMod) // Create runtime.initAll function that calls the runtime // initializer of each package. llvmInitFn := mod.NamedFunction("runtime.initAll") llvmInitFn.SetLinkage(llvm.InternalLinkage) llvmInitFn.SetUnnamedAddr(true) transform.AddStandardAttributes(llvmInitFn, config) llvmInitFn.Param(0).SetName("context") block := mod.Context().AddBasicBlock(llvmInitFn, "entry") irbuilder := mod.Context().NewBuilder() defer irbuilder.Dispose() irbuilder.SetInsertPointAtEnd(block) ptrType := llvm.PointerType(mod.Context().Int8Type(), 0) for _, pkg := range lprogram.Sorted() { pkgInit := mod.NamedFunction(pkg.Pkg.Path() + ".init") if pkgInit.IsNil() { panic("init not found for " + pkg.Pkg.Path()) } irbuilder.CreateCall(pkgInit.GlobalValueType(), pkgInit, []llvm.Value{llvm.Undef(ptrType)}, "") } irbuilder.CreateRetVoid() // After linking, functions should (as far as possible) be set to // private linkage or internal linkage. The compiler package marks // non-exported functions by setting the visibility to hidden or // (for thunks) to linkonce_odr linkage. Change the linkage here to // internal to benefit much more from interprocedural optimizations. for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { if fn.Visibility() == llvm.HiddenVisibility { fn.SetVisibility(llvm.DefaultVisibility) fn.SetLinkage(llvm.InternalLinkage) } else if fn.Linkage() == llvm.LinkOnceODRLinkage { fn.SetLinkage(llvm.InternalLinkage) } } // Do the same for globals. for global := mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) { if global.Visibility() == llvm.HiddenVisibility { global.SetVisibility(llvm.DefaultVisibility) global.SetLinkage(llvm.InternalLinkage) } else if global.Linkage() == llvm.LinkOnceODRLinkage { global.SetLinkage(llvm.InternalLinkage) } } if config.Options.PrintIR { fmt.Println("; Generated LLVM IR:") fmt.Println(mod.String()) } // Run all optimization passes, which are much more effective now // that the optimizer can see the whole program at once. err := optimizeProgram(mod, config) if err != nil { return err } // Make sure stack sizes are loaded from a separate section so they can be // modified after linking. if config.AutomaticStackSize() { stackSizeLoads = transform.CreateStackSizeLoads(mod, config) } return nil }, } // Create the output directory, if needed if err := os.MkdirAll(filepath.Dir(outpath), 0777); err != nil { return result, err } // Check whether we only need to create an object file. // If so, we don't need to link anything and will be finished quickly. outext := filepath.Ext(outpath) if outext == ".o" || outext == ".bc" || outext == ".ll" { // Run jobs to produce the LLVM module. err := runJobs(programJob, config.Options.Semaphore) if err != nil { return result, err } // Generate output. switch outext { case ".o": llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile) if err != nil { return result, err } defer llvmBuf.Dispose() return result, os.WriteFile(outpath, llvmBuf.Bytes(), 0666) case ".bc": buf := llvm.WriteThinLTOBitcodeToMemoryBuffer(mod) defer buf.Dispose() return result, os.WriteFile(outpath, buf.Bytes(), 0666) case ".ll": data := []byte(mod.String()) return result, os.WriteFile(outpath, data, 0666) default: panic("unreachable") } } // Act as a compiler driver, as we need to produce a complete executable. // First add all jobs necessary to build this object file, then afterwards // run all jobs in parallel as far as possible. // Add job to write the output object file. objfile := filepath.Join(tmpdir, "main.o") outputObjectFileJob := &compileJob{ description: "generate output file", dependencies: []*compileJob{programJob}, result: objfile, run: func(*compileJob) error { llvmBuf := llvm.WriteThinLTOBitcodeToMemoryBuffer(mod) defer llvmBuf.Dispose() return os.WriteFile(objfile, llvmBuf.Bytes(), 0666) }, } // Prepare link command. linkerDependencies := []*compileJob{outputObjectFileJob} result.Executable = filepath.Join(tmpdir, "main") if config.GOOS() == "windows" { result.Executable += ".exe" } result.Binary = result.Executable // final file ldflags := append(config.LDFlags(), "-o", result.Executable) if config.Options.BuildMode == "c-shared" { if !strings.HasPrefix(config.Triple(), "wasm32-") { return result, fmt.Errorf("buildmode c-shared is only supported on wasm at the moment") } ldflags = append(ldflags, "--no-entry") } if config.Options.BuildMode == "wasi-legacy" { if !strings.HasPrefix(config.Triple(), "wasm32-") { return result, fmt.Errorf("buildmode wasi-legacy is only supported on wasm") } if config.Options.Scheduler != "none" { return result, fmt.Errorf("buildmode wasi-legacy only supports scheduler=none") } } // Add compiler-rt dependency if needed. Usually this is a simple load from // a cache. if config.Target.RTLib == "compiler-rt" { job, unlock, err := libCompilerRT.load(config, tmpdir) if err != nil { return result, err } defer unlock() linkerDependencies = append(linkerDependencies, job) } // The Boehm collector is stored in a separate C library. if config.GC() == "boehm" { job, unlock, err := BoehmGC.load(config, tmpdir) if err != nil { return BuildResult{}, err } defer unlock() linkerDependencies = append(linkerDependencies, job) } // Add jobs to compile extra files. These files are in C or assembly and // contain things like the interrupt vector table and low level operations // such as stack switching. for _, path := range config.ExtraFiles() { abspath := filepath.Join(root, path) job := &compileJob{ description: "compile extra file " + path, run: func(job *compileJob) error { result, err := compileAndCacheCFile(abspath, tmpdir, config.CFlags(false), config.Options.PrintCommands) job.result = result return err }, } linkerDependencies = append(linkerDependencies, job) } // Add jobs to compile C files in all packages. This is part of CGo. // TODO: do this as part of building the package to be able to link the // bitcode files together. for _, pkg := range lprogram.Sorted() { pkg := pkg for _, filename := range pkg.CFiles { abspath := filepath.Join(pkg.OriginalDir(), filename) job := &compileJob{ description: "compile CGo file " + abspath, run: func(job *compileJob) error { result, err := compileAndCacheCFile(abspath, tmpdir, pkg.CFlags, config.Options.PrintCommands) job.result = result return err }, } linkerDependencies = append(linkerDependencies, job) } } // Linker flags from CGo lines: // #cgo LDFLAGS: foo if len(lprogram.LDFlags) > 0 { ldflags = append(ldflags, lprogram.LDFlags...) } // Add libc dependencies, if they exist. linkerDependencies = append(linkerDependencies, libcDependencies...) // Add embedded files. linkerDependencies = append(linkerDependencies, embedFileObjects...) // Determine whether the compilation configuration would result in debug // (DWARF) information in the object files. var hasDebug = true if config.GOOS() == "darwin" { // Debug information isn't stored in the binary itself on MacOS but // is left in the object files by default. The binary does store the // path to these object files though. hasDebug = false } // Strip debug information with -no-debug. if hasDebug && !config.Debug() { if config.Target.Linker == "wasm-ld" { // Don't just strip debug information, also compress relocations // while we're at it. Relocations can only be compressed when debug // information is stripped. ldflags = append(ldflags, "--strip-debug", "--compress-relocations") } else if config.Target.Linker == "ld.lld" { // ld.lld is also used on Linux. ldflags = append(ldflags, "--strip-debug") } else { // Other linkers may have different flags. return result, errors.New("cannot remove debug information: unknown linker: " + config.Target.Linker) } } // Create a linker job, which links all object files together and does some // extra stuff that can only be done after linking. linkJob := &compileJob{ description: "link", dependencies: linkerDependencies, run: func(job *compileJob) error { for _, dependency := range job.dependencies { if dependency.result == "" { return errors.New("dependency without result: " + dependency.description) } ldflags = append(ldflags, dependency.result) } ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU()) ldflags = append(ldflags, "-mllvm", "-mattr="+config.Features()) // needed for MIPS softfloat if config.GOOS() == "windows" { // Options for the MinGW wrapper for the lld COFF linker. ldflags = append(ldflags, "-Xlink=/opt:lldlto="+strconv.Itoa(speedLevel), "--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto")) } else if config.GOOS() == "darwin" { // Options for the ld64-compatible lld linker. ldflags = append(ldflags, "--lto-O"+strconv.Itoa(speedLevel), "-cache_path_lto", filepath.Join(cacheDir, "thinlto")) } else { // Options for the ELF linker. ldflags = append(ldflags, "--lto-O"+strconv.Itoa(speedLevel), "--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"), ) } if config.CodeModel() != "default" { ldflags = append(ldflags, "-mllvm", "-code-model="+config.CodeModel()) } if sizeLevel >= 2 { // Workaround with roughly the same effect as // https://reviews.llvm.org/D119342. // Can hopefully be removed in LLVM 19. ldflags = append(ldflags, "-mllvm", "--rotation-max-header-size=0") } if config.Options.PrintCommands != nil { config.Options.PrintCommands(config.Target.Linker, ldflags...) } err = link(config.Target.Linker, ldflags...) if err != nil { return err } var calculatedStacks []string var stackSizes map[string]functionStackSize if config.Options.PrintStacks || config.AutomaticStackSize() { // Try to determine stack sizes at compile time. // Don't do this by default as it usually doesn't work on // unsupported architectures. calculatedStacks, stackSizes, err = determineStackSizes(mod, result.Executable) if err != nil { return err } } // Apply ELF patches if config.AutomaticStackSize() { // Modify the .tinygo_stacksizes section that contains a stack size // for each goroutine. err = modifyStackSizes(result.Executable, stackSizeLoads, stackSizes) if err != nil { return fmt.Errorf("could not modify stack sizes: %w", err) } } // Apply patches of bootloader in the order they appear. if len(config.Target.BootPatches) > 0 { err = applyPatches(result.Executable, config.Target.BootPatches) } if config.RP2040BootPatch() { // Patch the second stage bootloader CRC into the .boot2 section err = patchRP2040BootCRC(result.Executable) if err != nil { return fmt.Errorf("could not patch RP2040 second stage boot loader: %w", err) } } // Run wasm-opt for wasm binaries if arch := strings.Split(config.Triple(), "-")[0]; arch == "wasm32" { optLevel, _, _ := config.OptLevel() opt := "-" + optLevel var args []string if config.Scheduler() == "asyncify" { args = append(args, "--asyncify") } inputFile := result.Binary result.Binary = result.Executable + ".wasmopt" args = append(args, opt, "-g", inputFile, "--output", result.Binary, ) wasmopt := goenv.Get("WASMOPT") if config.Options.PrintCommands != nil { config.Options.PrintCommands(wasmopt, args...) } cmd := exec.Command(wasmopt, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Run() if err != nil { return fmt.Errorf("wasm-opt failed: %w", err) } } // Run wasm-tools for component-model binaries witPackage := strings.ReplaceAll(config.Target.WITPackage, "{root}", goenv.Get("TINYGOROOT")) if config.Options.WITPackage != "" { witPackage = config.Options.WITPackage } witWorld := config.Target.WITWorld if config.Options.WITWorld != "" { witWorld = config.Options.WITWorld } if witPackage != "" && witWorld != "" { // wasm-tools component embed -w wasi:cli/command // $$(tinygo env TINYGOROOT)/lib/wasi-cli/wit/ main.wasm -o embedded.wasm componentEmbedInputFile := result.Binary result.Binary = result.Executable + ".wasm-component-embed" args := []string{ "component", "embed", "-w", witWorld, witPackage, componentEmbedInputFile, "-o", result.Binary, } wasmtools := goenv.Get("WASMTOOLS") if config.Options.PrintCommands != nil { config.Options.PrintCommands(wasmtools, args...) } cmd := exec.Command(wasmtools, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Run() if err != nil { return fmt.Errorf("`wasm-tools component embed` failed: %w", err) } // wasm-tools component new embedded.wasm -o component.wasm componentNewInputFile := result.Binary result.Binary = result.Executable + ".wasm-component-new" args = []string{ "component", "new", componentNewInputFile, "-o", result.Binary, } if config.Options.PrintCommands != nil { config.Options.PrintCommands(wasmtools, args...) } cmd = exec.Command(wasmtools, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err = cmd.Run() if err != nil { return fmt.Errorf("`wasm-tools component new` failed: %w", err) } } // Print code size if requested. if config.Options.PrintSizes != "" { sizes, err := loadProgramSize(result.Executable, result.PackagePathMap) if err != nil { return err } switch config.Options.PrintSizes { case "short": fmt.Printf(" code data bss | flash ram\n") fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code+sizes.ROData, sizes.Data, sizes.BSS, sizes.Flash(), sizes.RAM()) case "full": if !config.Debug() { fmt.Println("warning: data incomplete, remove the -no-debug flag for more detail") } fmt.Printf(" code rodata data bss | flash ram | package\n") fmt.Printf("------------------------------- | --------------- | -------\n") for _, name := range sizes.sortedPackageNames() { pkgSize := sizes.Packages[name] fmt.Printf("%7d %7d %7d %7d | %7d %7d | %s\n", pkgSize.Code, pkgSize.ROData, pkgSize.Data, pkgSize.BSS, pkgSize.Flash(), pkgSize.RAM(), name) } fmt.Printf("------------------------------- | --------------- | -------\n") fmt.Printf("%7d %7d %7d %7d | %7d %7d | total\n", sizes.Code, sizes.ROData, sizes.Data, sizes.BSS, sizes.Code+sizes.ROData+sizes.Data, sizes.Data+sizes.BSS) case "html": const filename = "size-report.html" err := writeSizeReport(sizes, filename, pkgName) if err != nil { return err } fmt.Println("Wrote size report to", filename) } } // Print goroutine stack sizes, as far as possible. if config.Options.PrintStacks { printStacks(calculatedStacks, stackSizes) } return nil }, } // Run all jobs to compile and link the program. // Do this now (instead of after elf-to-hex and similar conversions) as it // is simpler and cannot be parallelized. err = runJobs(linkJob, config.Options.Semaphore) if err != nil { return result, err } // Get an Intel .hex file or .bin file from the .elf file. outputBinaryFormat := config.BinaryFormat(outext) switch outputBinaryFormat { case "elf": // do nothing, file is already in ELF format case "hex", "bin": // Extract raw binary, either encoding it as a hex file or as a raw // firmware file. result.Binary = filepath.Join(tmpdir, "main"+outext) err := objcopy(result.Executable, result.Binary, outputBinaryFormat) if err != nil { return result, err } case "uf2": // Get UF2 from the .elf file. result.Binary = filepath.Join(tmpdir, "main"+outext) err := convertELFFileToUF2File(result.Executable, result.Binary, config.Target.UF2FamilyID) if err != nil { return result, err } case "esp32", "esp32-img", "esp32c3", "esp32s3", "esp8266": // Special format for the ESP family of chips (parsed by the ROM // bootloader). result.Binary = filepath.Join(tmpdir, "main"+outext) err := makeESPFirmwareImage(result.Executable, result.Binary, outputBinaryFormat) if err != nil { return result, err } case "nrf-dfu": // special format for nrfutil for Nordic chips result.Binary = filepath.Join(tmpdir, "main"+outext) err = makeDFUFirmwareImage(config.Options, result.Executable, result.Binary) if err != nil { return result, err } default: return result, fmt.Errorf("unknown output binary format: %s", outputBinaryFormat) } return result, nil } // createEmbedObjectFile creates a new object file with the given contents, for // the embed package. func createEmbedObjectFile(data, hexSum, sourceFile, sourceDir, tmpdir string, compilerConfig *compiler.Config) (string, error) { // TODO: this works for small files, but can be a problem for larger files. // For larger files, it seems more appropriate to generate the object file // manually without going through LLVM. // On the other hand, generating DWARF like we do here can be difficult // without assistance from LLVM. // Create new LLVM module just for this file. ctx := llvm.NewContext() defer ctx.Dispose() mod := ctx.NewModule("data") defer mod.Dispose() // Create data global. value := ctx.ConstString(data, false) globalName := "embed/file_" + hexSum global := llvm.AddGlobal(mod, value.Type(), globalName) global.SetInitializer(value) global.SetLinkage(llvm.LinkOnceODRLinkage) global.SetGlobalConstant(true) global.SetUnnamedAddr(true) global.SetAlignment(1) if compilerConfig.GOOS != "darwin" { // MachO doesn't support COMDATs, while COFF requires it (to avoid // "duplicate symbol" errors). ELF works either way. // Therefore, only use a COMDAT on non-MachO systems (aka non-MacOS). global.SetComdat(mod.Comdat(globalName)) } // Add DWARF debug information to this global, so that it is // correctly counted when compiling with the -size= flag. dibuilder := llvm.NewDIBuilder(mod) dibuilder.CreateCompileUnit(llvm.DICompileUnit{ Language: 0xb, // DW_LANG_C99 (0xc, off-by-one?) File: sourceFile, Dir: sourceDir, Producer: "TinyGo", Optimized: false, }) ditype := dibuilder.CreateArrayType(llvm.DIArrayType{ SizeInBits: uint64(len(data)) * 8, AlignInBits: 8, ElementType: dibuilder.CreateBasicType(llvm.DIBasicType{ Name: "byte", SizeInBits: 8, Encoding: llvm.DW_ATE_unsigned_char, }), Subscripts: []llvm.DISubrange{ { Lo: 0, Count: int64(len(data)), }, }, }) difile := dibuilder.CreateFile(sourceFile, sourceDir) diglobalexpr := dibuilder.CreateGlobalVariableExpression(difile, llvm.DIGlobalVariableExpression{ Name: globalName, File: difile, Line: 1, Type: ditype, Expr: dibuilder.CreateExpression(nil), AlignInBits: 8, }) global.AddMetadata(0, diglobalexpr) mod.AddNamedMetadataOperand("llvm.module.flags", ctx.MDNode([]llvm.Metadata{ llvm.ConstInt(ctx.Int32Type(), 2, false).ConstantAsMetadata(), // Warning on mismatch ctx.MDString("Debug Info Version"), llvm.ConstInt(ctx.Int32Type(), 3, false).ConstantAsMetadata(), }), ) mod.AddNamedMetadataOperand("llvm.module.flags", ctx.MDNode([]llvm.Metadata{ llvm.ConstInt(ctx.Int32Type(), 7, false).ConstantAsMetadata(), // Max on mismatch ctx.MDString("Dwarf Version"), llvm.ConstInt(ctx.Int32Type(), 4, false).ConstantAsMetadata(), }), ) dibuilder.Finalize() dibuilder.Destroy() // Write this LLVM module out as an object file. machine, err := compiler.NewTargetMachine(compilerConfig) if err != nil { return "", err } defer machine.Dispose() outfile, err := os.CreateTemp(tmpdir, "embed-"+hexSum+"-*.o") if err != nil { return "", err } defer outfile.Close() buf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile) if err != nil { return "", err } defer buf.Dispose() _, err = outfile.Write(buf.Bytes()) if err != nil { return "", err } return outfile.Name(), outfile.Close() } // optimizeProgram runs a series of optimizations and transformations that are // needed to convert a program to its final form. Some transformations are not // optional and must be run as the compiler expects them to run. func optimizeProgram(mod llvm.Module, config *compileopts.Config) error { err := interp.Run(mod, config.Options.InterpTimeout, config.DumpSSA()) if err != nil { return err } if config.VerifyIR() { // Only verify if we really need it. // The IR has already been verified before writing the bitcode to disk // and the interp function above doesn't need to do a lot as most of the // package initializers have already run. Additionally, verifying this // linked IR is _expensive_ because dead code hasn't been removed yet, // easily costing a few hundred milliseconds. Therefore, only do it when // specifically requested. if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { return errors.New("verification error after interpreting runtime.initAll") } } // Run most of the whole-program optimizations (including the whole // O0/O1/O2/Os/Oz optimization pipeline). errs := transform.Optimize(mod, config) if len(errs) > 0 { return newMultiError(errs, "") } if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { return errors.New("verification failure after LLVM optimization passes") } return nil } func makeGlobalsModule(ctx llvm.Context, globals map[string]map[string]string, machine llvm.TargetMachine) llvm.Module { mod := ctx.NewModule("cmdline-globals") targetData := machine.CreateTargetData() defer targetData.Dispose() mod.SetDataLayout(targetData.String()) stringType := ctx.StructCreateNamed("runtime._string") uintptrType := ctx.IntType(targetData.PointerSize() * 8) stringType.StructSetBody([]llvm.Type{ llvm.PointerType(ctx.Int8Type(), 0), uintptrType, }, false) var pkgPaths []string for pkgPath := range globals { pkgPaths = append(pkgPaths, pkgPath) } sort.Strings(pkgPaths) for _, pkgPath := range pkgPaths { pkg := globals[pkgPath] var names []string for name := range pkg { names = append(names, name) } sort.Strings(names) for _, name := range names { value := pkg[name] globalName := pkgPath + "." + name // Create a buffer for the string contents. bufInitializer := mod.Context().ConstString(value, false) buf := llvm.AddGlobal(mod, bufInitializer.Type(), ".string") buf.SetInitializer(bufInitializer) buf.SetAlignment(1) buf.SetUnnamedAddr(true) buf.SetLinkage(llvm.PrivateLinkage) // Create the string value, which is a {ptr, len} pair. length := llvm.ConstInt(uintptrType, uint64(len(value)), false) initializer := llvm.ConstNamedStruct(stringType, []llvm.Value{ buf, length, }) // Create the string global. global := llvm.AddGlobal(mod, stringType, globalName) global.SetInitializer(initializer) global.SetAlignment(targetData.PrefTypeAlignment(stringType)) } } return mod } // functionStackSizes keeps stack size information about a single function // (usually a goroutine). type functionStackSize struct { humanName string stackSize uint64 stackSizeType stacksize.SizeType missingStackSize *stacksize.CallNode } // determineStackSizes tries to determine the stack sizes of all started // goroutines and of the reset vector. The LLVM module is necessary to find // functions that call a function pointer. func determineStackSizes(mod llvm.Module, executable string) ([]string, map[string]functionStackSize, error) { var callsIndirectFunction []string gowrappers := []string{} gowrapperNames := make(map[string]string) for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { // Determine which functions call a function pointer. for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) { for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) { if inst.IsACallInst().IsNil() { continue } if callee := inst.CalledValue(); callee.IsAFunction().IsNil() && callee.IsAInlineAsm().IsNil() { callsIndirectFunction = append(callsIndirectFunction, fn.Name()) } } } // Get a list of "go wrappers", small wrapper functions that decode // parameters when starting a new goroutine. attr := fn.GetStringAttributeAtIndex(-1, "tinygo-gowrapper") if !attr.IsNil() { gowrappers = append(gowrappers, fn.Name()) gowrapperNames[fn.Name()] = attr.GetStringValue() } } sort.Strings(gowrappers) // Load the ELF binary. f, err := elf.Open(executable) if err != nil { return nil, nil, fmt.Errorf("could not load executable for stack size analysis: %w", err) } defer f.Close() // Determine the frame size of each function (if available) and the callgraph. functions, err := stacksize.CallGraph(f, callsIndirectFunction) if err != nil { return nil, nil, fmt.Errorf("could not parse executable for stack size analysis: %w", err) } // Goroutines need to be started and finished and take up some stack space // that way. This can be measured by measuring the stack size of // tinygo_startTask. if numFuncs := len(functions["tinygo_startTask"]); numFuncs != 1 { return nil, nil, fmt.Errorf("expected exactly one definition of tinygo_startTask, got %d", numFuncs) } baseStackSize, baseStackSizeType, baseStackSizeFailedAt := functions["tinygo_startTask"][0].StackSize() sizes := make(map[string]functionStackSize) // Add the reset handler function, for convenience. The reset handler runs // startup code and the scheduler. The listed stack size is not the full // stack size: interrupts are not counted. var resetFunction string switch f.Machine { case elf.EM_ARM: // Note: all interrupts happen on this stack so the real size is bigger. resetFunction = "Reset_Handler" } if resetFunction != "" { funcs := functions[resetFunction] if len(funcs) != 1 { return nil, nil, fmt.Errorf("expected exactly one definition of %s in the callgraph, found %d", resetFunction, len(funcs)) } stackSize, stackSizeType, missingStackSize := funcs[0].StackSize() sizes[resetFunction] = functionStackSize{ stackSize: stackSize, stackSizeType: stackSizeType, missingStackSize: missingStackSize, humanName: resetFunction, } } // Add all goroutine wrapper functions. for _, name := range gowrappers { funcs := functions[name] if len(funcs) != 1 { return nil, nil, fmt.Errorf("expected exactly one definition of %s in the callgraph, found %d", name, len(funcs)) } humanName := gowrapperNames[name] if humanName == "" { humanName = name // fallback } stackSize, stackSizeType, missingStackSize := funcs[0].StackSize() if baseStackSizeType != stacksize.Bounded { // It was not possible to determine the stack size at compile time // because tinygo_startTask does not have a fixed stack size. This // can happen when using -opt=1. stackSizeType = baseStackSizeType missingStackSize = baseStackSizeFailedAt } else if stackSize < baseStackSize { // This goroutine has a very small stack, but still needs to fit all // registers to start and suspend the goroutine. Otherwise a stack // overflow will occur even before the goroutine is started. stackSize = baseStackSize } sizes[name] = functionStackSize{ stackSize: stackSize, stackSizeType: stackSizeType, missingStackSize: missingStackSize, humanName: humanName, } } if resetFunction != "" { return append([]string{resetFunction}, gowrappers...), sizes, nil } return gowrappers, sizes, nil } // modifyStackSizes modifies the .tinygo_stacksizes section with the updated // stack size information. Before this modification, all stack sizes in the // section assume the default stack size (which is relatively big). func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map[string]functionStackSize) error { data, fileHeader, err := getElfSectionData(executable, ".tinygo_stacksizes") if err != nil { return err } if len(stackSizeLoads)*4 != len(data) { // Note: while AVR should use 2 byte stack sizes, even 64-bit platforms // should probably stick to 4 byte stack sizes as a larger than 4GB // stack doesn't make much sense. return errors.New("expected 4 byte stack sizes") } // Modify goroutine stack sizes with a compile-time known worst case stack // size. for i, name := range stackSizeLoads { fn, ok := stackSizes[name] if !ok { return fmt.Errorf("could not find symbol %s in ELF file", name) } if fn.stackSizeType == stacksize.Bounded { stackSize := uint32(fn.stackSize) // Add stack size used by interrupts. switch fileHeader.Machine { case elf.EM_ARM: if stackSize%8 != 0 { // If the stack isn't a multiple of 8, it means the leaf // function with the biggest stack depth doesn't have an aligned // stack. If the STKALIGN flag is set (which it is by default) // the interrupt controller will forcibly align the stack before // storing in-use registers. This will thus overwrite one word // past the end of the stack (off-by-one). stackSize += 4 } // On Cortex-M (assumed here), this stack size is 8 words or 32 // bytes. This is only to store the registers that the interrupt // may modify, the interrupt will switch to the interrupt stack // (MSP). // Some background: // https://interrupt.memfault.com/blog/cortex-m-rtos-context-switching stackSize += 32 // Adding 4 for the stack canary, and another 4 to keep the // stack aligned. Even though the size may be automatically // determined, stack overflow checking is still important as the // stack size cannot be determined for all goroutines. stackSize += 8 default: return fmt.Errorf("unknown architecture: %s", fileHeader.Machine.String()) } // Finally write the stack size to the binary. binary.LittleEndian.PutUint32(data[i*4:], stackSize) } } return replaceElfSection(executable, ".tinygo_stacksizes", data) } // printStacks prints the maximum stack depth for functions that are started as // goroutines. Stack sizes cannot always be determined statically, in particular // recursive functions and functions that call interface methods or function // pointers may have an unknown stack depth (depending on what the optimizer // manages to optimize away). // // It might print something like the following: // // function stack usage (in bytes) // Reset_Handler 316 // examples/blinky2.led1 92 // runtime.run$1 300 func printStacks(calculatedStacks []string, stackSizes map[string]functionStackSize) { // Print the sizes of all stacks. fmt.Printf("%-32s %s\n", "function", "stack usage (in bytes)") for _, name := range calculatedStacks { fn := stackSizes[name] switch fn.stackSizeType { case stacksize.Bounded: fmt.Printf("%-32s %d\n", fn.humanName, fn.stackSize) case stacksize.Unknown: fmt.Printf("%-32s unknown, %s does not have stack frame information\n", fn.humanName, fn.missingStackSize) case stacksize.Recursive: fmt.Printf("%-32s recursive, %s may call itself\n", fn.humanName, fn.missingStackSize) case stacksize.IndirectCall: fmt.Printf("%-32s unknown, %s calls a function pointer\n", fn.humanName, fn.missingStackSize) } } } func applyPatches(executable string, bootPatches []string) (err error) { for _, patch := range bootPatches { switch patch { case "rp2040": err = patchRP2040BootCRC(executable) // case "rp2350": // err = patchRP2350BootIMAGE_DEF(executable) default: err = errors.New("undefined boot patch name") } if err != nil { return fmt.Errorf("apply boot patch %q: %w", patch, err) } } return nil } // RP2040 second stage bootloader CRC32 calculation // // Spec: https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf // Section: 2.8.1.3.1. Checksum func patchRP2040BootCRC(executable string) error { bytes, _, err := getElfSectionData(executable, ".boot2") if err != nil { return err } if len(bytes) != 256 { return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes, got %d", len(bytes)) } // From the 'official' RP2040 checksum script: // // Our bootrom CRC32 is slightly bass-ackward but it's // best to work around for now (FIXME) // 100% worth it to save two Thumb instructions revBytes := make([]byte, len(bytes)) for i := range bytes { revBytes[i] = bits.Reverse8(bytes[i]) } // crc32.Update does an initial negate and negates the // result, so to meet RP2040 spec, pass 0x0 as initial // hash and negate returned value. // // Note: checksum is over 252 bytes (256 - 4) hash := bits.Reverse32(crc32.Update(0x0, crc32.IEEETable, revBytes[:252]) ^ 0xFFFFFFFF) // Write the CRC to the end of the bootloader. binary.LittleEndian.PutUint32(bytes[252:], hash) // Update the .boot2 section to included the CRC return replaceElfSection(executable, ".boot2", bytes) } // lock may acquire a lock at the specified path. // It returns a function to release the lock. // If flock is not supported, it does nothing. func lock(path string) func() { flock := flock.New(path) err := flock.Lock() if err != nil { return func() {} } return func() { flock.Close() } } func b2u8(b bool) uint8 { if b { return 1 } return 0 } ================================================ FILE: builder/builder_test.go ================================================ package builder import ( "fmt" "os" "path/filepath" "runtime" "testing" "github.com/tinygo-org/tinygo/compileopts" "tinygo.org/x/go-llvm" ) // Test whether the Clang generated "target-cpu" and "target-features" // attributes match the CPU and Features property in TinyGo target files. func TestClangAttributes(t *testing.T) { var targetNames = []string{ // Please keep this list sorted! "atmega328p", "atmega1280", "atmega1284p", "atmega2560", "attiny85", "cortex-m0", "cortex-m0plus", "cortex-m3", "cortex-m33", "cortex-m4", "cortex-m7", "esp32c3", "esp32s3", "fe310", "gameboy-advance", "k210", "nintendoswitch", "riscv-qemu", "tkey", "wasip1", "wasip2", "wasm", "wasm-unknown", } if hasBuiltinTools { // hasBuiltinTools is set when TinyGo is statically linked with LLVM, // which also implies it was built with Xtensa support. targetNames = append(targetNames, "esp32", "esp8266") } for _, targetName := range targetNames { targetName := targetName t.Run(targetName, func(t *testing.T) { testClangAttributes(t, &compileopts.Options{Target: targetName}) }) } for _, options := range []*compileopts.Options{ {GOOS: "linux", GOARCH: "386"}, {GOOS: "linux", GOARCH: "amd64"}, {GOOS: "linux", GOARCH: "arm", GOARM: "5,softfloat"}, {GOOS: "linux", GOARCH: "arm", GOARM: "6,softfloat"}, {GOOS: "linux", GOARCH: "arm", GOARM: "7,softfloat"}, {GOOS: "linux", GOARCH: "arm", GOARM: "5,hardfloat"}, {GOOS: "linux", GOARCH: "arm", GOARM: "6,hardfloat"}, {GOOS: "linux", GOARCH: "arm", GOARM: "7,hardfloat"}, {GOOS: "linux", GOARCH: "arm64"}, {GOOS: "linux", GOARCH: "mips", GOMIPS: "hardfloat"}, {GOOS: "linux", GOARCH: "mipsle", GOMIPS: "hardfloat"}, {GOOS: "linux", GOARCH: "mips", GOMIPS: "softfloat"}, {GOOS: "linux", GOARCH: "mipsle", GOMIPS: "softfloat"}, {GOOS: "darwin", GOARCH: "amd64"}, {GOOS: "darwin", GOARCH: "arm64"}, {GOOS: "windows", GOARCH: "386"}, {GOOS: "windows", GOARCH: "amd64"}, {GOOS: "windows", GOARCH: "arm64"}, } { name := "GOOS=" + options.GOOS + ",GOARCH=" + options.GOARCH if options.GOARCH == "arm" { name += ",GOARM=" + options.GOARM } if options.GOARCH == "mips" || options.GOARCH == "mipsle" { name += ",GOMIPS=" + options.GOMIPS } t.Run(name, func(t *testing.T) { testClangAttributes(t, options) }) } } func testClangAttributes(t *testing.T, options *compileopts.Options) { testDir := t.TempDir() ctx := llvm.NewContext() defer ctx.Dispose() target, err := compileopts.LoadTarget(options) if err != nil { t.Fatalf("could not load target: %s", err) } config := compileopts.Config{ Options: options, Target: target, } // Create a very simple C input file. srcpath := filepath.Join(testDir, "test.c") err = os.WriteFile(srcpath, []byte("int add(int a, int b) { return a + b; }"), 0o666) if err != nil { t.Fatalf("could not write target file %s: %s", srcpath, err) } // Compile this file using Clang. outpath := filepath.Join(testDir, "test.bc") flags := append([]string{"-c", "-emit-llvm", "-o", outpath, srcpath}, config.CFlags(false)...) if config.GOOS() == "darwin" { // Silence some warnings that happen when testing GOOS=darwin on // something other than MacOS. flags = append(flags, "-Wno-missing-sysroot", "-Wno-incompatible-sysroot") } err = runCCompiler(flags...) if err != nil { t.Fatalf("failed to compile %s: %s", srcpath, err) } // Read the resulting LLVM bitcode. mod, err := ctx.ParseBitcodeFile(outpath) if err != nil { t.Fatalf("could not parse bitcode file %s: %s", outpath, err) } defer mod.Dispose() // Check whether the LLVM target matches. if mod.Target() != config.Triple() { t.Errorf("target has LLVM triple %#v but Clang makes it LLVM triple %#v", config.Triple(), mod.Target()) } // Check the "target-cpu" and "target-features" string attribute of the add // function. add := mod.NamedFunction("add") var cpu, features string cpuAttr := add.GetStringAttributeAtIndex(-1, "target-cpu") featuresAttr := add.GetStringAttributeAtIndex(-1, "target-features") if !cpuAttr.IsNil() { cpu = cpuAttr.GetStringValue() } if !featuresAttr.IsNil() { features = featuresAttr.GetStringValue() } if cpu != config.CPU() { t.Errorf("target has CPU %#v but Clang makes it CPU %#v", config.CPU(), cpu) } if features != config.Features() { if hasBuiltinTools || runtime.GOOS != "linux" { // Skip this step when using an external Clang invocation on Linux. // The reason is that Debian has patched Clang in a way that // modifies the LLVM features string, changing lots of FPU/float // related flags. We want to test vanilla Clang, not Debian Clang. t.Errorf("target has LLVM features\n\t%#v\nbut Clang makes it\n\t%#v", config.Features(), features) } } } // This TestMain is necessary because TinyGo may also be invoked to run certain // LLVM tools in a separate process. Not capturing these invocations would lead // to recursive tests. func TestMain(m *testing.M) { if len(os.Args) >= 2 { switch os.Args[1] { case "clang", "ld.lld", "wasm-ld": // Invoke a specific tool. err := RunTool(os.Args[1], os.Args[2:]...) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } os.Exit(0) } } // Run normal tests. os.Exit(m.Run()) } ================================================ FILE: builder/buildid.go ================================================ package builder import ( "bytes" "debug/elf" "debug/macho" "encoding/binary" "fmt" "io" "os" "runtime" ) // ReadBuildID reads the build ID from the currently running executable. func ReadBuildID() ([]byte, error) { executable, err := os.Executable() if err != nil { return nil, err } f, err := os.Open(executable) if err != nil { return nil, err } defer f.Close() switch runtime.GOOS { case "linux", "freebsd", "android": // Read the GNU build id section. (Not sure about FreeBSD though...) file, err := elf.NewFile(f) if err != nil { return nil, err } var gnuID, goID []byte for _, section := range file.Sections { if section.Type != elf.SHT_NOTE || (section.Name != ".note.gnu.build-id" && section.Name != ".note.go.buildid") { continue } buf := make([]byte, section.Size) n, err := section.ReadAt(buf, 0) if uint64(n) != section.Size || err != nil { return nil, fmt.Errorf("could not read build id: %w", err) } if section.Name == ".note.gnu.build-id" { gnuID = buf } else { goID = buf } } if gnuID != nil { return gnuID, nil } else if goID != nil { return goID, nil } case "darwin": // Read the LC_UUID load command, which contains the equivalent of a // build ID. file, err := macho.NewFile(f) if err != nil { return nil, err } for _, load := range file.Loads { // Unfortunately, the debug/macho package doesn't support the // LC_UUID command directly. So we have to read it from // macho.LoadBytes. load, ok := load.(macho.LoadBytes) if !ok { continue } raw := load.Raw() command := binary.LittleEndian.Uint32(raw) if command != 0x1b { // Looking for the LC_UUID load command. // LC_UUID is defined here as 0x1b: // https://opensource.apple.com/source/xnu/xnu-4570.71.2/EXTERNAL_HEADERS/mach-o/loader.h.auto.html continue } return raw[4:], nil } // Normally we would have found a build ID by now. But not on Nix, // unfortunately, because Nix adds -no_uuid for some reason: // https://github.com/NixOS/nixpkgs/issues/178366 // Fall back to the same implementation that we use for Windows. id, err := readRawGoBuildID(f, 32*1024) if len(id) != 0 || err != nil { return id, err } default: // On other platforms (such as Windows) there isn't such a convenient // build ID. Luckily, Go does have an equivalent of the build ID, which // is stored as a special symbol named go.buildid. You can read it // using `go tool buildid`, but the code below extracts it directly // from the binary. // Unfortunately, because of stripping with the -w flag, no symbol // table might be available. Therefore, we have to scan the binary // directly. Luckily the build ID is always at the start of the file. // For details, see: // https://github.com/golang/go/blob/master/src/cmd/internal/buildid/buildid.go id, err := readRawGoBuildID(f, 4096) if len(id) != 0 || err != nil { return id, err } } return nil, fmt.Errorf("could not find build ID in %v", executable) } // The Go toolchain stores a build ID in the binary that we can use, as a // fallback if binary file specific build IDs can't be obtained. // This function reads that build ID from the binary. func readRawGoBuildID(f *os.File, prefixSize int) ([]byte, error) { fileStart := make([]byte, prefixSize) _, err := io.ReadFull(f, fileStart) if err != nil { return nil, fmt.Errorf("could not read build id from %s: %v", f.Name(), err) } index := bytes.Index(fileStart, []byte("\xff Go build ID: \"")) if index < 0 || index > len(fileStart)-103 { return nil, fmt.Errorf("could not find build id in %s", f.Name()) } buf := fileStart[index : index+103] if bytes.HasPrefix(buf, []byte("\xff Go build ID: \"")) && bytes.HasSuffix(buf, []byte("\"\n \xff")) { return buf[len("\xff Go build ID: \"") : len(buf)-1], nil } return nil, nil } ================================================ FILE: builder/builtins.go ================================================ package builder import ( "os" "path/filepath" "strings" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/goenv" ) // These are the GENERIC_SOURCES according to CMakeList.txt except for // divmodsi4.c and udivmodsi4.c. var genericBuiltins = []string{ "absvdi2.c", "absvsi2.c", "absvti2.c", "adddf3.c", "addsf3.c", "addvdi3.c", "addvsi3.c", "addvti3.c", "apple_versioning.c", "ashldi3.c", "ashlti3.c", "ashrdi3.c", "ashrti3.c", "bswapdi2.c", "bswapsi2.c", "clzdi2.c", "clzsi2.c", "clzti2.c", "cmpdi2.c", "cmpti2.c", "comparedf2.c", "comparesf2.c", "ctzdi2.c", "ctzsi2.c", "ctzti2.c", "divdc3.c", "divdf3.c", "divdi3.c", "divmoddi4.c", //"divmodsi4.c", "divmodti4.c", "divsc3.c", "divsf3.c", "divsi3.c", "divti3.c", "extendsfdf2.c", "extendhfsf2.c", "ffsdi2.c", "ffssi2.c", "ffsti2.c", "fixdfdi.c", "fixdfsi.c", "fixdfti.c", "fixsfdi.c", "fixsfsi.c", "fixsfti.c", "fixunsdfdi.c", "fixunsdfsi.c", "fixunsdfti.c", "fixunssfdi.c", "fixunssfsi.c", "fixunssfti.c", "floatdidf.c", "floatdisf.c", "floatsidf.c", "floatsisf.c", "floattidf.c", "floattisf.c", "floatundidf.c", "floatundisf.c", "floatunsidf.c", "floatunsisf.c", "floatuntidf.c", "floatuntisf.c", "fp_mode.c", //"int_util.c", "lshrdi3.c", "lshrti3.c", "moddi3.c", "modsi3.c", "modti3.c", "muldc3.c", "muldf3.c", "muldi3.c", "mulodi4.c", "mulosi4.c", "muloti4.c", "mulsc3.c", "mulsf3.c", "multi3.c", "mulvdi3.c", "mulvsi3.c", "mulvti3.c", "negdf2.c", "negdi2.c", "negsf2.c", "negti2.c", "negvdi2.c", "negvsi2.c", "negvti2.c", "os_version_check.c", "paritydi2.c", "paritysi2.c", "parityti2.c", "popcountdi2.c", "popcountsi2.c", "popcountti2.c", "powidf2.c", "powisf2.c", "subdf3.c", "subsf3.c", "subvdi3.c", "subvsi3.c", "subvti3.c", "trampoline_setup.c", "truncdfhf2.c", "truncdfsf2.c", "truncsfhf2.c", "ucmpdi2.c", "ucmpti2.c", "udivdi3.c", "udivmoddi4.c", //"udivmodsi4.c", "udivmodti4.c", "udivsi3.c", "udivti3.c", "umoddi3.c", "umodsi3.c", "umodti3.c", } // These are the GENERIC_TF_SOURCES as of LLVM 18. // They are not needed on all platforms (32-bit platforms usually don't need // these) but they seem to compile fine so it's easier to include them. var genericBuiltins128 = []string{ "addtf3.c", "comparetf2.c", "divtc3.c", "divtf3.c", "extenddftf2.c", "extendhftf2.c", "extendsftf2.c", "fixtfdi.c", "fixtfsi.c", "fixtfti.c", "fixunstfdi.c", "fixunstfsi.c", "fixunstfti.c", "floatditf.c", "floatsitf.c", "floattitf.c", "floatunditf.c", "floatunsitf.c", "floatuntitf.c", "multc3.c", "multf3.c", "powitf2.c", "subtf3.c", "trunctfdf2.c", "trunctfhf2.c", "trunctfsf2.c", } var aeabiBuiltins = []string{ "arm/aeabi_cdcmp.S", "arm/aeabi_cdcmpeq_check_nan.c", "arm/aeabi_cfcmp.S", "arm/aeabi_cfcmpeq_check_nan.c", "arm/aeabi_dcmp.S", "arm/aeabi_div0.c", "arm/aeabi_drsub.c", "arm/aeabi_fcmp.S", "arm/aeabi_frsub.c", "arm/aeabi_idivmod.S", "arm/aeabi_ldivmod.S", "arm/aeabi_memcmp.S", "arm/aeabi_memcpy.S", "arm/aeabi_memmove.S", "arm/aeabi_memset.S", "arm/aeabi_uidivmod.S", "arm/aeabi_uldivmod.S", // These two are not technically EABI builtins but are used by them and only // seem to be used on ARM. LLVM seems to use __divsi3 and __modsi3 on most // other architectures. // Most importantly, they have a different calling convention on AVR so // should not be used on AVR. "divmodsi4.c", "udivmodsi4.c", } var avrBuiltins = []string{ "avr/divmodhi4.S", "avr/divmodqi4.S", "avr/mulhi3.S", "avr/mulqi3.S", "avr/udivmodhi4.S", "avr/udivmodqi4.S", } // Builtins needed specifically for windows/386. var windowsI386Builtins = []string{ "i386/chkstk.S", // also _alloca } // libCompilerRT is a library with symbols required by programs compiled with // LLVM. These symbols are for operations that cannot be emitted with a single // instruction or a short sequence of instructions for that target. // // For more information, see: https://compiler-rt.llvm.org/ var libCompilerRT = Library{ name: "compiler-rt", cflags: func(target, headerPath string) []string { return []string{"-Werror", "-Wall", "-std=c11", "-nostdlibinc"} }, sourceDir: func() string { llvmDir := filepath.Join(goenv.Get("TINYGOROOT"), "llvm-project/compiler-rt/lib/builtins") if _, err := os.Stat(llvmDir); err == nil { // Release build. return llvmDir } // Development build. return filepath.Join(goenv.Get("TINYGOROOT"), "lib/compiler-rt-builtins") }, librarySources: func(target string, _ bool) ([]string, error) { builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins switch compileopts.CanonicalArchName(target) { case "arm": builtins = append(builtins, aeabiBuiltins...) case "avr": builtins = append(builtins, avrBuiltins...) case "x86_64", "aarch64", "riscv64": // any 64-bit arch builtins = append(builtins, genericBuiltins128...) case "i386": if strings.Split(target, "-")[2] == "windows" { builtins = append(builtins, windowsI386Builtins...) } } return builtins, nil }, } ================================================ FILE: builder/cc.go ================================================ package builder // This file implements a wrapper around the C compiler (Clang) which uses a // build cache. import ( "crypto/sha512" "encoding/hex" "encoding/json" "errors" "fmt" "io" "io/fs" "os" "path/filepath" "sort" "strings" "unicode" "github.com/tinygo-org/tinygo/goenv" "tinygo.org/x/go-llvm" ) // compileAndCacheCFile compiles a C or assembly file using a build cache. // Compiling the same file again (if nothing changed, including included header // files) the output is loaded from the build cache instead. // // Its operation is a bit complex (more complex than Go package build caching) // because the list of file dependencies is only known after the file is // compiled. However, luckily compilers have a flag to write a list of file // dependencies in Makefile syntax which can be used for caching. // // Because of this complexity, every file has in fact two cached build outputs: // the file itself, and the list of dependencies. Its operation is as follows: // // depfile = hash(path, compiler, cflags, ...) // if depfile exists: // outfile = hash of all files and depfile name // if outfile exists: // # cache hit // return outfile // # cache miss // tmpfile = compile file // read dependencies (side effect of compile) // write depfile // outfile = hash of all files and depfile name // rename tmpfile to outfile // // There are a few edge cases that are not handled: // - If a file is added to an include path, that file may be included instead of // some other file. This would be fixed by also including lookup failures in the // dependencies file, but I'm not aware of a compiler which does that. // - The Makefile syntax that compilers output has issues, see readDepFile for // details. // - A header file may be changed to add/remove an include. This invalidates the // depfile but without invalidating its name. For this reason, the depfile is // written on each new compilation (even when it seems unnecessary). However, it // could in rare cases lead to a stale file fetched from the cache. func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands func(string, ...string)) (string, error) { // Hash input file. fileHash, err := hashFile(abspath) if err != nil { return "", err } // Acquire a lock (if supported). unlock := lock(filepath.Join(goenv.Get("GOCACHE"), fileHash+".c.lock")) defer unlock() // Create cache key for the dependencies file. buf, err := json.Marshal(struct { Path string Hash string Flags []string LLVMVersion string }{ Path: abspath, Hash: fileHash, Flags: cflags, LLVMVersion: llvm.Version, }) if err != nil { panic(err) // shouldn't happen } depfileNameHashBuf := sha512.Sum512_224(buf) depfileNameHash := hex.EncodeToString(depfileNameHashBuf[:]) // Load dependencies file, if possible. depfileName := "dep-" + depfileNameHash + ".json" depfileCachePath := filepath.Join(goenv.Get("GOCACHE"), depfileName) depfileBuf, err := os.ReadFile(depfileCachePath) var dependencies []string // sorted list of dependency paths if err == nil { // There is a dependency file, that's great! // Parse it first. err := json.Unmarshal(depfileBuf, &dependencies) if err != nil { return "", fmt.Errorf("could not parse dependencies JSON: %w", err) } // Obtain hashes of all the files listed as a dependency. outpath, err := makeCFileCachePath(dependencies, depfileNameHash) if err == nil { if _, err := os.Stat(outpath); err == nil { return outpath, nil } else if !errors.Is(err, fs.ErrNotExist) { return "", err } } } else if !errors.Is(err, fs.ErrNotExist) { // expected either nil or IsNotExist return "", err } objTmpFile, err := os.CreateTemp(goenv.Get("GOCACHE"), "tmp-*.bc") if err != nil { return "", err } objTmpFile.Close() depTmpFile, err := os.CreateTemp(tmpdir, "dep-*.d") if err != nil { return "", err } depTmpFile.Close() flags := append([]string{}, cflags...) // copy cflags flags = append(flags, "-MD", "-MV", "-MTdeps", "-MF", depTmpFile.Name(), "-flto=thin") // autogenerate dependencies flags = append(flags, "-c", "-o", objTmpFile.Name(), abspath) if strings.ToLower(filepath.Ext(abspath)) == ".s" { // If this is an assembly file (.s or .S, lowercase or uppercase), then // we'll need to add -Qunused-arguments because many parameters are // relevant to C, not assembly. And with -Werror, having meaningless // flags (for the assembler) is a compiler error. flags = append(flags, "-Qunused-arguments") } if printCommands != nil { printCommands("clang", flags...) } err = runCCompiler(flags...) if err != nil { return "", &commandError{"failed to build", abspath, err} } // Create sorted and uniqued slice of dependencies. dependencyPaths, err := readDepFile(depTmpFile.Name()) if err != nil { return "", err } dependencyPaths = append(dependencyPaths, abspath) // necessary for .s files dependencySet := make(map[string]struct{}, len(dependencyPaths)) var dependencySlice []string for _, path := range dependencyPaths { if _, ok := dependencySet[path]; ok { continue } dependencySet[path] = struct{}{} dependencySlice = append(dependencySlice, path) } sort.Strings(dependencySlice) // Write dependencies file. f, err := os.CreateTemp(filepath.Dir(depfileCachePath), depfileName) if err != nil { return "", err } buf, err = json.MarshalIndent(dependencySlice, "", "\t") if err != nil { panic(err) // shouldn't happen } _, err = f.Write(buf) if err != nil { return "", err } err = f.Close() if err != nil { return "", err } err = os.Rename(f.Name(), depfileCachePath) if err != nil { return "", err } // Move temporary object file to final location. outpath, err := makeCFileCachePath(dependencySlice, depfileNameHash) if err != nil { return "", err } err = os.Rename(objTmpFile.Name(), outpath) if err != nil { return "", err } return outpath, nil } // Create a cache path (a path in GOCACHE) to store the output of a compiler // job. This path is based on the dep file name (which is a hash of metadata // including compiler flags) and the hash of all input files in the paths slice. func makeCFileCachePath(paths []string, depfileNameHash string) (string, error) { // Hash all input files. fileHashes := make(map[string]string, len(paths)) for _, path := range paths { hash, err := hashFile(path) if err != nil { return "", err } fileHashes[path] = hash } // Calculate a cache key based on the above hashes. buf, err := json.Marshal(struct { DepfileHash string FileHashes map[string]string }{ DepfileHash: depfileNameHash, FileHashes: fileHashes, }) if err != nil { panic(err) // shouldn't happen } outFileNameBuf := sha512.Sum512_224(buf) cacheKey := hex.EncodeToString(outFileNameBuf[:]) outpath := filepath.Join(goenv.Get("GOCACHE"), "obj-"+cacheKey+".bc") return outpath, nil } // hashFile hashes the given file path and returns the hash as a hex string. func hashFile(path string) (string, error) { f, err := os.Open(path) if err != nil { return "", fmt.Errorf("failed to hash file: %w", err) } defer f.Close() fileHasher := sha512.New512_224() _, err = io.Copy(fileHasher, f) if err != nil { return "", fmt.Errorf("failed to hash file: %w", err) } return hex.EncodeToString(fileHasher.Sum(nil)), nil } // readDepFile reads a dependency file in NMake (Visual Studio make) format. The // file is assumed to have a single target named deps. // // There are roughly three make syntax variants: // - BSD make, which doesn't support any escaping. This means that many special // characters are not supported in file names. // - GNU make, which supports escaping using a backslash but when it fails to // find a file it tries to fall back with the literal path name (to match BSD // make). // - NMake (Visual Studio) and Jom, which simply quote the string if there are // any weird characters. // // Clang supports two variants: a format that's a compromise between BSD and GNU // make (and is buggy to match GCC which is equally buggy), and NMake/Jom, which // is at least somewhat sane. This last format isn't perfect either: it does not // correctly handle filenames with quote marks in them. Those are generally not // allowed on Windows, but of course can be used on POSIX like systems. Still, // it's the most sane of any of the formats so readDepFile will use that format. func readDepFile(filename string) ([]string, error) { buf, err := os.ReadFile(filename) if err != nil { return nil, err } if len(buf) == 0 { return nil, nil } return parseDepFile(string(buf)) } func parseDepFile(s string) ([]string, error) { // This function makes no attempt at parsing anything other than Clang -MD // -MV output. // For Windows: replace CRLF with LF to make the logic below simpler. s = strings.ReplaceAll(s, "\r\n", "\n") // Collapse all lines ending in a backslash. These backslashes are really // just a way to continue a line without making very long lines. s = strings.ReplaceAll(s, "\\\n", " ") // Only use the first line, which is expected to begin with "deps:". line := strings.SplitN(s, "\n", 2)[0] if !strings.HasPrefix(line, "deps:") { return nil, errors.New("readDepFile: expected 'deps:' prefix") } line = strings.TrimSpace(line[len("deps:"):]) var deps []string for line != "" { if line[0] == '"' { // File path is quoted. Path ends with double quote. // This does not handle double quotes in path names, which is a // problem on non-Windows systems. line = line[1:] end := strings.IndexByte(line, '"') if end < 0 { return nil, errors.New("readDepFile: path is incorrectly quoted") } dep := line[:end] line = strings.TrimSpace(line[end+1:]) deps = append(deps, dep) } else { // File path is not quoted. Path ends in space or EOL. end := strings.IndexFunc(line, unicode.IsSpace) if end < 0 { // last dependency deps = append(deps, line) break } dep := line[:end] line = strings.TrimSpace(line[end:]) deps = append(deps, dep) } } return deps, nil } ================================================ FILE: builder/cc1as.cpp ================================================ //go:build byollvm // Source: https://github.com/llvm/llvm-project/blob/main/clang/tools/driver/cc1as_main.cpp // This file needs to be updated each LLVM release. // There are a few small modifications to make, like: // * ExecuteAssembler is made non-static. // * The struct AssemblerImplementation is moved to cc1as.h so it can be // included elsewhere. //===-- cc1as.cpp - Clang Assembler --------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This is the entry point to the clang -cc1as functionality, which implements // the direct interface to the LLVM MC based assembler. // //===----------------------------------------------------------------------===// #include "clang/Basic/Diagnostic.h" #include "clang/Basic/DiagnosticOptions.h" #include "clang/Driver/DriverDiagnostic.h" #include "clang/Driver/Options.h" #include "clang/Frontend/FrontendDiagnostic.h" #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Frontend/Utils.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/IR/DataLayout.h" #include "llvm/MC/MCAsmBackend.h" #include "llvm/MC/MCAsmInfo.h" #include "llvm/MC/MCCodeEmitter.h" #include "llvm/MC/MCContext.h" #include "llvm/MC/MCInstrInfo.h" #include "llvm/MC/MCObjectFileInfo.h" #include "llvm/MC/MCObjectWriter.h" #include "llvm/MC/MCParser/MCAsmParser.h" #include "llvm/MC/MCParser/MCTargetAsmParser.h" #include "llvm/MC/MCRegisterInfo.h" #include "llvm/MC/MCSectionMachO.h" #include "llvm/MC/MCStreamer.h" #include "llvm/MC/MCSubtargetInfo.h" #include "llvm/MC/MCTargetOptions.h" #include "llvm/MC/TargetRegistry.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/OptTable.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/FormattedStream.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/Process.h" #include "llvm/Support/Signals.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/Timer.h" #include "llvm/Support/raw_ostream.h" #include "llvm/TargetParser/Host.h" #include "llvm/TargetParser/Triple.h" #include #include #include using namespace clang; using namespace clang::driver; using namespace clang::driver::options; using namespace llvm; using namespace llvm::opt; #include "cc1as.h" bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts, ArrayRef Argv, DiagnosticsEngine &Diags) { bool Success = true; // Parse the arguments. const OptTable &OptTbl = getDriverOptTable(); llvm::opt::Visibility VisibilityMask(options::CC1AsOption); unsigned MissingArgIndex, MissingArgCount; InputArgList Args = OptTbl.ParseArgs(Argv, MissingArgIndex, MissingArgCount, VisibilityMask); // Check for missing argument error. if (MissingArgCount) { Diags.Report(diag::err_drv_missing_argument) << Args.getArgString(MissingArgIndex) << MissingArgCount; Success = false; } // Issue errors on unknown arguments. for (const Arg *A : Args.filtered(OPT_UNKNOWN)) { auto ArgString = A->getAsString(Args); std::string Nearest; if (OptTbl.findNearest(ArgString, Nearest, VisibilityMask) > 1) Diags.Report(diag::err_drv_unknown_argument) << ArgString; else Diags.Report(diag::err_drv_unknown_argument_with_suggestion) << ArgString << Nearest; Success = false; } // Construct the invocation. // Target Options Opts.Triple = llvm::Triple::normalize(Args.getLastArgValue(OPT_triple)); if (Arg *A = Args.getLastArg(options::OPT_darwin_target_variant_triple)) Opts.DarwinTargetVariantTriple = llvm::Triple(A->getValue()); if (Arg *A = Args.getLastArg(OPT_darwin_target_variant_sdk_version_EQ)) { VersionTuple Version; if (Version.tryParse(A->getValue())) Diags.Report(diag::err_drv_invalid_value) << A->getAsString(Args) << A->getValue(); else Opts.DarwinTargetVariantSDKVersion = Version; } Opts.CPU = std::string(Args.getLastArgValue(OPT_target_cpu)); Opts.Features = Args.getAllArgValues(OPT_target_feature); // Use the default target triple if unspecified. if (Opts.Triple.empty()) Opts.Triple = llvm::sys::getDefaultTargetTriple(); // Language Options Opts.IncludePaths = Args.getAllArgValues(OPT_I); Opts.NoInitialTextSection = Args.hasArg(OPT_n); Opts.SaveTemporaryLabels = Args.hasArg(OPT_msave_temp_labels); // Any DebugInfoKind implies GenDwarfForAssembly. Opts.GenDwarfForAssembly = Args.hasArg(OPT_debug_info_kind_EQ); if (const Arg *A = Args.getLastArg(OPT_compress_debug_sections_EQ)) { Opts.CompressDebugSections = llvm::StringSwitch(A->getValue()) .Case("none", llvm::DebugCompressionType::None) .Case("zlib", llvm::DebugCompressionType::Zlib) .Case("zstd", llvm::DebugCompressionType::Zstd) .Default(llvm::DebugCompressionType::None); } if (auto *DwarfFormatArg = Args.getLastArg(OPT_gdwarf64, OPT_gdwarf32)) Opts.Dwarf64 = DwarfFormatArg->getOption().matches(OPT_gdwarf64); Opts.DwarfVersion = getLastArgIntValue(Args, OPT_dwarf_version_EQ, 2, Diags); Opts.DwarfDebugFlags = std::string(Args.getLastArgValue(OPT_dwarf_debug_flags)); Opts.DwarfDebugProducer = std::string(Args.getLastArgValue(OPT_dwarf_debug_producer)); if (const Arg *A = Args.getLastArg(options::OPT_ffile_compilation_dir_EQ, options::OPT_fdebug_compilation_dir_EQ)) Opts.DebugCompilationDir = A->getValue(); Opts.MainFileName = std::string(Args.getLastArgValue(OPT_main_file_name)); for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ)) { auto Split = StringRef(Arg).split('='); Opts.DebugPrefixMap.emplace_back(Split.first, Split.second); } // Frontend Options if (Args.hasArg(OPT_INPUT)) { bool First = true; for (const Arg *A : Args.filtered(OPT_INPUT)) { if (First) { Opts.InputFile = A->getValue(); First = false; } else { Diags.Report(diag::err_drv_unknown_argument) << A->getAsString(Args); Success = false; } } } Opts.LLVMArgs = Args.getAllArgValues(OPT_mllvm); Opts.OutputPath = std::string(Args.getLastArgValue(OPT_o)); Opts.SplitDwarfOutput = std::string(Args.getLastArgValue(OPT_split_dwarf_output)); if (Arg *A = Args.getLastArg(OPT_filetype)) { StringRef Name = A->getValue(); unsigned OutputType = StringSwitch(Name) .Case("asm", FT_Asm) .Case("null", FT_Null) .Case("obj", FT_Obj) .Default(~0U); if (OutputType == ~0U) { Diags.Report(diag::err_drv_invalid_value) << A->getAsString(Args) << Name; Success = false; } else Opts.OutputType = FileType(OutputType); } Opts.ShowHelp = Args.hasArg(OPT_help); Opts.ShowVersion = Args.hasArg(OPT_version); // Transliterate Options Opts.OutputAsmVariant = getLastArgIntValue(Args, OPT_output_asm_variant, 0, Diags); Opts.ShowEncoding = Args.hasArg(OPT_show_encoding); Opts.ShowInst = Args.hasArg(OPT_show_inst); // Assemble Options Opts.RelaxAll = Args.hasArg(OPT_mrelax_all); Opts.NoExecStack = Args.hasArg(OPT_mno_exec_stack); Opts.FatalWarnings = Args.hasArg(OPT_massembler_fatal_warnings); Opts.NoWarn = Args.hasArg(OPT_massembler_no_warn); Opts.NoTypeCheck = Args.hasArg(OPT_mno_type_check); Opts.RelocationModel = std::string(Args.getLastArgValue(OPT_mrelocation_model, "pic")); Opts.TargetABI = std::string(Args.getLastArgValue(OPT_target_abi)); Opts.IncrementalLinkerCompatible = Args.hasArg(OPT_mincremental_linker_compatible); Opts.SymbolDefs = Args.getAllArgValues(OPT_defsym); // EmbedBitcode Option. If -fembed-bitcode is enabled, set the flag. // EmbedBitcode behaves the same for all embed options for assembly files. if (auto *A = Args.getLastArg(OPT_fembed_bitcode_EQ)) { Opts.EmbedBitcode = llvm::StringSwitch(A->getValue()) .Case("all", 1) .Case("bitcode", 1) .Case("marker", 1) .Default(0); } if (auto *A = Args.getLastArg(OPT_femit_dwarf_unwind_EQ)) { Opts.EmitDwarfUnwind = llvm::StringSwitch(A->getValue()) .Case("always", EmitDwarfUnwindType::Always) .Case("no-compact-unwind", EmitDwarfUnwindType::NoCompactUnwind) .Case("default", EmitDwarfUnwindType::Default); } Opts.EmitCompactUnwindNonCanonical = Args.hasArg(OPT_femit_compact_unwind_non_canonical); Opts.Crel = Args.hasArg(OPT_crel); Opts.ImplicitMapsyms = Args.hasArg(OPT_mmapsyms_implicit); Opts.X86RelaxRelocations = !Args.hasArg(OPT_mrelax_relocations_no); Opts.X86Sse2Avx = Args.hasArg(OPT_msse2avx); Opts.AsSecureLogFile = Args.getLastArgValue(OPT_as_secure_log_file); return Success; } static std::unique_ptr getOutputStream(StringRef Path, DiagnosticsEngine &Diags, bool Binary) { // Make sure that the Out file gets unlinked from the disk if we get a // SIGINT. if (Path != "-") sys::RemoveFileOnSignal(Path); std::error_code EC; auto Out = std::make_unique( Path, EC, (Binary ? sys::fs::OF_None : sys::fs::OF_TextWithCRLF)); if (EC) { Diags.Report(diag::err_fe_unable_to_open_output) << Path << EC.message(); return nullptr; } return Out; } static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) { // Get the target specific parser. std::string Error; const Target *TheTarget = TargetRegistry::lookupTarget(Opts.Triple, Error); if (!TheTarget) return Diags.Report(diag::err_target_unknown_triple) << Opts.Triple; ErrorOr> Buffer = MemoryBuffer::getFileOrSTDIN(Opts.InputFile, /*IsText=*/true); if (std::error_code EC = Buffer.getError()) { return Diags.Report(diag::err_fe_error_reading) << Opts.InputFile << EC.message(); } SourceMgr SrcMgr; // Tell SrcMgr about this buffer, which is what the parser will pick up. unsigned BufferIndex = SrcMgr.AddNewSourceBuffer(std::move(*Buffer), SMLoc()); // Record the location of the include directories so that the lexer can find // it later. SrcMgr.setIncludeDirs(Opts.IncludePaths); std::unique_ptr MRI(TheTarget->createMCRegInfo(Opts.Triple)); assert(MRI && "Unable to create target register info!"); MCTargetOptions MCOptions; MCOptions.MCRelaxAll = Opts.RelaxAll; MCOptions.EmitDwarfUnwind = Opts.EmitDwarfUnwind; MCOptions.EmitCompactUnwindNonCanonical = Opts.EmitCompactUnwindNonCanonical; MCOptions.MCSaveTempLabels = Opts.SaveTemporaryLabels; MCOptions.Crel = Opts.Crel; MCOptions.ImplicitMapSyms = Opts.ImplicitMapsyms; MCOptions.X86RelaxRelocations = Opts.X86RelaxRelocations; MCOptions.X86Sse2Avx = Opts.X86Sse2Avx; MCOptions.CompressDebugSections = Opts.CompressDebugSections; MCOptions.AsSecureLogFile = Opts.AsSecureLogFile; std::unique_ptr MAI( TheTarget->createMCAsmInfo(*MRI, Opts.Triple, MCOptions)); assert(MAI && "Unable to create target asm info!"); // Ensure MCAsmInfo initialization occurs before any use, otherwise sections // may be created with a combination of default and explicit settings. bool IsBinary = Opts.OutputType == AssemblerInvocation::FT_Obj; if (Opts.OutputPath.empty()) Opts.OutputPath = "-"; std::unique_ptr FDOS = getOutputStream(Opts.OutputPath, Diags, IsBinary); if (!FDOS) return true; std::unique_ptr DwoOS; if (!Opts.SplitDwarfOutput.empty()) DwoOS = getOutputStream(Opts.SplitDwarfOutput, Diags, IsBinary); // Build up the feature string from the target feature list. std::string FS = llvm::join(Opts.Features, ","); std::unique_ptr STI( TheTarget->createMCSubtargetInfo(Opts.Triple, Opts.CPU, FS)); assert(STI && "Unable to create subtarget info!"); MCContext Ctx(Triple(Opts.Triple), MAI.get(), MRI.get(), STI.get(), &SrcMgr, &MCOptions); bool PIC = false; if (Opts.RelocationModel == "static") { PIC = false; } else if (Opts.RelocationModel == "pic") { PIC = true; } else { assert(Opts.RelocationModel == "dynamic-no-pic" && "Invalid PIC model!"); PIC = false; } // FIXME: This is not pretty. MCContext has a ptr to MCObjectFileInfo and // MCObjectFileInfo needs a MCContext reference in order to initialize itself. std::unique_ptr MOFI( TheTarget->createMCObjectFileInfo(Ctx, PIC)); Ctx.setObjectFileInfo(MOFI.get()); if (Opts.GenDwarfForAssembly) Ctx.setGenDwarfForAssembly(true); if (!Opts.DwarfDebugFlags.empty()) Ctx.setDwarfDebugFlags(StringRef(Opts.DwarfDebugFlags)); if (!Opts.DwarfDebugProducer.empty()) Ctx.setDwarfDebugProducer(StringRef(Opts.DwarfDebugProducer)); if (!Opts.DebugCompilationDir.empty()) Ctx.setCompilationDir(Opts.DebugCompilationDir); else { // If no compilation dir is set, try to use the current directory. SmallString<128> CWD; if (!sys::fs::current_path(CWD)) Ctx.setCompilationDir(CWD); } if (!Opts.DebugPrefixMap.empty()) for (const auto &KV : Opts.DebugPrefixMap) Ctx.addDebugPrefixMapEntry(KV.first, KV.second); if (!Opts.MainFileName.empty()) Ctx.setMainFileName(StringRef(Opts.MainFileName)); Ctx.setDwarfFormat(Opts.Dwarf64 ? dwarf::DWARF64 : dwarf::DWARF32); Ctx.setDwarfVersion(Opts.DwarfVersion); if (Opts.GenDwarfForAssembly) Ctx.setGenDwarfRootFile(Opts.InputFile, SrcMgr.getMemoryBuffer(BufferIndex)->getBuffer()); std::unique_ptr Str; std::unique_ptr MCII(TheTarget->createMCInstrInfo()); assert(MCII && "Unable to create instruction info!"); raw_pwrite_stream *Out = FDOS.get(); std::unique_ptr BOS; MCOptions.MCNoWarn = Opts.NoWarn; MCOptions.MCFatalWarnings = Opts.FatalWarnings; MCOptions.MCNoTypeCheck = Opts.NoTypeCheck; MCOptions.ShowMCInst = Opts.ShowInst; MCOptions.AsmVerbose = true; MCOptions.MCUseDwarfDirectory = MCTargetOptions::EnableDwarfDirectory; MCOptions.ABIName = Opts.TargetABI; // FIXME: There is a bit of code duplication with addPassesToEmitFile. if (Opts.OutputType == AssemblerInvocation::FT_Asm) { MCInstPrinter *IP = TheTarget->createMCInstPrinter( llvm::Triple(Opts.Triple), Opts.OutputAsmVariant, *MAI, *MCII, *MRI); std::unique_ptr CE; if (Opts.ShowEncoding) CE.reset(TheTarget->createMCCodeEmitter(*MCII, Ctx)); std::unique_ptr MAB( TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions)); auto FOut = std::make_unique(*Out); Str.reset(TheTarget->createAsmStreamer(Ctx, std::move(FOut), IP, std::move(CE), std::move(MAB))); } else if (Opts.OutputType == AssemblerInvocation::FT_Null) { Str.reset(createNullStreamer(Ctx)); } else { assert(Opts.OutputType == AssemblerInvocation::FT_Obj && "Invalid file type!"); if (!FDOS->supportsSeeking()) { BOS = std::make_unique(*FDOS); Out = BOS.get(); } std::unique_ptr CE( TheTarget->createMCCodeEmitter(*MCII, Ctx)); std::unique_ptr MAB( TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions)); assert(MAB && "Unable to create asm backend!"); std::unique_ptr OW = DwoOS ? MAB->createDwoObjectWriter(*Out, *DwoOS) : MAB->createObjectWriter(*Out); Triple T(Opts.Triple); Str.reset(TheTarget->createMCObjectStreamer( T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI)); Str.get()->initSections(Opts.NoExecStack, *STI); if (T.isOSBinFormatMachO() && T.isOSDarwin()) { Triple *TVT = Opts.DarwinTargetVariantTriple ? &*Opts.DarwinTargetVariantTriple : nullptr; Str->emitVersionForTarget(T, VersionTuple(), TVT, Opts.DarwinTargetVariantSDKVersion); } } // When -fembed-bitcode is passed to clang_as, a 1-byte marker // is emitted in __LLVM,__asm section if the object file is MachO format. if (Opts.EmbedBitcode && Ctx.getObjectFileType() == MCContext::IsMachO) { MCSection *AsmLabel = Ctx.getMachOSection( "__LLVM", "__asm", MachO::S_REGULAR, 4, SectionKind::getReadOnly()); Str.get()->switchSection(AsmLabel); Str.get()->emitZeros(1); } bool Failed = false; std::unique_ptr Parser( createMCAsmParser(SrcMgr, Ctx, *Str.get(), *MAI)); // FIXME: init MCTargetOptions from sanitizer flags here. std::unique_ptr TAP( TheTarget->createMCAsmParser(*STI, *Parser, *MCII, MCOptions)); if (!TAP) Failed = Diags.Report(diag::err_target_unknown_triple) << Opts.Triple; // Set values for symbols, if any. for (auto &S : Opts.SymbolDefs) { auto Pair = StringRef(S).split('='); auto Sym = Pair.first; auto Val = Pair.second; int64_t Value; // We have already error checked this in the driver. Val.getAsInteger(0, Value); Ctx.setSymbolValue(Parser->getStreamer(), Sym, Value); } if (!Failed) { Parser->setTargetParser(*TAP.get()); Failed = Parser->Run(Opts.NoInitialTextSection); } return Failed; } bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) { bool Failed = ExecuteAssemblerImpl(Opts, Diags); // Delete output file if there were errors. if (Failed) { if (Opts.OutputPath != "-") sys::fs::remove(Opts.OutputPath); if (!Opts.SplitDwarfOutput.empty() && Opts.SplitDwarfOutput != "-") sys::fs::remove(Opts.SplitDwarfOutput); } return Failed; } static void LLVMErrorHandler(void *UserData, const char *Message, bool GenCrashDiag) { DiagnosticsEngine &Diags = *static_cast(UserData); Diags.Report(diag::err_fe_error_backend) << Message; // We cannot recover from llvm errors. sys::Process::Exit(1); } int cc1as_main(ArrayRef Argv, const char *Argv0, void *MainAddr) { // Initialize targets and assembly printers/parsers. InitializeAllTargetInfos(); InitializeAllTargetMCs(); InitializeAllAsmParsers(); // Construct our diagnostic client. IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions(); TextDiagnosticPrinter *DiagClient = new TextDiagnosticPrinter(errs(), &*DiagOpts); DiagClient->setPrefix("clang -cc1as"); IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); DiagnosticsEngine Diags(DiagID, &*DiagOpts, DiagClient); // Set an error handler, so that any LLVM backend diagnostics go through our // error handler. ScopedFatalErrorHandler FatalErrorHandler (LLVMErrorHandler, static_cast(&Diags)); // Parse the arguments. AssemblerInvocation Asm; if (!AssemblerInvocation::CreateFromArgs(Asm, Argv, Diags)) return 1; if (Asm.ShowHelp) { getDriverOptTable().printHelp( llvm::outs(), "clang -cc1as [options] file...", "Clang Integrated Assembler", /*ShowHidden=*/false, /*ShowAllAliases=*/false, llvm::opt::Visibility(driver::options::CC1AsOption)); return 0; } // Honor -version. // // FIXME: Use a better -version message? if (Asm.ShowVersion) { llvm::cl::PrintVersionMessage(); return 0; } // Honor -mllvm. // // FIXME: Remove this, one day. if (!Asm.LLVMArgs.empty()) { unsigned NumArgs = Asm.LLVMArgs.size(); auto Args = std::make_unique(NumArgs + 2); Args[0] = "clang (LLVM option parsing)"; for (unsigned i = 0; i != NumArgs; ++i) Args[i + 1] = Asm.LLVMArgs[i].c_str(); Args[NumArgs + 1] = nullptr; llvm::cl::ParseCommandLineOptions(NumArgs + 1, Args.get()); } // Execute the invocation, unless there were parsing errors. bool Failed = Diags.hasErrorOccurred() || ExecuteAssembler(Asm, Diags); // If any timers were active but haven't been destroyed yet, print their // results now. TimerGroup::printAll(errs()); TimerGroup::clearAll(); return !!Failed; } ================================================ FILE: builder/cc1as.h ================================================ // Source: https://github.com/llvm/llvm-project/blob/main/clang/tools/driver/cc1as_main.cpp // See cc1as.cpp for details. //===-- cc1as.h - Clang Assembler ----------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This is the entry point to the clang -cc1as functionality, which implements // the direct interface to the LLVM MC based assembler. // //===----------------------------------------------------------------------===// /// Helper class for representing a single invocation of the assembler. struct AssemblerInvocation { /// @name Target Options /// @{ /// The name of the target triple to assemble for. std::string Triple; /// If given, the name of the target CPU to determine which instructions /// are legal. std::string CPU; /// The list of target specific features to enable or disable -- this should /// be a list of strings starting with '+' or '-'. std::vector Features; /// The list of symbol definitions. std::vector SymbolDefs; /// @} /// @name Language Options /// @{ std::vector IncludePaths; LLVM_PREFERRED_TYPE(bool) unsigned NoInitialTextSection : 1; LLVM_PREFERRED_TYPE(bool) unsigned SaveTemporaryLabels : 1; LLVM_PREFERRED_TYPE(bool) unsigned GenDwarfForAssembly : 1; LLVM_PREFERRED_TYPE(bool) unsigned Dwarf64 : 1; unsigned DwarfVersion; std::string DwarfDebugFlags; std::string DwarfDebugProducer; std::string DebugCompilationDir; llvm::SmallVector, 0> DebugPrefixMap; llvm::DebugCompressionType CompressDebugSections = llvm::DebugCompressionType::None; std::string MainFileName; std::string SplitDwarfOutput; /// @} /// @name Frontend Options /// @{ std::string InputFile; std::vector LLVMArgs; std::string OutputPath; enum FileType { FT_Asm, ///< Assembly (.s) output, transliterate mode. FT_Null, ///< No output, for timing purposes. FT_Obj ///< Object file output. }; FileType OutputType; LLVM_PREFERRED_TYPE(bool) unsigned ShowHelp : 1; LLVM_PREFERRED_TYPE(bool) unsigned ShowVersion : 1; /// @} /// @name Transliterate Options /// @{ unsigned OutputAsmVariant; LLVM_PREFERRED_TYPE(bool) unsigned ShowEncoding : 1; LLVM_PREFERRED_TYPE(bool) unsigned ShowInst : 1; /// @} /// @name Assembler Options /// @{ LLVM_PREFERRED_TYPE(bool) unsigned RelaxAll : 1; LLVM_PREFERRED_TYPE(bool) unsigned NoExecStack : 1; LLVM_PREFERRED_TYPE(bool) unsigned FatalWarnings : 1; LLVM_PREFERRED_TYPE(bool) unsigned NoWarn : 1; LLVM_PREFERRED_TYPE(bool) unsigned NoTypeCheck : 1; LLVM_PREFERRED_TYPE(bool) unsigned IncrementalLinkerCompatible : 1; LLVM_PREFERRED_TYPE(bool) unsigned EmbedBitcode : 1; /// Whether to emit DWARF unwind info. EmitDwarfUnwindType EmitDwarfUnwind; // Whether to emit compact-unwind for non-canonical entries. // Note: maybe overriden by other constraints. LLVM_PREFERRED_TYPE(bool) unsigned EmitCompactUnwindNonCanonical : 1; LLVM_PREFERRED_TYPE(bool) unsigned Crel : 1; LLVM_PREFERRED_TYPE(bool) unsigned ImplicitMapsyms : 1; LLVM_PREFERRED_TYPE(bool) unsigned X86RelaxRelocations : 1; LLVM_PREFERRED_TYPE(bool) unsigned X86Sse2Avx : 1; /// The name of the relocation model to use. std::string RelocationModel; /// The ABI targeted by the backend. Specified using -target-abi. Empty /// otherwise. std::string TargetABI; /// Darwin target variant triple, the variant of the deployment target /// for which the code is being compiled. std::optional DarwinTargetVariantTriple; /// The version of the darwin target variant SDK which was used during the /// compilation llvm::VersionTuple DarwinTargetVariantSDKVersion; /// The name of a file to use with \c .secure_log_unique directives. std::string AsSecureLogFile; /// @} public: AssemblerInvocation() { Triple = ""; NoInitialTextSection = 0; InputFile = "-"; OutputPath = "-"; OutputType = FT_Asm; OutputAsmVariant = 0; ShowInst = 0; ShowEncoding = 0; RelaxAll = 0; NoExecStack = 0; FatalWarnings = 0; NoWarn = 0; NoTypeCheck = 0; IncrementalLinkerCompatible = 0; Dwarf64 = 0; DwarfVersion = 0; EmbedBitcode = 0; EmitDwarfUnwind = EmitDwarfUnwindType::Default; EmitCompactUnwindNonCanonical = false; Crel = false; ImplicitMapsyms = 0; X86RelaxRelocations = 0; X86Sse2Avx = 0; } static bool CreateFromArgs(AssemblerInvocation &Res, ArrayRef Argv, DiagnosticsEngine &Diags); }; bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags); ================================================ FILE: builder/cc_test.go ================================================ package builder import ( "reflect" "testing" ) func TestSplitDepFile(t *testing.T) { for i, tc := range []struct { in string out []string }{ {`deps: foo bar`, []string{"foo", "bar"}}, {`deps: foo "bar"`, []string{"foo", "bar"}}, {`deps: "foo" bar`, []string{"foo", "bar"}}, {`deps: "foo bar"`, []string{"foo bar"}}, {`deps: "foo bar" `, []string{"foo bar"}}, {"deps: foo\nbar", []string{"foo"}}, {"deps: foo \\\nbar", []string{"foo", "bar"}}, {"deps: foo\\bar \\\nbaz", []string{"foo\\bar", "baz"}}, {"deps: foo\\bar \\\r\n baz", []string{"foo\\bar", "baz"}}, // Windows uses CRLF line endings } { out, err := parseDepFile(tc.in) if err != nil { t.Errorf("test #%d failed: %v", i, err) continue } if !reflect.DeepEqual(out, tc.out) { t.Errorf("test #%d failed: expected %#v but got %#v", i, tc.out, out) continue } } } ================================================ FILE: builder/clang.cpp ================================================ //go:build byollvm #include #include #include #include #include #include #include #include #include #include #include #include using namespace llvm; using namespace clang; #include "cc1as.h" // This file provides C wrappers for the builtin tools cc1 and cc1as // provided by Clang, and calls them as the driver would call them. extern "C" { bool tinygo_clang_driver(int argc, char **argv) { std::vector args(argv, argv + argc); // The compiler invocation needs a DiagnosticsEngine so it can report problems llvm::IntrusiveRefCntPtr DiagOpts = new clang::DiagnosticOptions(); clang::TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), &*DiagOpts); clang::DiagnosticsEngine Diags(llvm::IntrusiveRefCntPtr(new clang::DiagnosticIDs()), &*DiagOpts, &DiagnosticPrinter, false); // Create the clang driver clang::driver::Driver TheDriver(args[0], llvm::sys::getDefaultTargetTriple(), Diags); // Create the set of actions to perform std::unique_ptr C(TheDriver.BuildCompilation(args)); if (!C) { return false; } const clang::driver::JobList &Jobs = C->getJobs(); // There may be more than one job, for example for .S files // (preprocessor + assembler). for (auto Cmd : Jobs) { // Select the tool: cc1 or cc1as. const llvm::opt::ArgStringList &CCArgs = Cmd.getArguments(); if (strcmp(*CCArgs.data(), "-cc1") == 0) { // This is the C frontend. // Initialize a compiler invocation object from the clang (-cc1) arguments. std::unique_ptr Clang(new clang::CompilerInstance()); bool success = clang::CompilerInvocation::CreateFromArgs( Clang->getInvocation(), CCArgs, Diags); if (!success) { return false; } // Create the actual diagnostics engine. Clang->createDiagnostics(*llvm::vfs::getRealFileSystem()); if (!Clang->hasDiagnostics()) { return false; } // Execute the frontend actions. success = ExecuteCompilerInvocation(Clang.get()); if (!success) { return false; } } else if (strcmp(*CCArgs.data(), "-cc1as") == 0) { // This is the assembler frontend. Parse the arguments. AssemblerInvocation Asm; ArrayRef Argv = llvm::ArrayRef(CCArgs); if (!AssemblerInvocation::CreateFromArgs(Asm, Argv.slice(1), Diags)) return false; // Execute the invocation, unless there were parsing errors. bool failed = Diags.hasErrorOccurred() || ExecuteAssembler(Asm, Diags); if (failed) { return false; } } else { // Unknown tool, print the tool and exit. fprintf(stderr, "unknown tool: %s\n", *CCArgs.data()); return false; } } // Commands executed successfully. return true; } } // extern "C" ================================================ FILE: builder/commands.go ================================================ package builder import ( "errors" "fmt" "os/exec" "runtime" "strings" "tinygo.org/x/go-llvm" ) // Commands lists command alternatives for various operating systems. These // commands may have a slightly different name across operating systems and // distributions or may not even exist in $PATH, in which case absolute paths // may be used. var commands = map[string][]string{} func init() { llvmMajor := strings.Split(llvm.Version, ".")[0] commands["clang"] = []string{"clang-" + llvmMajor} commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"} commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"} commands["lldb"] = []string{"lldb-" + llvmMajor, "lldb"} // Add the path to a Homebrew-installed LLVM for ease of use (no need to // manually set $PATH). if runtime.GOOS == "darwin" { var prefix string switch runtime.GOARCH { case "amd64": prefix = "/usr/local/opt/llvm@" + llvmMajor + "/bin/" case "arm64": prefix = "/opt/homebrew/opt/llvm@" + llvmMajor + "/bin/" default: // unknown GOARCH panic(fmt.Sprintf("unknown GOARCH: %s on darwin", runtime.GOARCH)) } commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor) commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld") commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld") commands["lldb"] = append(commands["lldb"], prefix+"lldb") } // Add the path for when LLVM was installed with the installer from // llvm.org, which by default doesn't add LLVM to the $PATH environment // variable. if runtime.GOOS == "windows" { commands["clang"] = append(commands["clang"], "clang", "C:\\Program Files\\LLVM\\bin\\clang.exe") commands["ld.lld"] = append(commands["ld.lld"], "lld", "C:\\Program Files\\LLVM\\bin\\lld.exe") commands["wasm-ld"] = append(commands["wasm-ld"], "C:\\Program Files\\LLVM\\bin\\wasm-ld.exe") commands["lldb"] = append(commands["lldb"], "C:\\Program Files\\LLVM\\bin\\lldb.exe") } // Add the path to LLVM installed from ports. if runtime.GOOS == "freebsd" { prefix := "/usr/local/llvm" + llvmMajor + "/bin/" commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor) commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld") commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld") commands["lldb"] = append(commands["lldb"], prefix+"lldb") } } // LookupCommand looks up the executable name for a given LLVM tool such as // clang or wasm-ld. It returns the (relative) command that can be used to // invoke the tool or an error if it could not be found. func LookupCommand(name string) (string, error) { for _, cmdName := range commands[name] { _, err := exec.LookPath(cmdName) if err != nil { if errors.Unwrap(err) == exec.ErrNotFound { continue } return cmdName, err } return cmdName, nil } return "", errors.New("none of these commands were found in your $PATH: " + strings.Join(commands[name], " ")) } ================================================ FILE: builder/config.go ================================================ package builder import ( "fmt" "runtime" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/goenv" ) // NewConfig builds a new Config object from a set of compiler options. It also // loads some information from the environment while doing that. For example, it // uses the currently active GOPATH (from the goenv package) to determine the Go // version to use. func NewConfig(options *compileopts.Options) (*compileopts.Config, error) { spec, err := compileopts.LoadTarget(options) if err != nil { return nil, err } if options.OpenOCDCommands != nil { // Override the OpenOCDCommands from the target spec if specified on // the command-line spec.OpenOCDCommands = options.OpenOCDCommands } // Version range supported by TinyGo. const minorMin = 19 const minorMax = 25 // Check that we support this Go toolchain version. gorootMajor, gorootMinor, err := goenv.GetGorootVersion() if err != nil { return nil, err } if options.GoCompatibility { if gorootMajor != 1 || gorootMinor < minorMin || gorootMinor > minorMax { // Note: when this gets updated, also update the Go compatibility matrix: // https://github.com/tinygo-org/tinygo-site/blob/dev/content/docs/reference/go-compat-matrix.md return nil, fmt.Errorf("requires go version 1.%d through 1.%d, got go%d.%d", minorMin, minorMax, gorootMajor, gorootMinor) } } // Check that the Go toolchain version isn't too new, if we haven't been // compiled with the latest Go version. // This may be a bit too aggressive: if the newer version doesn't change the // Go language we will most likely be able to compile it. buildMajor, buildMinor, _, err := goenv.Parse(runtime.Version()) if err != nil { return nil, err } if buildMajor != 1 || buildMinor < gorootMinor { return nil, fmt.Errorf("cannot compile with Go toolchain version go%d.%d (TinyGo was built using toolchain version %s)", gorootMajor, gorootMinor, runtime.Version()) } return &compileopts.Config{ Options: options, Target: spec, GoMinorVersion: gorootMinor, TestConfig: options.TestConfig, }, nil } ================================================ FILE: builder/darwin-libsystem.go ================================================ package builder import ( "path/filepath" "strings" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/goenv" ) // Create a job that builds a Darwin libSystem.dylib stub library. This library // contains all the symbols needed so that we can link against it, but it // doesn't contain any real symbol implementations. func makeDarwinLibSystemJob(config *compileopts.Config, tmpdir string) *compileJob { return &compileJob{ description: "compile Darwin libSystem.dylib", run: func(job *compileJob) (err error) { arch := strings.Split(config.Triple(), "-")[0] job.result = filepath.Join(tmpdir, "libSystem.dylib") objpath := filepath.Join(tmpdir, "libSystem.o") inpath := filepath.Join(goenv.Get("TINYGOROOT"), "lib/macos-minimal-sdk/src", arch, "libSystem.s") // Compile assembly file to object file. flags := []string{ "-nostdlib", "--target=" + config.Triple(), "-c", "-o", objpath, inpath, } if config.Options.PrintCommands != nil { config.Options.PrintCommands("clang", flags...) } err = runCCompiler(flags...) if err != nil { return err } // Link object file to dynamic library. platformVersion := strings.TrimPrefix(strings.Split(config.Triple(), "-")[2], "macosx") flags = []string{ "-flavor", "darwin", "-demangle", "-dynamic", "-dylib", "-arch", arch, "-platform_version", "macos", platformVersion, platformVersion, "-install_name", "/usr/lib/libSystem.B.dylib", "-o", job.result, objpath, } if config.Options.PrintCommands != nil { config.Options.PrintCommands("ld.lld", flags...) } return link("ld.lld", flags...) }, } } ================================================ FILE: builder/elfpatch.go ================================================ package builder import ( "debug/elf" "fmt" "os" ) func getElfSectionData(executable string, sectionName string) ([]byte, elf.FileHeader, error) { elfFile, err := elf.Open(executable) if err != nil { return nil, elf.FileHeader{}, err } defer elfFile.Close() section := elfFile.Section(sectionName) if section == nil { return nil, elf.FileHeader{}, fmt.Errorf("could not find %s section", sectionName) } data, err := section.Data() return data, elfFile.FileHeader, err } func replaceElfSection(executable string, sectionName string, data []byte) error { fp, err := os.OpenFile(executable, os.O_RDWR, 0) if err != nil { return err } defer fp.Close() elfFile, err := elf.Open(executable) if err != nil { return err } defer elfFile.Close() section := elfFile.Section(sectionName) if section == nil { return fmt.Errorf("could not find %s section", sectionName) } // Implicitly check for compressed sections if section.Size != section.FileSize { return fmt.Errorf("expected section %s to have identical size and file size, got %d and %d", sectionName, section.Size, section.FileSize) } // Only permit complete replacement of section if section.Size != uint64(len(data)) { return fmt.Errorf("expected section %s to have size %d, was actually %d", sectionName, len(data), section.Size) } // Write the replacement section data _, err = fp.WriteAt(data, int64(section.Offset)) return err } ================================================ FILE: builder/error.go ================================================ package builder // MultiError is a list of multiple errors (actually: diagnostics) returned // during LLVM IR generation. type MultiError struct { ImportPath string Errs []error } func (e *MultiError) Error() string { // Return the first error, to conform to the error interface. Clients should // really do a type-assertion on *MultiError. return e.Errs[0].Error() } // newMultiError returns a *MultiError if there is more than one error, or // returns that error directly when there is only one. Passing an empty slice // will return nil (because there is no error). // The importPath may be passed if this error is for a single package. func newMultiError(errs []error, importPath string) error { switch len(errs) { case 0: return nil case 1: return errs[0] default: return &MultiError{importPath, errs} } } // commandError is an error type to wrap os/exec.Command errors. This provides // some more information regarding what went wrong while running a command. type commandError struct { Msg string File string Err error } func (e *commandError) Error() string { return e.Msg + " " + e.File + ": " + e.Err.Error() } ================================================ FILE: builder/esp.go ================================================ package builder // This file implements support for writing ESP image files. These image files // are read by the ROM bootloader so have to be in a particular format. // // In the future, it may be necessary to implement support for other image // formats, such as the ESP8266 image formats (again, used by the ROM bootloader // to load the firmware). import ( "bytes" "crypto/sha256" "debug/elf" "encoding/binary" "fmt" "os" "sort" "strings" ) type espImageSegment struct { addr uint32 data []byte } // makeESPFirmwareImage converts an input ELF file to an image file for an ESP32 or // ESP8266 chip. This is a special purpose image format just for the ESP chip // family, and is parsed by the on-chip mask ROM bootloader. // // The following documentation has been used: // https://github.com/espressif/esptool/wiki/Firmware-Image-Format // https://github.com/espressif/esp-idf/blob/8fbb63c2a701c22ccf4ce249f43aded73e134a34/components/bootloader_support/include/esp_image_format.h#L58 // https://github.com/espressif/esptool/blob/master/esptool.py func makeESPFirmwareImage(infile, outfile, format string) error { inf, err := elf.Open(infile) if err != nil { return err } defer inf.Close() // Load all segments to be written to the image. These are actually ELF // sections, not true ELF segments (similar to how esptool does it). var segments []*espImageSegment for _, section := range inf.Sections { if section.Type != elf.SHT_PROGBITS || section.Size == 0 || section.Flags&elf.SHF_ALLOC == 0 { continue } data, err := section.Data() if err != nil { return fmt.Errorf("failed to read section data: %w", err) } for len(data)%4 != 0 { // Align segment to 4 bytes. data = append(data, 0) } if uint64(uint32(section.Addr)) != section.Addr { return fmt.Errorf("section address too big: 0x%x", section.Addr) } segments = append(segments, &espImageSegment{ addr: uint32(section.Addr), data: data, }) } // Sort the segments by address. This is what esptool does too. sort.SliceStable(segments, func(i, j int) bool { return segments[i].addr < segments[j].addr }) // Calculate checksum over the segment data. This is used in the image // footer. checksum := uint8(0xef) for _, segment := range segments { for _, b := range segment.data { checksum ^= b } } // Write first to an in-memory buffer, primarily so that we can easily // calculate a hash over the entire image. // An added benefit is that we don't need to check for errors all the time. outf := &bytes.Buffer{} // Separate esp32 and esp32-img. The -img suffix indicates we should make an // image, not just a binary to be flashed at 0x1000 for example. chip := format makeImage := false if strings.HasSuffix(format, "-img") { makeImage = true chip = format[:len(format)-len("-img")] } if makeImage { // The bootloader starts at 0x1000, or 4096. // TinyGo doesn't use a separate bootloader and runs the entire // application in the bootloader location. outf.Write(make([]byte, 4096)) } // Chip IDs. Source: // https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L22 chip_id := map[string]uint16{ "esp32": 0x0000, "esp32c3": 0x0005, "esp32s3": 0x0009, }[chip] // Image header. switch chip { case "esp32", "esp32c3", "esp32s3": // Header format: // https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L71 // Note: not adding a SHA256 hash as the binary is modified by // esptool.py while flashing and therefore the hash won't be valid // anymore. binary.Write(outf, binary.LittleEndian, struct { magic uint8 segment_count uint8 spi_mode uint8 spi_speed_size uint8 entry_addr uint32 wp_pin uint8 spi_pin_drv [3]uint8 chip_id uint16 min_chip_rev uint8 reserved [8]uint8 hash_appended bool }{ magic: 0xE9, segment_count: byte(len(segments)), spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO spi_speed_size: 0x1f, // ESP_IMAGE_SPI_SPEED_80M, ESP_IMAGE_FLASH_SIZE_2MB entry_addr: uint32(inf.Entry), wp_pin: 0xEE, // disable WP pin chip_id: chip_id, hash_appended: true, // add a SHA256 hash }) case "esp8266": // Header format: // https://github.com/espressif/esptool/wiki/Firmware-Image-Format // Basically a truncated version of the ESP32 header. binary.Write(outf, binary.LittleEndian, struct { magic uint8 segment_count uint8 spi_mode uint8 spi_speed_size uint8 entry_addr uint32 }{ magic: 0xE9, segment_count: byte(len(segments)), spi_mode: 0, // irrelevant, replaced by esptool when flashing spi_speed_size: 0x20, // spi_speed, spi_size: replaced by esptool when flashing entry_addr: uint32(inf.Entry), }) default: return fmt.Errorf("builder: unknown binary format %#v, expected esp32 or esp8266", format) } // Write all segments to the image. // https://github.com/espressif/esptool/wiki/Firmware-Image-Format#segment for _, segment := range segments { binary.Write(outf, binary.LittleEndian, struct { addr uint32 length uint32 }{ addr: segment.addr, length: uint32(len(segment.data)), }) outf.Write(segment.data) } // Footer, including checksum. // The entire image size must be a multiple of 16, so pad the image to one // byte less than that before writing the checksum. outf.Write(make([]byte, 15-outf.Len()%16)) outf.WriteByte(checksum) if chip != "esp8266" { // SHA256 hash (to protect against image corruption, not for security). hash := sha256.Sum256(outf.Bytes()) outf.Write(hash[:]) } // QEMU (or more precisely, qemu-system-xtensa from Espressif) expects the // image to be a certain size. if makeImage { // Use a default image size of 4MB. grow := 4096*1024 - outf.Len() if grow > 0 { outf.Write(make([]byte, grow)) } } // Write the image to the output file. return os.WriteFile(outfile, outf.Bytes(), 0666) } ================================================ FILE: builder/jobs.go ================================================ package builder // This file implements a job runner for the compiler, which runs jobs in // parallel while taking care of dependencies. import ( "container/heap" "errors" "fmt" "runtime" "sort" "strings" "time" ) // Set to true to enable logging in the job runner. This may help to debug // concurrency or performance issues. const jobRunnerDebug = false // compileJob is a single compiler job, comparable to a single Makefile target. // It is used to orchestrate various compiler tasks that can be run in parallel // but that have dependencies and thus have limitations in how they can be run. type compileJob struct { description string // description, only used for logging dependencies []*compileJob result string // result (path) run func(*compileJob) (err error) err error // error if finished duration time.Duration // how long it took to run this job (only set after finishing) } // dummyCompileJob returns a new *compileJob that produces an output without // doing anything. This can be useful where a *compileJob producing an output is // expected but nothing needs to be done, for example for a load from a cache. func dummyCompileJob(result string) *compileJob { return &compileJob{ description: "", result: result, } } // runJobs runs the indicated job and all its dependencies. For every job, all // the dependencies are run first. It returns the error of the first job that // fails. // It runs all jobs in the order of the dependencies slice, depth-first. // Therefore, if some jobs are preferred to run before others, they should be // ordered as such in the job dependencies. func runJobs(job *compileJob, sema chan struct{}) error { if sema == nil { // Have a default, if the semaphore isn't set. This is useful for tests. sema = make(chan struct{}, runtime.NumCPU()) } if cap(sema) == 0 { return errors.New("cannot run 0 jobs at a time") } // Create a slice of jobs to run, where all dependencies are run in order. jobs := []*compileJob{} addedJobs := map[*compileJob]struct{}{} var addJobs func(*compileJob) addJobs = func(job *compileJob) { if _, ok := addedJobs[job]; ok { return } for _, dep := range job.dependencies { addJobs(dep) } jobs = append(jobs, job) addedJobs[job] = struct{}{} } addJobs(job) waiting := make(map[*compileJob]map[*compileJob]struct{}, len(jobs)) dependents := make(map[*compileJob][]*compileJob, len(jobs)) compileJobs := make(map[*compileJob]int) var ready intHeap for i, job := range jobs { compileJobs[job] = i if len(job.dependencies) == 0 { // This job is ready to run. ready.Push(i) continue } // Construct a map for dependencies which the job is currently waiting on. waitDeps := make(map[*compileJob]struct{}) waiting[job] = waitDeps // Add the job to the dependents list of each dependency. for _, dep := range job.dependencies { dependents[dep] = append(dependents[dep], job) waitDeps[dep] = struct{}{} } } // Create a channel to accept notifications of completion. doneChan := make(chan *compileJob) // Send each job in the jobs slice to a worker, taking care of job dependencies. numRunningJobs := 0 var totalTime time.Duration start := time.Now() for len(ready.IntSlice) > 0 || numRunningJobs != 0 { var completed *compileJob if len(ready.IntSlice) > 0 { select { case sema <- struct{}{}: // Start a job. job := jobs[heap.Pop(&ready).(int)] if jobRunnerDebug { fmt.Println("## start: ", job.description) } go runJob(job, doneChan) numRunningJobs++ continue case completed = <-doneChan: // A job completed. } } else { // Wait for a job to complete. completed = <-doneChan } numRunningJobs-- <-sema if jobRunnerDebug { fmt.Println("## finished:", completed.description, "(time "+completed.duration.String()+")") } if completed.err != nil { // Wait for any current jobs to finish. for numRunningJobs != 0 { <-doneChan numRunningJobs-- } // The build failed. return completed.err } // Update total run time. totalTime += completed.duration // Update dependent jobs. for _, j := range dependents[completed] { wait := waiting[j] delete(wait, completed) if len(wait) == 0 { // This job is now ready to run. ready.Push(compileJobs[j]) delete(waiting, j) } } } if len(waiting) != 0 { // There is a dependency cycle preventing some jobs from running. return errDependencyCycle{waiting} } // Some statistics, if debugging. if jobRunnerDebug { // Total duration of running all jobs. duration := time.Since(start) fmt.Println("## total: ", duration) // The individual time of each job combined. On a multicore system, this // should be lower than the total above. fmt.Println("## job sum: ", totalTime) } return nil } type errDependencyCycle struct { waiting map[*compileJob]map[*compileJob]struct{} } func (err errDependencyCycle) Error() string { waits := make([]string, 0, len(err.waiting)) for j, wait := range err.waiting { deps := make([]string, 0, len(wait)) for dep := range wait { deps = append(deps, dep.description) } sort.Strings(deps) waits = append(waits, fmt.Sprintf("\t%s is waiting for [%s]", j.description, strings.Join(deps, ", "), )) } sort.Strings(waits) return "deadlock:\n" + strings.Join(waits, "\n") } type intHeap struct { sort.IntSlice } func (h *intHeap) Push(x interface{}) { h.IntSlice = append(h.IntSlice, x.(int)) } func (h *intHeap) Pop() interface{} { x := h.IntSlice[len(h.IntSlice)-1] h.IntSlice = h.IntSlice[:len(h.IntSlice)-1] return x } // runJob runs a compile job and notifies doneChan of completion. func runJob(job *compileJob, doneChan chan *compileJob) { start := time.Now() if job.run != nil { err := job.run(job) if err != nil { job.err = err } } job.duration = time.Since(start) doneChan <- job } ================================================ FILE: builder/library.go ================================================ package builder import ( "errors" "io/fs" "os" "path/filepath" "runtime" "strings" "sync" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/goenv" ) // Library is a container for information about a single C library, such as a // compiler runtime or libc. // // Note: whenever a library gets changed, the version in compileopts/config.go // probably also needs to be incremented. type Library struct { // The library name, such as compiler-rt or picolibc. name string // makeHeaders creates a header include dir for the library makeHeaders func(target, includeDir string) error // cflags returns the C flags specific to this library cflags func(target, headerPath string) []string // cflagsForFile returns additional C flags for a particular source file. cflagsForFile func(path string) []string // needsLibc is set to true if this library needs libc headers. needsLibc bool // The source directory. sourceDir func() string // The source files, relative to sourceDir. librarySources func(target string, libcNeedsMalloc bool) ([]string, error) // The source code for the crt1.o file, relative to sourceDir. crt1Source string } // load returns a compile job to build this library file for the given target // and CPU. It may return a dummy compileJob if the library build is already // cached. The path is stored as job.result but is only valid after the job has // been run. // The provided tmpdir will be used to store intermediary files and possibly the // output archive file, it is expected to be removed after use. // As a side effect, this call creates the library header files if they didn't // exist yet. func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJob, abortLock func(), err error) { outdir := config.LibraryPath(l.name) archiveFilePath := filepath.Join(outdir, "lib.a") // Create a lock on the output (if supported). // This is a bit messy, but avoids a deadlock because it is ordered consistently with other library loads within a build. outname := filepath.Base(outdir) unlock := lock(filepath.Join(goenv.Get("GOCACHE"), outname+".lock")) var ok bool defer func() { if !ok { unlock() } }() // Try to fetch this library from the cache. if _, err := os.Stat(archiveFilePath); err == nil { return dummyCompileJob(archiveFilePath), func() {}, nil } // Cache miss, build it now. // Create the destination directory where the components of this library // (lib.a file, include directory) are placed. err = os.MkdirAll(filepath.Join(goenv.Get("GOCACHE"), outname), 0o777) if err != nil { // Could not create directory (and not because it already exists). return nil, nil, err } // Make headers if needed. headerPath := filepath.Join(outdir, "include") target := config.Triple() if l.makeHeaders != nil { if _, err = os.Stat(headerPath); err != nil { temporaryHeaderPath, err := os.MkdirTemp(outdir, "include.tmp*") if err != nil { return nil, nil, err } defer os.RemoveAll(temporaryHeaderPath) err = l.makeHeaders(target, temporaryHeaderPath) if err != nil { return nil, nil, err } err = os.Chmod(temporaryHeaderPath, 0o755) // TempDir uses 0o700 by default if err != nil { return nil, nil, err } err = os.Rename(temporaryHeaderPath, headerPath) if err != nil { switch { case errors.Is(err, fs.ErrExist): // Another invocation of TinyGo also seems to have already created the headers. case runtime.GOOS == "windows" && errors.Is(err, fs.ErrPermission): // On Windows, a rename with a destination directory that already // exists does not result in an IsExist error, but rather in an // access denied error. To be sure, check for this case by checking // whether the target directory exists. if _, err := os.Stat(headerPath); err == nil { break } fallthrough default: return nil, nil, err } } } } remapDir := filepath.Join(os.TempDir(), "tinygo-"+l.name) dir := filepath.Join(tmpdir, "build-lib-"+l.name) err = os.Mkdir(dir, 0777) if err != nil { return nil, nil, err } // Precalculate the flags to the compiler invocation. // Note: -fdebug-prefix-map is necessary to make the output archive // reproducible. Otherwise the temporary directory is stored in the archive // itself, which varies each run. args := append(l.cflags(target, headerPath), "-c", "-Oz", "-gdwarf-4", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir) resourceDir := goenv.ClangResourceDir(false) if resourceDir != "" { args = append(args, "-resource-dir="+resourceDir) } cpu := config.CPU() if cpu != "" { // X86 has deprecated the -mcpu flag, so we need to use -march instead. // However, ARM has not done this. if strings.HasPrefix(target, "i386") || strings.HasPrefix(target, "x86_64") { args = append(args, "-march="+cpu) } else if strings.HasPrefix(target, "avr") { args = append(args, "-mmcu="+cpu) } else { args = append(args, "-mcpu="+cpu) } } if config.ABI() != "" { args = append(args, "-mabi="+config.ABI()) } switch compileopts.CanonicalArchName(target) { case "arm": if strings.Split(target, "-")[2] == "linux" { args = append(args, "-fno-unwind-tables", "-fno-asynchronous-unwind-tables") } else { args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft", "-fno-unwind-tables", "-fno-asynchronous-unwind-tables") } case "avr": // AVR defaults to C float and double both being 32-bit. This deviates // from what most code (and certainly compiler-rt) expects. So we need // to force the compiler to use 64-bit floating point numbers for // double. args = append(args, "-mdouble=64") case "riscv32": args = append(args, "-march=rv32imac", "-fforce-enable-int128") case "riscv64": args = append(args, "-march=rv64gc") case "mips": args = append(args, "-fno-pic") } if config.Target.SoftFloat { // Use softfloat instead of floating point instructions. This is // supported on many architectures. args = append(args, "-msoft-float") } else { if strings.HasPrefix(target, "armv5") { // On ARMv5 we need to explicitly enable hardware floating point // instructions: Clang appears to assume the hardware doesn't have a // FPU otherwise. args = append(args, "-mfpu=vfpv2") } } if l.needsLibc { args = append(args, config.LibcCFlags()...) } var once sync.Once // Create job to put all the object files in a single archive. This archive // file is the (static) library file. var objs []string job = &compileJob{ description: "ar " + l.name + "/lib.a", result: filepath.Join(goenv.Get("GOCACHE"), outname, "lib.a"), run: func(*compileJob) error { defer once.Do(unlock) // Create an archive of all object files. f, err := os.CreateTemp(outdir, "libc.a.tmp*") if err != nil { return err } err = makeArchive(f, objs) if err != nil { return err } err = f.Close() if err != nil { return err } err = os.Chmod(f.Name(), 0o644) // TempFile uses 0o600 by default if err != nil { return err } // Store this archive in the cache. return os.Rename(f.Name(), archiveFilePath) }, } sourceDir := l.sourceDir() // Create jobs to compile all sources. These jobs are depended upon by the // archive job above, so must be run first. paths, err := l.librarySources(target, config.LibcNeedsMalloc()) if err != nil { return nil, nil, err } for _, path := range paths { // Strip leading "../" parts off the path. path := path cleanpath := path for strings.HasPrefix(cleanpath, "../") { cleanpath = cleanpath[3:] } srcpath := filepath.Join(sourceDir, path) objpath := filepath.Join(dir, cleanpath+".o") os.MkdirAll(filepath.Dir(objpath), 0o777) objs = append(objs, objpath) objfile := &compileJob{ description: "compile " + srcpath, run: func(*compileJob) error { var compileArgs []string compileArgs = append(compileArgs, args...) if l.cflagsForFile != nil { compileArgs = append(compileArgs, l.cflagsForFile(path)...) } compileArgs = append(compileArgs, "-o", objpath, srcpath) if config.Options.PrintCommands != nil { config.Options.PrintCommands("clang", compileArgs...) } err := runCCompiler(compileArgs...) if err != nil { return &commandError{"failed to build", srcpath, err} } return nil }, } job.dependencies = append(job.dependencies, objfile) } // Create crt1.o job, if needed. // Add this as a (fake) dependency to the ar file so it gets compiled. // (It could be done in parallel with creating the ar file, but it probably // won't make much of a difference in speed). if l.crt1Source != "" { srcpath := filepath.Join(sourceDir, l.crt1Source) crt1Job := &compileJob{ description: "compile " + srcpath, run: func(*compileJob) error { var compileArgs []string compileArgs = append(compileArgs, args...) tmpfile, err := os.CreateTemp(outdir, "crt1.o.tmp*") if err != nil { return err } tmpfile.Close() compileArgs = append(compileArgs, "-o", tmpfile.Name(), srcpath) if config.Options.PrintCommands != nil { config.Options.PrintCommands("clang", compileArgs...) } err = runCCompiler(compileArgs...) if err != nil { return &commandError{"failed to build", srcpath, err} } return os.Rename(tmpfile.Name(), filepath.Join(outdir, "crt1.o")) }, } job.dependencies = append(job.dependencies, crt1Job) } ok = true return job, func() { once.Do(unlock) }, nil } ================================================ FILE: builder/lld.cpp ================================================ //go:build byollvm // This file provides C wrappers for liblld. #include #include LLD_HAS_DRIVER(coff) LLD_HAS_DRIVER(elf) LLD_HAS_DRIVER(mingw) LLD_HAS_DRIVER(macho) LLD_HAS_DRIVER(wasm) static void configure() { #if _WIN64 // This is a hack to work around a hang in the LLD linker on Windows, with // -DLLVM_ENABLE_THREADS=ON. It has a similar effect as the -threads=1 // linker flag, but with support for the COFF linker. llvm::parallel::strategy = llvm::hardware_concurrency(1); #endif } extern "C" { bool tinygo_link(int argc, char **argv) { configure(); std::vector args(argv, argv + argc); lld::Result r = lld::lldMain(args, llvm::outs(), llvm::errs(), LLD_ALL_DRIVERS); return !r.retCode; } } // external "C" ================================================ FILE: builder/mingw-w64.go ================================================ package builder import ( "fmt" "io" "os" "path/filepath" "strings" "github.com/tinygo-org/tinygo/goenv" ) var libMinGW = Library{ name: "mingw-w64", makeHeaders: func(target, includeDir string) error { // copy _mingw.h srcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "mingw-w64") outf, err := os.Create(includeDir + "/_mingw.h") if err != nil { return err } defer outf.Close() inf, err := os.Open(srcDir + "/mingw-w64-headers/crt/_mingw.h.in") if err != nil { return err } _, err = io.Copy(outf, inf) return err }, sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64") }, cflags: func(target, headerPath string) []string { mingwDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64") flags := []string{ "-nostdlibinc", "-isystem", mingwDir + "/mingw-w64-crt/include", "-isystem", mingwDir + "/mingw-w64-headers/crt", "-isystem", mingwDir + "/mingw-w64-headers/include", "-I", mingwDir + "/mingw-w64-headers/defaults/include", "-I" + headerPath, } if strings.Split(target, "-")[0] == "i386" { flags = append(flags, "-D__MSVCRT_VERSION__=0x700", // Microsoft Visual C++ .NET 2002 "-D_WIN32_WINNT=0x0501", // target Windows XP "-D_CRTBLD", "-Wno-pragma-pack", ) } return flags }, librarySources: func(target string, _ bool) ([]string, error) { // These files are needed so that printf and the like are supported. var sources []string if strings.Split(target, "-")[0] == "i386" { // Old 32-bit x86 systems use msvcrt.dll. sources = []string{ "mingw-w64-crt/crt/pseudo-reloc.c", "mingw-w64-crt/gdtoa/dmisc.c", "mingw-w64-crt/gdtoa/gdtoa.c", "mingw-w64-crt/gdtoa/gmisc.c", "mingw-w64-crt/gdtoa/misc.c", "mingw-w64-crt/math/x86/exp2.S", "mingw-w64-crt/math/x86/trunc.S", "mingw-w64-crt/misc/___mb_cur_max_func.c", "mingw-w64-crt/misc/lc_locale_func.c", "mingw-w64-crt/misc/mbrtowc.c", "mingw-w64-crt/misc/strnlen.c", "mingw-w64-crt/misc/wcrtomb.c", "mingw-w64-crt/misc/wcsnlen.c", "mingw-w64-crt/stdio/acrt_iob_func.c", "mingw-w64-crt/stdio/mingw_lock.c", "mingw-w64-crt/stdio/mingw_pformat.c", "mingw-w64-crt/stdio/mingw_vfprintf.c", "mingw-w64-crt/stdio/mingw_vsnprintf.c", } } else { // Anything somewhat modern (amd64, arm64) uses UCRT. sources = []string{ "mingw-w64-crt/stdio/ucrt_fprintf.c", "mingw-w64-crt/stdio/ucrt_fwprintf.c", "mingw-w64-crt/stdio/ucrt_printf.c", "mingw-w64-crt/stdio/ucrt_snprintf.c", "mingw-w64-crt/stdio/ucrt_sprintf.c", "mingw-w64-crt/stdio/ucrt_vfprintf.c", "mingw-w64-crt/stdio/ucrt_vprintf.c", "mingw-w64-crt/stdio/ucrt_vsnprintf.c", "mingw-w64-crt/stdio/ucrt_vsprintf.c", } } return sources, nil }, } // makeMinGWExtraLibs returns a slice of jobs to import the correct .dll // libraries. This is done by converting input .def files to .lib files which // can then be linked as usual. // // TODO: cache the result. At the moment, it costs a few hundred milliseconds to // compile these files. func makeMinGWExtraLibs(tmpdir, goarch string) []*compileJob { var jobs []*compileJob root := goenv.Get("TINYGOROOT") var libs []string if goarch == "386" { libs = []string{ // x86 uses msvcrt.dll instead of UCRT for compatibility with old // Windows versions. "advapi32.def.in", "kernel32.def.in", "msvcrt.def.in", } } else { // Use the modernized UCRT on new systems. // Normally all the api-ms-win-crt-*.def files are all compiled to a // single .lib file. But to simplify things, we're going to leave them // as separate files. libs = []string{ "advapi32.def.in", "kernel32.def.in", "api-ms-win-crt-conio-l1-1-0.def", "api-ms-win-crt-convert-l1-1-0.def.in", "api-ms-win-crt-environment-l1-1-0.def", "api-ms-win-crt-filesystem-l1-1-0.def", "api-ms-win-crt-heap-l1-1-0.def", "api-ms-win-crt-locale-l1-1-0.def", "api-ms-win-crt-math-l1-1-0.def.in", "api-ms-win-crt-multibyte-l1-1-0.def", "api-ms-win-crt-private-l1-1-0.def.in", "api-ms-win-crt-process-l1-1-0.def", "api-ms-win-crt-runtime-l1-1-0.def.in", "api-ms-win-crt-stdio-l1-1-0.def", "api-ms-win-crt-string-l1-1-0.def", "api-ms-win-crt-time-l1-1-0.def", "api-ms-win-crt-utility-l1-1-0.def", } } for _, name := range libs { outpath := filepath.Join(tmpdir, filepath.Base(name)+".lib") inpath := filepath.Join(root, "lib/mingw-w64/mingw-w64-crt/lib-common/"+name) job := &compileJob{ description: "create lib file " + inpath, result: outpath, run: func(job *compileJob) error { defpath := inpath var archDef, emulation string switch goarch { case "386": archDef = "-DDEF_I386" emulation = "i386pe" case "amd64": archDef = "-DDEF_X64" emulation = "i386pep" case "arm64": archDef = "-DDEF_ARM64" emulation = "arm64pe" default: return fmt.Errorf("unsupported architecture for mingw-w64: %s", goarch) } if strings.HasSuffix(inpath, ".in") { // .in files need to be preprocessed by a preprocessor (-E) // first. defpath = outpath + ".def" err := runCCompiler("-E", "-x", "c", "-Wp,-w", "-P", archDef, "-DDATA", "-o", defpath, inpath, "-I"+goenv.Get("TINYGOROOT")+"/lib/mingw-w64/mingw-w64-crt/def-include/") if err != nil { return err } } return link("ld.lld", "-m", emulation, "-o", outpath, defpath) }, } jobs = append(jobs, job) } return jobs } ================================================ FILE: builder/musl.go ================================================ package builder import ( "bytes" "fmt" "os" "path/filepath" "regexp" "strings" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/goenv" ) // Create the alltypes.h file from the appropriate alltypes.h.in files. func buildMuslAllTypes(arch, muslDir, outputBitsDir string) error { // Create the file alltypes.h. f, err := os.Create(filepath.Join(outputBitsDir, "alltypes.h")) if err != nil { return err } infiles := []string{ filepath.Join(muslDir, "arch", arch, "bits", "alltypes.h.in"), filepath.Join(muslDir, "include", "alltypes.h.in"), } for _, infile := range infiles { data, err := os.ReadFile(infile) if err != nil { return err } lines := strings.Split(string(data), "\n") for _, line := range lines { if strings.HasPrefix(line, "TYPEDEF ") { matches := regexp.MustCompile(`TYPEDEF (.*) ([^ ]*);`).FindStringSubmatch(line) value := matches[1] name := matches[2] line = fmt.Sprintf("#if defined(__NEED_%s) && !defined(__DEFINED_%s)\ntypedef %s %s;\n#define __DEFINED_%s\n#endif\n", name, name, value, name, name) } if strings.HasPrefix(line, "STRUCT ") { matches := regexp.MustCompile(`STRUCT * ([^ ]*) (.*);`).FindStringSubmatch(line) name := matches[1] value := matches[2] line = fmt.Sprintf("#if defined(__NEED_struct_%s) && !defined(__DEFINED_struct_%s)\nstruct %s %s;\n#define __DEFINED_struct_%s\n#endif\n", name, name, name, value, name) } _, err := f.WriteString(line + "\n") if err != nil { return err } } } return f.Close() } var libMusl = Library{ name: "musl", makeHeaders: func(target, includeDir string) error { bits := filepath.Join(includeDir, "bits") err := os.Mkdir(bits, 0777) if err != nil { return err } arch := compileopts.MuslArchitecture(target) muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "musl") err = buildMuslAllTypes(arch, muslDir, bits) if err != nil { return err } // Create the file syscall.h. f, err := os.Create(filepath.Join(bits, "syscall.h")) if err != nil { return err } data, err := os.ReadFile(filepath.Join(muslDir, "arch", arch, "bits", "syscall.h.in")) if err != nil { return err } _, err = f.Write(bytes.ReplaceAll(data, []byte("__NR_"), []byte("SYS_"))) if err != nil { return err } f.Close() return nil }, cflags: func(target, headerPath string) []string { arch := compileopts.MuslArchitecture(target) muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl") cflags := []string{ "-std=c99", // same as in musl "-D_XOPEN_SOURCE=700", // same as in musl // Musl triggers some warnings and we don't want to show any // warnings while compiling (only errors or silence), so disable // specific warnings that are triggered in musl. "-Werror", "-Wno-logical-op-parentheses", "-Wno-bitwise-op-parentheses", "-Wno-shift-op-parentheses", "-Wno-ignored-attributes", "-Wno-string-plus-int", "-Wno-ignored-pragmas", "-Wno-tautological-constant-out-of-range-compare", "-Wno-deprecated-non-prototype", "-Wno-format", "-Wno-parentheses", "-Qunused-arguments", // Select include dirs. Don't include standard library includes // (that would introduce host dependencies and other complications), // but do include all the include directories expected by musl. "-nostdlibinc", "-I" + muslDir + "/arch/" + arch, "-I" + muslDir + "/arch/generic", "-I" + muslDir + "/src/include", "-I" + muslDir + "/src/internal", "-I" + headerPath, "-I" + muslDir + "/include", "-fno-stack-protector", } return cflags }, sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl/src") }, librarySources: func(target string, _ bool) ([]string, error) { arch := compileopts.MuslArchitecture(target) globs := []string{ "conf/*.c", "ctype/*.c", "env/*.c", "errno/*.c", "exit/*.c", "fcntl/*.c", "internal/defsysinfo.c", "internal/intscan.c", "internal/libc.c", "internal/shgetc.c", "internal/syscall_ret.c", "internal/vdso.c", "legacy/*.c", "locale/*.c", "linux/*.c", "locale/*.c", "malloc/*.c", "malloc/mallocng/*.c", "mman/*.c", "math/*.c", "misc/*.c", "multibyte/*.c", "sched/*.c", "signal/" + arch + "/*.s", "signal/*.c", "stdio/*.c", "stdlib/*.c", "string/*.c", "thread/" + arch + "/*.s", "thread/*.c", "time/*.c", "unistd/*.c", "process/*.c", } if arch == "arm" { // These files need to be added to the start for some reason. globs = append([]string{"thread/arm/*.c"}, globs...) } if arch != "aarch64" && arch != "mips" { //aarch64 and mips have no architecture specific code, either they // are not supported or don't need any? globs = append([]string{"process/" + arch + "/*.s"}, globs...) } var sources []string seenSources := map[string]struct{}{} basepath := goenv.Get("TINYGOROOT") + "/lib/musl/src/" for _, pattern := range globs { matches, err := filepath.Glob(basepath + pattern) if err != nil { // From the documentation: // > Glob ignores file system errors such as I/O errors reading // > directories. The only possible returned error is // > ErrBadPattern, when pattern is malformed. // So the only possible error is when the (statically defined) // pattern is wrong. In other words, a programming bug. return nil, fmt.Errorf("musl: could not glob source dirs: %w", err) } if len(matches) == 0 { return nil, fmt.Errorf("musl: did not find any files for pattern %#v", pattern) } for _, match := range matches { relpath, err := filepath.Rel(basepath, match) if err != nil { // Not sure if this is even possible. return nil, err } // Make sure architecture specific files override generic files. id := strings.ReplaceAll(relpath, "/"+arch+"/", "/") if _, ok := seenSources[id]; ok { // Already seen this file, skipping this (generic) file. continue } seenSources[id] = struct{}{} sources = append(sources, relpath) } } return sources, nil }, crt1Source: "../crt/crt1.c", // lib/musl/crt/crt1.c } ================================================ FILE: builder/nrfutil.go ================================================ package builder import ( "archive/zip" "bytes" "encoding/binary" "encoding/json" "os" "github.com/sigurn/crc16" "github.com/tinygo-org/tinygo/compileopts" ) // Structure of the manifest.json file. type jsonManifest struct { Manifest struct { Application struct { BinaryFile string `json:"bin_file"` DataFile string `json:"dat_file"` InitPacketData nrfInitPacket `json:"init_packet_data"` } `json:"application"` DFUVersion float64 `json:"dfu_version"` // yes, this is a JSON number, not a string } `json:"manifest"` } // Structure of the init packet. // Source: // https://github.com/adafruit/Adafruit_nRF52_Bootloader/blob/master/lib/sdk11/components/libraries/bootloader_dfu/dfu_init.h#L47-L57 type nrfInitPacket struct { ApplicationVersion uint32 `json:"application_version"` DeviceRevision uint16 `json:"device_revision"` DeviceType uint16 `json:"device_type"` FirmwareCRC16 uint16 `json:"firmware_crc16"` SoftDeviceRequired []uint16 `json:"softdevice_req"` // this is actually a variable length array } // Create the init packet (the contents of application.dat). func (p nrfInitPacket) createInitPacket() []byte { buf := &bytes.Buffer{} binary.Write(buf, binary.LittleEndian, p.DeviceType) // uint16_t device_type; binary.Write(buf, binary.LittleEndian, p.DeviceRevision) // uint16_t device_rev; binary.Write(buf, binary.LittleEndian, p.ApplicationVersion) // uint32_t app_version; binary.Write(buf, binary.LittleEndian, uint16(len(p.SoftDeviceRequired))) // uint16_t softdevice_len; binary.Write(buf, binary.LittleEndian, p.SoftDeviceRequired) // uint16_t softdevice[1]; binary.Write(buf, binary.LittleEndian, p.FirmwareCRC16) return buf.Bytes() } // Make a Nordic DFU firmware image from an ELF file. func makeDFUFirmwareImage(options *compileopts.Options, infile, outfile string) error { // Read ELF file as input and convert it to a binary image file. _, data, err := extractROM(infile) if err != nil { return err } // Create the zip file in memory. // It won't be very large anyway. buf := &bytes.Buffer{} w := zip.NewWriter(buf) // Write the application binary to the zip file. binw, err := w.Create("application.bin") if err != nil { return err } _, err = binw.Write(data) if err != nil { return err } // Create the init packet. initPacket := nrfInitPacket{ ApplicationVersion: 0xffff_ffff, // appears to be unused by the Adafruit bootloader DeviceRevision: 0xffff, // DFU_DEVICE_REVISION_EMPTY DeviceType: 0x0052, // ADAFRUIT_DEVICE_TYPE FirmwareCRC16: crc16.Checksum(data, crc16.MakeTable(crc16.CRC16_CCITT_FALSE)), SoftDeviceRequired: []uint16{0xfffe}, // DFU_SOFTDEVICE_ANY } // Write the init packet to the zip file. datw, err := w.Create("application.dat") if err != nil { return err } _, err = datw.Write(initPacket.createInitPacket()) if err != nil { return err } // Create the JSON manifest. manifest := &jsonManifest{} manifest.Manifest.Application.BinaryFile = "application.bin" manifest.Manifest.Application.DataFile = "application.dat" manifest.Manifest.Application.InitPacketData = initPacket manifest.Manifest.DFUVersion = 0.5 // Write the JSON manifest to the file. jsonw, err := w.Create("manifest.json") if err != nil { return err } enc := json.NewEncoder(jsonw) enc.SetIndent("", " ") err = enc.Encode(manifest) if err != nil { return err } // Finish the zip file. err = w.Close() if err != nil { return err } return os.WriteFile(outfile, buf.Bytes(), 0o666) } ================================================ FILE: builder/objcopy.go ================================================ package builder import ( "debug/elf" "io" "os" "sort" "github.com/marcinbor85/gohex" ) // maxPadBytes is the maximum allowed bytes to be padded in a rom extraction // this value is currently defined by Nintendo Switch Page Alignment (4096 bytes) const maxPadBytes = 4095 // objcopyError is an error returned by functions that act like objcopy. type objcopyError struct { Op string Err error } func (e objcopyError) Error() string { if e.Err == nil { return e.Op } return e.Op + ": " + e.Err.Error() } type progSlice []*elf.Prog func (s progSlice) Len() int { return len(s) } func (s progSlice) Less(i, j int) bool { return s[i].Paddr < s[j].Paddr } func (s progSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // extractROM extracts a firmware image and the first load address from the // given ELF file. It tries to emulate the behavior of objcopy. func extractROM(path string) (uint64, []byte, error) { f, err := elf.Open(path) if err != nil { return 0, nil, objcopyError{"failed to open ELF file to extract text segment", err} } defer f.Close() // The GNU objcopy command does the following for firmware extraction (from // the man page): // > When objcopy generates a raw binary file, it will essentially produce a // > memory dump of the contents of the input object file. All symbols and // > relocation information will be discarded. The memory dump will start at // > the load address of the lowest section copied into the output file. // Find the lowest section address. startAddr := ^uint64(0) for _, section := range f.Sections { if section.Type != elf.SHT_PROGBITS || section.Flags&elf.SHF_ALLOC == 0 { continue } if section.Addr < startAddr { startAddr = section.Addr } } progs := make(progSlice, 0, 2) for _, prog := range f.Progs { if prog.Type != elf.PT_LOAD || prog.Filesz == 0 || prog.Off == 0 { continue } progs = append(progs, prog) } if len(progs) == 0 { return 0, nil, objcopyError{"file does not contain ROM segments: " + path, nil} } sort.Sort(progs) var rom []byte for _, prog := range progs { romEnd := progs[0].Paddr + uint64(len(rom)) if prog.Paddr > romEnd && prog.Paddr < romEnd+16 { // Sometimes, the linker seems to insert a bit of padding between // segments. Simply zero-fill these parts. rom = append(rom, make([]byte, prog.Paddr-romEnd)...) } if prog.Paddr != progs[0].Paddr+uint64(len(rom)) { diff := prog.Paddr - (progs[0].Paddr + uint64(len(rom))) if diff > maxPadBytes { return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil} } // Pad the difference rom = append(rom, make([]byte, diff)...) } data, err := io.ReadAll(prog.Open()) if err != nil { return 0, nil, objcopyError{"failed to extract segment from ELF file: " + path, err} } rom = append(rom, data...) } if progs[0].Paddr < startAddr { // The lowest memory address is before the first section. This means // that there is some extra data loaded at the start of the image that // should be discarded. // Example: ELF files where .text doesn't start at address 0 because // there is a bootloader at the start. return startAddr, rom[startAddr-progs[0].Paddr:], nil } else { return progs[0].Paddr, rom, nil } } // objcopy converts an ELF file to a different (simpler) output file format: // .bin or .hex. It extracts only the .text section. func objcopy(infile, outfile, binaryFormat string) error { f, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) if err != nil { return err } defer f.Close() // Read the .text segment. addr, data, err := extractROM(infile) if err != nil { return err } // Write to the file, in the correct format. switch binaryFormat { case "hex": // Intel hex file, includes the firmware start address. mem := gohex.NewMemory() err := mem.AddBinary(uint32(addr), data) if err != nil { return objcopyError{"failed to create .hex file", err} } return mem.DumpIntelHex(f, 16) case "bin": // The start address is not stored in raw firmware files (therefore you // should use .hex files in most cases). _, err := f.Write(data) return err default: panic("unreachable") } } ================================================ FILE: builder/picolibc.go ================================================ package builder import ( "os" "path/filepath" "strings" "github.com/tinygo-org/tinygo/goenv" ) // libPicolibc is a C library for bare metal embedded devices. It was originally // based on newlib. var libPicolibc = Library{ name: "picolibc", makeHeaders: func(target, includeDir string) error { f, err := os.Create(filepath.Join(includeDir, "picolibc.h")) if err != nil { return err } return f.Close() }, cflags: func(target, headerPath string) []string { newlibDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib") return []string{ "-Werror", "-Wall", "-std=gnu11", "-D_COMPILING_NEWLIB", "-D_HAVE_ALIAS_ATTRIBUTE", "-DTINY_STDIO", "-DPOSIX_IO", "-DFORMAT_DEFAULT_INTEGER", // use __i_vfprintf and __i_vfscanf by default "-D_IEEE_LIBM", "-D__OBSOLETE_MATH_FLOAT=1", // use old math code that doesn't expect a FPU "-D__OBSOLETE_MATH_DOUBLE=0", "-D_WANT_IO_C99_FORMATS", "-D__PICOLIBC_ERRNO_FUNCTION=__errno_location", "-nostdlibinc", "-isystem", newlibDir + "/libc/include", "-I" + newlibDir + "/libc/tinystdio", "-I" + newlibDir + "/libm/common", "-I" + headerPath, } }, sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib") }, librarySources: func(target string, _ bool) ([]string, error) { sources := append([]string(nil), picolibcSources...) if !strings.HasPrefix(target, "avr") { // Small chips without long jumps can't compile many files (printf, // pow, etc). Therefore exclude those source files for those chips. // Unfortunately it's difficult to exclude only some chips, so this // excludes those files on all AVR chips for now. // More information: // https://github.com/llvm/llvm-project/issues/67042 sources = append(sources, picolibcSourcesLarge...) } return sources, nil }, } var picolibcSources = []string{ "../../picolibc-stdio.c", "libc/string/bcmp.c", "libc/string/bcopy.c", "libc/string/bzero.c", "libc/string/explicit_bzero.c", "libc/string/ffsl.c", "libc/string/ffsll.c", "libc/string/fls.c", "libc/string/flsl.c", "libc/string/flsll.c", "libc/string/gnu_basename.c", "libc/string/index.c", "libc/string/memccpy.c", "libc/string/memchr.c", "libc/string/memcmp.c", "libc/string/memcpy.c", "libc/string/memmem.c", "libc/string/memmove.c", "libc/string/mempcpy.c", "libc/string/memrchr.c", "libc/string/memset.c", "libc/string/rawmemchr.c", "libc/string/rindex.c", "libc/string/stpcpy.c", "libc/string/stpncpy.c", "libc/string/strcasecmp.c", "libc/string/strcasecmp_l.c", "libc/string/strcasestr.c", "libc/string/strcat.c", "libc/string/strchr.c", "libc/string/strchrnul.c", "libc/string/strcmp.c", "libc/string/strcoll.c", "libc/string/strcoll_l.c", "libc/string/strcpy.c", "libc/string/strcspn.c", "libc/string/strdup.c", "libc/string/strerror.c", "libc/string/strerror_r.c", "libc/string/strlcat.c", "libc/string/strlcpy.c", "libc/string/strlen.c", "libc/string/strlwr.c", "libc/string/strncasecmp.c", "libc/string/strncasecmp_l.c", "libc/string/strncat.c", "libc/string/strncmp.c", "libc/string/strncpy.c", "libc/string/strndup.c", "libc/string/strnlen.c", "libc/string/strnstr.c", "libc/string/strpbrk.c", "libc/string/strrchr.c", "libc/string/strsep.c", "libc/string/strsignal.c", "libc/string/strspn.c", "libc/string/strstr.c", "libc/string/strtok.c", "libc/string/strtok_r.c", "libc/string/strupr.c", "libc/string/strverscmp.c", "libc/string/strxfrm.c", "libc/string/strxfrm_l.c", "libc/string/swab.c", "libc/string/timingsafe_bcmp.c", "libc/string/timingsafe_memcmp.c", "libc/string/u_strerr.c", "libc/string/wcpcpy.c", "libc/string/wcpncpy.c", "libc/string/wcscasecmp.c", "libc/string/wcscasecmp_l.c", "libc/string/wcscat.c", "libc/string/wcschr.c", "libc/string/wcscmp.c", "libc/string/wcscoll.c", "libc/string/wcscoll_l.c", "libc/string/wcscpy.c", "libc/string/wcscspn.c", "libc/string/wcsdup.c", "libc/string/wcslcat.c", "libc/string/wcslcpy.c", "libc/string/wcslen.c", "libc/string/wcsncasecmp.c", "libc/string/wcsncasecmp_l.c", "libc/string/wcsncat.c", "libc/string/wcsncmp.c", "libc/string/wcsncpy.c", "libc/string/wcsnlen.c", "libc/string/wcspbrk.c", "libc/string/wcsrchr.c", "libc/string/wcsspn.c", "libc/string/wcsstr.c", "libc/string/wcstok.c", "libc/string/wcswidth.c", "libc/string/wcsxfrm.c", "libc/string/wcsxfrm_l.c", "libc/string/wcwidth.c", "libc/string/wmemchr.c", "libc/string/wmemcmp.c", "libc/string/wmemcpy.c", "libc/string/wmemmove.c", "libc/string/wmempcpy.c", "libc/string/wmemset.c", "libc/string/xpg_strerror_r.c", } // Parts of picolibc that are too large for small AVRs. var picolibcSourcesLarge = []string{ // srcs_tinystdio "libc/tinystdio/asprintf.c", "libc/tinystdio/bufio.c", "libc/tinystdio/clearerr.c", "libc/tinystdio/ecvt_r.c", "libc/tinystdio/ecvt.c", "libc/tinystdio/ecvtf_r.c", "libc/tinystdio/ecvtf.c", "libc/tinystdio/fcvt.c", "libc/tinystdio/fcvt_r.c", "libc/tinystdio/fcvtf.c", "libc/tinystdio/fcvtf_r.c", "libc/tinystdio/gcvt.c", "libc/tinystdio/gcvtf.c", "libc/tinystdio/fclose.c", "libc/tinystdio/fdevopen.c", "libc/tinystdio/feof.c", "libc/tinystdio/ferror.c", "libc/tinystdio/fflush.c", "libc/tinystdio/fgetc.c", "libc/tinystdio/fgets.c", "libc/tinystdio/fileno.c", "libc/tinystdio/filestrget.c", "libc/tinystdio/filestrput.c", "libc/tinystdio/filestrputalloc.c", "libc/tinystdio/fmemopen.c", "libc/tinystdio/fprintf.c", "libc/tinystdio/fputc.c", "libc/tinystdio/fputs.c", "libc/tinystdio/fread.c", //"libc/tinystdio/freopen.c", // crashes with AVR, see: https://github.com/picolibc/picolibc/pull/369 "libc/tinystdio/fscanf.c", "libc/tinystdio/fseek.c", "libc/tinystdio/fseeko.c", "libc/tinystdio/ftell.c", "libc/tinystdio/ftello.c", "libc/tinystdio/fwrite.c", "libc/tinystdio/getchar.c", "libc/tinystdio/gets.c", "libc/tinystdio/matchcaseprefix.c", "libc/tinystdio/mktemp.c", "libc/tinystdio/perror.c", "libc/tinystdio/printf.c", "libc/tinystdio/putchar.c", "libc/tinystdio/puts.c", "libc/tinystdio/rewind.c", "libc/tinystdio/scanf.c", "libc/tinystdio/setbuf.c", "libc/tinystdio/setbuffer.c", "libc/tinystdio/setlinebuf.c", "libc/tinystdio/setvbuf.c", "libc/tinystdio/snprintf.c", "libc/tinystdio/sprintf.c", "libc/tinystdio/snprintfd.c", "libc/tinystdio/snprintff.c", "libc/tinystdio/sprintff.c", "libc/tinystdio/sprintfd.c", "libc/tinystdio/sscanf.c", "libc/tinystdio/strfromf.c", "libc/tinystdio/strfromd.c", "libc/tinystdio/strtof.c", "libc/tinystdio/strtof_l.c", "libc/tinystdio/strtod.c", "libc/tinystdio/strtod_l.c", "libc/tinystdio/ungetc.c", "libc/tinystdio/vasprintf.c", "libc/tinystdio/vfiprintf.c", "libc/tinystdio/vfprintf.c", "libc/tinystdio/vfprintff.c", "libc/tinystdio/vfscanf.c", "libc/tinystdio/vfiscanf.c", "libc/tinystdio/vfscanff.c", "libc/tinystdio/vprintf.c", "libc/tinystdio/vscanf.c", "libc/tinystdio/vsscanf.c", "libc/tinystdio/vsnprintf.c", "libc/tinystdio/vsprintf.c", "libm/common/sf_finite.c", "libm/common/sf_copysign.c", "libm/common/sf_modf.c", "libm/common/sf_scalbn.c", "libm/common/sf_cbrt.c", "libm/common/sf_exp10.c", "libm/common/sf_expm1.c", "libm/common/sf_ilogb.c", "libm/common/sf_infinity.c", "libm/common/sf_isinf.c", "libm/common/sf_isinff.c", "libm/common/sf_isnan.c", "libm/common/sf_isnanf.c", "libm/common/sf_issignaling.c", "libm/common/sf_log1p.c", "libm/common/sf_nan.c", "libm/common/sf_nextafter.c", "libm/common/sf_pow10.c", "libm/common/sf_rint.c", "libm/common/sf_logb.c", "libm/common/sf_fdim.c", "libm/common/sf_fma.c", "libm/common/sf_fmax.c", "libm/common/sf_fmin.c", "libm/common/sf_fpclassify.c", "libm/common/sf_lrint.c", "libm/common/sf_llrint.c", "libm/common/sf_lround.c", "libm/common/sf_llround.c", "libm/common/sf_nearbyint.c", "libm/common/sf_remquo.c", "libm/common/sf_round.c", "libm/common/sf_scalbln.c", "libm/common/sf_trunc.c", "libm/common/sf_exp.c", "libm/common/sf_exp2.c", "libm/common/sf_exp2_data.c", "libm/common/sf_log.c", "libm/common/sf_log_data.c", "libm/common/sf_log2.c", "libm/common/sf_log2_data.c", "libm/common/sf_pow_log2_data.c", "libm/common/sf_pow.c", "libm/common/s_finite.c", "libm/common/s_copysign.c", "libm/common/s_modf.c", "libm/common/s_scalbn.c", "libm/common/s_cbrt.c", "libm/common/s_exp10.c", "libm/common/s_expm1.c", "libm/common/s_ilogb.c", "libm/common/s_infinity.c", "libm/common/s_iseqsig.c", "libm/common/s_isinf.c", "libm/common/s_isinfd.c", "libm/common/s_isnan.c", "libm/common/s_isnand.c", "libm/common/s_issignaling.c", "libm/common/s_log1p.c", "libm/common/s_nan.c", "libm/common/s_nextafter.c", "libm/common/s_pow10.c", "libm/common/s_rint.c", "libm/common/s_logb.c", "libm/common/s_log2.c", "libm/common/s_fdim.c", "libm/common/s_fma.c", "libm/common/s_fmax.c", "libm/common/s_fmin.c", "libm/common/s_fpclassify.c", "libm/common/s_getpayload.c", "libm/common/s_lrint.c", "libm/common/s_llrint.c", "libm/common/s_lround.c", "libm/common/s_llround.c", "libm/common/s_nearbyint.c", "libm/common/s_remquo.c", "libm/common/s_round.c", "libm/common/s_scalbln.c", "libm/common/s_signbit.c", "libm/common/s_trunc.c", "libm/common/exp.c", "libm/common/exp2.c", "libm/common/exp_data.c", "libm/common/math_err_with_errno.c", "libm/common/math_err_uflow.c", "libm/common/math_err_oflow.c", "libm/common/math_err_divzero.c", "libm/common/math_err_invalid.c", "libm/common/math_err_may_uflow.c", "libm/common/math_err_check_uflow.c", "libm/common/math_err_check_oflow.c", "libm/common/math_errf_divzerof.c", "libm/common/math_errf_invalidf.c", "libm/common/math_errf_may_uflowf.c", "libm/common/math_errf_oflowf.c", "libm/common/math_errf_uflowf.c", "libm/common/math_errf_with_errnof.c", "libm/common/math_inexact.c", "libm/common/math_inexactf.c", "libm/common/log.c", "libm/common/log_data.c", "libm/common/log2.c", "libm/common/log2_data.c", "libm/common/pow.c", "libm/common/pow_log_data.c", "libm/math/k_cos.c", "libm/math/k_rem_pio2.c", "libm/math/k_sin.c", "libm/math/k_tan.c", "libm/math/kf_cos.c", "libm/math/kf_rem_pio2.c", "libm/math/kf_sin.c", "libm/math/kf_tan.c", "libm/math/s_acos.c", "libm/math/s_acosh.c", "libm/math/s_asin.c", "libm/math/s_asinh.c", "libm/math/s_atan.c", "libm/math/s_atan2.c", "libm/math/s_atanh.c", "libm/math/s_ceil.c", "libm/math/s_cos.c", "libm/math/s_cosh.c", "libm/math/s_drem.c", "libm/math/s_erf.c", "libm/math/s_exp.c", "libm/math/s_exp2.c", "libm/math/s_fabs.c", "libm/math/s_floor.c", "libm/math/s_fmod.c", "libm/math/s_frexp.c", "libm/math/s_gamma.c", "libm/math/s_hypot.c", "libm/math/s_j0.c", "libm/math/s_j1.c", "libm/math/s_jn.c", "libm/math/s_lgamma.c", "libm/math/s_log.c", "libm/math/s_log10.c", "libm/math/s_pow.c", "libm/math/s_rem_pio2.c", "libm/math/s_remainder.c", "libm/math/s_scalb.c", "libm/math/s_signif.c", "libm/math/s_sin.c", "libm/math/s_sincos.c", "libm/math/s_sinh.c", "libm/math/s_sqrt.c", "libm/math/s_tan.c", "libm/math/s_tanh.c", "libm/math/s_tgamma.c", "libm/math/sf_acos.c", "libm/math/sf_acosh.c", "libm/math/sf_asin.c", "libm/math/sf_asinh.c", "libm/math/sf_atan.c", "libm/math/sf_atan2.c", "libm/math/sf_atanh.c", "libm/math/sf_ceil.c", "libm/math/sf_cos.c", "libm/math/sf_cosh.c", "libm/math/sf_drem.c", "libm/math/sf_erf.c", "libm/math/sf_exp.c", "libm/math/sf_exp2.c", "libm/math/sf_fabs.c", "libm/math/sf_floor.c", "libm/math/sf_fmod.c", "libm/math/sf_frexp.c", "libm/math/sf_gamma.c", "libm/math/sf_hypot.c", "libm/math/sf_j0.c", "libm/math/sf_j1.c", "libm/math/sf_jn.c", "libm/math/sf_lgamma.c", "libm/math/sf_log.c", "libm/math/sf_log10.c", "libm/math/sf_log2.c", "libm/math/sf_pow.c", "libm/math/sf_rem_pio2.c", "libm/math/sf_remainder.c", "libm/math/sf_scalb.c", "libm/math/sf_signif.c", "libm/math/sf_sin.c", "libm/math/sf_sincos.c", "libm/math/sf_sinh.c", "libm/math/sf_sqrt.c", "libm/math/sf_tan.c", "libm/math/sf_tanh.c", "libm/math/sf_tgamma.c", "libm/math/sr_lgamma.c", "libm/math/srf_lgamma.c", } ================================================ FILE: builder/size-report.go ================================================ package builder import ( _ "embed" "fmt" "html/template" "os" ) //go:embed size-report.html var sizeReportBase string func writeSizeReport(sizes *programSize, filename, pkgName string) error { tmpl, err := template.New("report").Parse(sizeReportBase) if err != nil { return err } f, err := os.Create(filename) if err != nil { return fmt.Errorf("could not open report file: %w", err) } defer f.Close() // Prepare data for the report. type sizeLine struct { Name string Size *packageSize } programData := []sizeLine{} for _, name := range sizes.sortedPackageNames() { pkgSize := sizes.Packages[name] programData = append(programData, sizeLine{ Name: name, Size: pkgSize, }) } sizeTotal := map[string]uint64{ "code": sizes.Code, "rodata": sizes.ROData, "data": sizes.Data, "bss": sizes.BSS, "flash": sizes.Flash(), } // Write the report. err = tmpl.Execute(f, map[string]any{ "pkgName": pkgName, "sizes": programData, "sizeTotal": sizeTotal, }) if err != nil { return fmt.Errorf("could not create report file: %w", err) } return nil } ================================================ FILE: builder/size-report.html ================================================ Size Report for {{.pkgName}}

Size Report for {{.pkgName}}

How much space is used by Go packages, C libraries, and other bits to set up the program environment.

  • Code is the actual program code (machine code instructions).
  • Read-only data are read-only global variables. On most microcontrollers, these are stored in flash and do not take up any RAM.
  • Data are writable global variables with a non-zero initializer. On microcontrollers, they are copied from flash to RAM on reset.
  • BSS are writable global variables that are zero initialized. They do not take up any space in the binary, but do take up RAM. On microcontrollers, this area is zeroed on reset.

The binary size consists of code, read-only data, and data. On microcontrollers, this is exactly the size of the firmware image. On other systems, there is some extra overhead: binary metadata (headers of the ELF/MachO/COFF file), debug information, exception tables, symbol names, etc. Using -no-debug strips most of those.

Program breakdown

You can click on the rows below to see which files contribute to the binary size.

{{range $i, $pkg := .sizes}} {{range $filename, $sizes := .Size.Sub}} {{end}} {{end}}
Package Code Read-only data Data BSS Binary size
{{.Name}} {{.Size.Code}} {{.Size.ROData}} {{.Size.Data}} {{.Size.BSS}} {{.Size.Flash}}
{{if eq $filename ""}} (unknown file) {{else}} {{$filename}} {{end}} {{$sizes.Code}} {{$sizes.ROData}} {{$sizes.Data}} {{$sizes.BSS}} {{$sizes.Flash}}
Total {{.sizeTotal.code}} {{.sizeTotal.rodata}} {{.sizeTotal.data}} {{.sizeTotal.bss}} {{.sizeTotal.flash}}
================================================ FILE: builder/sizes.go ================================================ package builder import ( "bytes" "debug/dwarf" "debug/elf" "debug/macho" "debug/pe" "encoding/binary" "fmt" "io" "os" "path/filepath" "regexp" "runtime" "sort" "strings" "github.com/aykevl/go-wasm" "github.com/tinygo-org/tinygo/goenv" ) // Set to true to print extra debug logs. const sizesDebug = false // programSize contains size statistics per package of a compiled program. type programSize struct { Packages map[string]*packageSize Code uint64 ROData uint64 Data uint64 BSS uint64 } // sortedPackageNames returns the list of package names (ProgramSize.Packages) // sorted alphabetically. func (ps *programSize) sortedPackageNames() []string { names := make([]string, 0, len(ps.Packages)) for name := range ps.Packages { names = append(names, name) } sort.Strings(names) return names } // Flash usage in regular microcontrollers. func (ps *programSize) Flash() uint64 { return ps.Code + ps.ROData + ps.Data } // Static RAM usage in regular microcontrollers. func (ps *programSize) RAM() uint64 { return ps.Data + ps.BSS } // Return the package size information for a given package path, creating it if // it doesn't exist yet. func (ps *programSize) getPackage(path string) *packageSize { if field, ok := ps.Packages[path]; ok { return field } field := &packageSize{ Program: ps, Sub: map[string]*packageSize{}, } ps.Packages[path] = field return field } // packageSize contains the size of a package, calculated from the linked object // file. type packageSize struct { Program *programSize Code uint64 ROData uint64 Data uint64 BSS uint64 Sub map[string]*packageSize } // Flash usage in regular microcontrollers. func (ps *packageSize) Flash() uint64 { return ps.Code + ps.ROData + ps.Data } // Static RAM usage in regular microcontrollers. func (ps *packageSize) RAM() uint64 { return ps.Data + ps.BSS } // Flash usage in regular microcontrollers, as a percentage of the total flash // usage of the program. func (ps *packageSize) FlashPercent() float64 { return float64(ps.Flash()) / float64(ps.Program.Flash()) * 100 } // Add a single size data point to this package. // This must only be called while calculating package size, not afterwards. func (ps *packageSize) addSize(getField func(*packageSize, bool) *uint64, filename string, size uint64, isVariable bool) { if size == 0 { return } // Add size for the package. *getField(ps, isVariable) += size // Add size for file inside package. sub, ok := ps.Sub[filename] if !ok { sub = &packageSize{Program: ps.Program} ps.Sub[filename] = sub } *getField(sub, isVariable) += size } // A mapping of a single chunk of code or data to a file path. type addressLine struct { Address uint64 Length uint64 // length of this chunk Align uint64 // (maximum) alignment of this line File string // file path as stored in DWARF IsVariable bool // true if this is a variable (or constant), false if it is code } // Sections defined in the input file. This struct defines them in a // filetype-agnostic way but roughly follow the ELF types (.text, .data, .bss, // etc). type memorySection struct { Type memoryType Address uint64 Size uint64 Align uint64 } type memoryType int const ( memoryCode memoryType = iota + 1 memoryData memoryROData memoryBSS memoryStack ) func (t memoryType) String() string { return [...]string{ 0: "-", memoryCode: "code", memoryData: "data", memoryROData: "rodata", memoryBSS: "bss", memoryStack: "stack", }[t] } // Regular expressions to match particular symbol names. These are not stored as // DWARF variables because they have no mapping to source code global variables. var ( // Various globals that aren't a variable but nonetheless need to be stored // somewhere: // alloc: heap allocations during init interpretation // pack: data created when storing a constant in an interface for example // string: buffer behind strings packageSymbolRegexp = regexp.MustCompile(`\$(alloc|pack|string)(\.[0-9]+)?$`) ) // readProgramSizeFromDWARF reads the source location for each line of code and // each variable in the program, as far as this is stored in the DWARF debug // information. func readProgramSizeFromDWARF(data *dwarf.Data, codeOffset, codeAlignment uint64, skipTombstone bool) ([]addressLine, error) { r := data.Reader() var lines []*dwarf.LineFile var addresses []addressLine for { e, err := r.Next() if err != nil { return nil, err } if e == nil { break } switch e.Tag { case dwarf.TagCompileUnit: // Found a compile unit. // We can read the .debug_line section using it, which contains a // mapping for most instructions to their file/line/column - even // for inlined functions! lr, err := data.LineReader(e) if err != nil { return nil, err } lines = lr.Files() var lineEntry = dwarf.LineEntry{ EndSequence: true, } // Line tables are organized as sequences of line entries until an // end sequence. A single line table can contain multiple such // sequences. The last line entry is an EndSequence to indicate the // end. for { // Read the next .debug_line entry. prevLineEntry := lineEntry err := lr.Next(&lineEntry) if err != nil { if err == io.EOF { break } return nil, err } if prevLineEntry.EndSequence && lineEntry.Address == 0 && skipTombstone { // Tombstone value. This symbol has been removed, for // example by the --gc-sections linker flag. It is still // here in the debug information because the linker can't // just remove this reference. // Read until the next EndSequence so that this sequence is // skipped. // For more details, see (among others): // https://reviews.llvm.org/D84825 // The value 0 can however really occur in object files, // that typically start at address 0. So don't skip // tombstone values in object files (like when parsing MachO // files). for { err := lr.Next(&lineEntry) if err != nil { return nil, err } if lineEntry.EndSequence { break } } } if !prevLineEntry.EndSequence { // The chunk describes the code from prevLineEntry to // lineEntry. path := prevLineEntry.File.Name if runtime.GOOS == "windows" { // Work around a Clang bug on Windows: // https://github.com/llvm/llvm-project/issues/117317 path = strings.ReplaceAll(path, "\\\\", "\\") // wasi-libc likes to use forward slashes, but we // canonicalize everything to use backwards slashes as // is common on Windows. path = strings.ReplaceAll(path, "/", "\\") } line := addressLine{ Address: prevLineEntry.Address + codeOffset, Length: lineEntry.Address - prevLineEntry.Address, Align: codeAlignment, File: path, } if line.Length != 0 { addresses = append(addresses, line) } } } case dwarf.TagVariable: // Global variable (or constant). Most of these are not actually // stored in the binary, because they have been optimized out. Only // the ones with a location are still present. r.SkipChildren() file := e.AttrField(dwarf.AttrDeclFile) location := e.AttrField(dwarf.AttrLocation) globalType := e.AttrField(dwarf.AttrType) if file == nil || location == nil || globalType == nil { // Doesn't contain the requested information. continue } // Try to parse the location. While this could in theory be a very // complex expression, usually it's just a DW_OP_addr opcode // followed by an address. addr, err := readDWARFConstant(r.AddressSize(), location.Val.([]uint8)) if err != nil { continue // ignore the error, we don't know what to do with it } // Parse the type of the global variable, which (importantly) // contains the variable size. We're not interested in the type, // only in the size. typ, err := data.Type(globalType.Val.(dwarf.Offset)) if err != nil { return nil, err } // Read alignment, if it's stored as part of the debug information. var alignment uint64 if attr := e.AttrField(dwarf.AttrAlignment); attr != nil { alignment = uint64(attr.Val.(int64)) } addresses = append(addresses, addressLine{ Address: addr, Length: uint64(typ.Size()), Align: alignment, File: lines[file.Val.(int64)].Name, IsVariable: true, }) default: r.SkipChildren() } } return addresses, nil } // Parse a DWARF constant. For addresses, this is usually a very simple // expression. func readDWARFConstant(addressSize int, bytecode []byte) (uint64, error) { var addr uint64 for len(bytecode) != 0 { op := bytecode[0] bytecode = bytecode[1:] switch op { case 0x03: // DW_OP_addr switch addressSize { case 2: addr = uint64(binary.LittleEndian.Uint16(bytecode)) case 4: addr = uint64(binary.LittleEndian.Uint32(bytecode)) case 8: addr = binary.LittleEndian.Uint64(bytecode) default: panic("unexpected address size") } bytecode = bytecode[addressSize:] case 0x23: // DW_OP_plus_uconst offset, n := readULEB128(bytecode) addr += offset bytecode = bytecode[n:] default: return 0, fmt.Errorf("unknown DWARF opcode: 0x%x", op) } } return addr, nil } // Source: https://en.wikipedia.org/wiki/LEB128#Decode_unsigned_integer func readULEB128(buf []byte) (result uint64, n int) { var shift uint8 for { b := buf[n] n++ result |= uint64(b&0x7f) << shift if b&0x80 == 0 { break } shift += 7 } return } // Read a MachO object file and return a line table. // Also return an index from symbol name to start address in the line table. func readMachOSymbolAddresses(path string) (map[string]int, []addressLine, error) { // Some constants from mach-o/nlist.h // See: https://opensource.apple.com/source/xnu/xnu-7195.141.2/EXTERNAL_HEADERS/mach-o/nlist.h.auto.html const ( N_STAB = 0xe0 N_TYPE = 0x0e // bitmask for N_TYPE field N_SECT = 0xe // one of the possible type in the N_TYPE field ) // Read DWARF from the given object file. file, err := macho.Open(path) if err != nil { return nil, nil, err } defer file.Close() dwarf, err := file.DWARF() if err != nil { return nil, nil, err } lines, err := readProgramSizeFromDWARF(dwarf, 0, 0, false) if err != nil { return nil, nil, err } // Make a map from start addresses to indices in the line table (because the // line table is a slice, not a map). addressToLine := make(map[uint64]int, len(lines)) for i, line := range lines { if _, ok := addressToLine[line.Address]; ok { addressToLine[line.Address] = -1 continue } addressToLine[line.Address] = i } // Make a map that for each symbol gives the start index in the line table. addresses := make(map[string]int, len(addressToLine)) for _, symbol := range file.Symtab.Syms { if symbol.Type&N_STAB != 0 { continue // STABS entry, ignore } if symbol.Type&0x0e != N_SECT { continue // undefined symbol } if index, ok := addressToLine[symbol.Value]; ok && index >= 0 { if _, ok := addresses[symbol.Name]; ok { // There is a duplicate. Mark it as unavailable. addresses[symbol.Name] = -1 continue } addresses[symbol.Name] = index } } return addresses, lines, nil } // loadProgramSize calculate a program/data size breakdown of each package for a // given ELF file. // If the file doesn't contain DWARF debug information, the returned program // size will still have valid summaries but won't have complete size information // per package. func loadProgramSize(path string, packagePathMap map[string]string) (*programSize, error) { // Open the binary file. f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() // This stores all chunks of addresses found in the binary. var addresses []addressLine // Load the binary file, which could be in a number of file formats. var sections []memorySection if file, err := elf.NewFile(f); err == nil { var codeAlignment uint64 switch file.Machine { case elf.EM_ARM: codeAlignment = 4 // usually 2, but can be 4 } // Read DWARF information. The error is intentionally ignored. data, _ := file.DWARF() if data != nil { addresses, err = readProgramSizeFromDWARF(data, 0, codeAlignment, true) if err != nil { // However, _do_ report an error here. Something must have gone // wrong while trying to parse DWARF data. return nil, err } } // Read the ELF symbols for some more chunks of location information. // Some globals (such as strings) aren't stored in the DWARF debug // information and therefore need to be obtained in a different way. allSymbols, err := file.Symbols() if err != nil { return nil, err } for _, symbol := range allSymbols { symType := elf.ST_TYPE(symbol.Info) if symbol.Size == 0 { continue } if symType != elf.STT_FUNC && symType != elf.STT_OBJECT && symType != elf.STT_NOTYPE { continue } if symbol.Section >= elf.SHN_LORESERVE { // Not a regular section, so skip it. // One example is elf.SHN_ABS, which is used for symbols // declared with an absolute value such as the memset function // on the ESP32 which is defined in the mask ROM. continue } section := file.Sections[symbol.Section] if section.Flags&elf.SHF_ALLOC == 0 { continue } if packageSymbolRegexp.MatchString(symbol.Name) || symbol.Name == "__isr_vector" { addresses = append(addresses, addressLine{ Address: symbol.Value, Length: symbol.Size, File: symbol.Name, IsVariable: true, }) } } // Load allocated sections. for _, section := range file.Sections { if section.Flags&elf.SHF_ALLOC == 0 { continue } if section.Type == elf.SHT_NOBITS { if strings.HasPrefix(section.Name, ".stack") { // TinyGo emits stack sections on microcontroller using the // ".stack" name. // This is a bit ugly, but I don't think there is a way to // mark the stack section in a linker script. sections = append(sections, memorySection{ Address: section.Addr, Size: section.Size, Align: section.Addralign, Type: memoryStack, }) } else { // Regular .bss section. sections = append(sections, memorySection{ Address: section.Addr, Size: section.Size, Align: section.Addralign, Type: memoryBSS, }) } } else if section.Type == elf.SHT_PROGBITS && section.Flags&elf.SHF_EXECINSTR != 0 { // .text sections = append(sections, memorySection{ Address: section.Addr, Size: section.Size, Align: section.Addralign, Type: memoryCode, }) } else if section.Type == elf.SHT_PROGBITS && section.Flags&elf.SHF_WRITE != 0 { // .data sections = append(sections, memorySection{ Address: section.Addr, Size: section.Size, Align: section.Addralign, Type: memoryData, }) } else if section.Type == elf.SHT_PROGBITS { // .rodata sections = append(sections, memorySection{ Address: section.Addr, Size: section.Size, Align: section.Addralign, Type: memoryROData, }) } } } else if file, err := macho.NewFile(f); err == nil { // Read segments, for use while reading through sections. segments := map[string]*macho.Segment{} for _, load := range file.Loads { switch load := load.(type) { case *macho.Segment: segments[load.Name] = load } } // Read MachO sections. for _, section := range file.Sections { sectionType := section.Flags & 0xff sectionFlags := section.Flags >> 8 segment := segments[section.Seg] // For the constants used here, see: // https://github.com/llvm/llvm-project/blob/release/14.x/llvm/include/llvm/BinaryFormat/MachO.h if sectionFlags&0x800000 != 0 { // S_ATTR_PURE_INSTRUCTIONS // Section containing only instructions. sections = append(sections, memorySection{ Address: section.Addr, Size: uint64(section.Size), Align: uint64(section.Align), Type: memoryCode, }) } else if sectionType == 1 { // S_ZEROFILL // Section filled with zeroes on demand. sections = append(sections, memorySection{ Address: section.Addr, Size: uint64(section.Size), Align: uint64(section.Align), Type: memoryBSS, }) } else if segment.Maxprot&0b011 == 0b001 { // --r (read-only data) // Protection doesn't allow writes, so mark this section read-only. sections = append(sections, memorySection{ Address: section.Addr, Size: uint64(section.Size), Align: uint64(section.Align), Type: memoryROData, }) } else { // The rest is assumed to be regular data. sections = append(sections, memorySection{ Address: section.Addr, Size: uint64(section.Size), Align: uint64(section.Align), Type: memoryData, }) } } // Read DWARF information. // The data isn't stored directly in the binary as in most executable // formats. Instead, it is left in the object files that were used as a // basis for linking. The executable does however contain STABS debug // information that points to the source object file and is used by // debuggers. // For more information: // http://wiki.dwarfstd.org/index.php?title=Apple%27s_%22Lazy%22_DWARF_Scheme var objSymbolNames map[string]int var objAddresses []addressLine var previousSymbol macho.Symbol for _, symbol := range file.Symtab.Syms { // STABS constants, from mach-o/stab.h: // https://opensource.apple.com/source/xnu/xnu-7195.141.2/EXTERNAL_HEADERS/mach-o/stab.h.auto.html const ( N_GSYM = 0x20 N_FUN = 0x24 N_STSYM = 0x26 N_SO = 0x64 N_OSO = 0x66 ) if symbol.Type == N_OSO { // Found an object file. Now try to parse it. objSymbolNames, objAddresses, err = readMachOSymbolAddresses(symbol.Name) if err != nil && sizesDebug { // Errors are normally ignored. If there is an error, it's // simply treated as that the DWARF is not available. fmt.Fprintf(os.Stderr, "could not read DWARF from file %s: %s\n", symbol.Name, err) } } else if symbol.Type == N_FUN { // Found a function. // The way this is encoded is a bit weird. MachO symbols don't // have a length. What I've found is that the length is encoded // by first having a N_FUN symbol as usual, and then having a // symbol with a zero-length name that has the value not set to // the address of the symbol but to the length. So in order to // get both the address and the length, we look for a symbol // with a name followed by a symbol without a name. if symbol.Name == "" && previousSymbol.Type == N_FUN && previousSymbol.Name != "" { // Functions are encoded as many small chunks in the line // table (one or a few instructions per source line). But // the symbol length covers the whole symbols, over many // lines and possibly including inlined functions. So we // continue to iterate through the objAddresses slice until // we've found all the source lines that are part of this // symbol. address := previousSymbol.Value length := symbol.Value if index, ok := objSymbolNames[previousSymbol.Name]; ok && index >= 0 { for length > 0 { line := objAddresses[index] line.Address = address if line.Length > length { // Line extends beyond the end of te symbol? // Weird, shouldn't happen. break } addresses = append(addresses, line) index++ length -= line.Length address += line.Length } } } } else if symbol.Type == N_GSYM || symbol.Type == N_STSYM { // Global variables. if index, ok := objSymbolNames[symbol.Name]; ok { address := objAddresses[index] address.Address = symbol.Value addresses = append(addresses, address) } } previousSymbol = symbol } } else if file, err := pe.NewFile(f); err == nil { // Read DWARF information. The error is intentionally ignored. data, _ := file.DWARF() if data != nil { addresses, err = readProgramSizeFromDWARF(data, 0, 0, true) if err != nil { // However, _do_ report an error here. Something must have gone // wrong while trying to parse DWARF data. return nil, err } } // Read COFF sections. optionalHeader := file.OptionalHeader.(*pe.OptionalHeader64) for _, section := range file.Sections { // For more information: // https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_section_header const ( IMAGE_SCN_CNT_CODE = 0x00000020 IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040 IMAGE_SCN_MEM_DISCARDABLE = 0x02000000 IMAGE_SCN_MEM_READ = 0x40000000 IMAGE_SCN_MEM_WRITE = 0x80000000 ) if section.Characteristics&IMAGE_SCN_MEM_DISCARDABLE != 0 { // Debug sections, etc. continue } address := uint64(section.VirtualAddress) + optionalHeader.ImageBase if section.Characteristics&IMAGE_SCN_CNT_CODE != 0 { // .text sections = append(sections, memorySection{ Address: address, Size: uint64(section.VirtualSize), Type: memoryCode, }) } else if section.Characteristics&IMAGE_SCN_CNT_INITIALIZED_DATA != 0 { if section.Characteristics&IMAGE_SCN_MEM_WRITE != 0 { // .data sections = append(sections, memorySection{ Address: address, Size: uint64(section.Size), Type: memoryData, }) if section.Size < section.VirtualSize { // Equivalent of a .bss section. // Note: because of how the PE/COFF format is // structured, not all zero-initialized data is marked // as such. A portion may be at the end of the .data // section and is thus marked as initialized data. sections = append(sections, memorySection{ Address: address + uint64(section.Size), Size: uint64(section.VirtualSize) - uint64(section.Size), Type: memoryBSS, }) } } else if section.Characteristics&IMAGE_SCN_MEM_READ != 0 { // .rdata, .buildid, .pdata sections = append(sections, memorySection{ Address: address, Size: uint64(section.VirtualSize), Type: memoryROData, }) } } } } else if file, err := wasm.Parse(f); err == nil { // File is in WebAssembly format. // Put code at a very high address, so that it won't conflict with the // data in the memory section. const codeOffset = 0x8000_0000_0000_0000 // Read DWARF information. The error is intentionally ignored. data, _ := file.DWARF() if data != nil { addresses, err = readProgramSizeFromDWARF(data, codeOffset, 0, true) if err != nil { // However, _do_ report an error here. Something must have gone // wrong while trying to parse DWARF data. return nil, err } } var linearMemorySize uint64 for _, section := range file.Sections { switch section := section.(type) { case *wasm.SectionCode: sections = append(sections, memorySection{ Address: codeOffset, Size: uint64(section.Size()), Type: memoryCode, }) case *wasm.SectionMemory: // This value is used when processing *wasm.SectionData (which // always comes after *wasm.SectionMemory). linearMemorySize = uint64(section.Entries[0].Limits.Initial) * 64 * 1024 case *wasm.SectionData: // Data sections contain initial values for linear memory. // First load the list of data sections, and sort them by // address for easier processing. var dataSections []memorySection for _, entry := range section.Entries { address, err := wasm.Eval(bytes.NewBuffer(entry.Offset)) if err != nil { return nil, fmt.Errorf("could not parse data section address: %w", err) } dataSections = append(dataSections, memorySection{ Address: uint64(address[0].(int32)), Size: uint64(len(entry.Data)), Type: memoryData, }) } sort.Slice(dataSections, func(i, j int) bool { return dataSections[i].Address < dataSections[j].Address }) // And now add all data sections for linear memory. // Parts that are in the slice of data sections are added as // memoryData, and parts that are not are added as memoryBSS. addr := uint64(0) for _, section := range dataSections { if addr < section.Address { sections = append(sections, memorySection{ Address: addr, Size: section.Address - addr, Type: memoryBSS, }) } if addr > section.Address { // This might be allowed, I'm not sure. // It certainly doesn't make a lot of sense. return nil, fmt.Errorf("overlapping data section") } // addr == section.Address sections = append(sections, section) addr = section.Address + section.Size } if addr < linearMemorySize { sections = append(sections, memorySection{ Address: addr, Size: linearMemorySize - addr, Type: memoryBSS, }) } } } } else { return nil, fmt.Errorf("could not parse file: %w", err) } // Sort the slice of address chunks by address, so that we can iterate // through it to calculate section sizes. sort.Slice(addresses, func(i, j int) bool { if addresses[i].Address == addresses[j].Address { // Very rarely, there might be duplicate addresses. // If that happens, sort the largest chunks first. return addresses[i].Length > addresses[j].Length } return addresses[i].Address < addresses[j].Address }) // Now finally determine the binary/RAM size usage per package by going // through each allocated section. sizes := make(map[string]*packageSize) program := &programSize{ Packages: sizes, } for _, section := range sections { switch section.Type { case memoryCode: readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 { if isVariable { return &ps.ROData } return &ps.Code }, packagePathMap) case memoryROData: readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 { return &ps.ROData }, packagePathMap) case memoryData: readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 { return &ps.Data }, packagePathMap) case memoryBSS: readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 { return &ps.BSS }, packagePathMap) case memoryStack: // We store the C stack as a pseudo-package. program.getPackage("C stack").addSize(func(ps *packageSize, isVariable bool) *uint64 { return &ps.BSS }, "", section.Size, false) } } // ...and summarize the results. for _, pkg := range sizes { program.Code += pkg.Code program.ROData += pkg.ROData program.Data += pkg.Data program.BSS += pkg.BSS } return program, nil } // readSection determines for each byte in this section to which package it // belongs. func readSection(section memorySection, addresses []addressLine, program *programSize, getField func(*packageSize, bool) *uint64, packagePathMap map[string]string) { // The addr variable tracks at which address we are while going through this // section. We start at the beginning. addr := section.Address sectionEnd := section.Address + section.Size if sizesDebug { fmt.Printf("%08x..%08x %5d: %s\n", addr, sectionEnd, section.Size, section.Type) } for _, line := range addresses { if line.Address < section.Address || line.Address+line.Length > sectionEnd { // Check that this line is entirely within the section. // Don't bother dealing with line entries that cross sections (that // seems rather unlikely anyway). continue } if addr < line.Address { // There is a gap: there is a space between the current and the // previous line entry. // Check whether this is caused by alignment requirements. addrAligned := (addr + line.Align - 1) &^ (line.Align - 1) if line.Align > 1 && addrAligned >= line.Address { // It is, assume that's what causes the gap. program.getPackage("(padding)").addSize(getField, "", line.Address-addr, true) } else { program.getPackage("(unknown)").addSize(getField, "", line.Address-addr, false) if sizesDebug { fmt.Printf("%08x..%08x %5d: unknown (gap), alignment=%d\n", addr, line.Address, line.Address-addr, line.Align) } } addr = line.Address } if addr > line.Address+line.Length { // The current line is already covered by a previous line entry. // Simply skip it. continue } // At this point, addr falls within the current line (probably at the // start). length := line.Length if addr > line.Address { // There is some overlap: the previous line entry already covered // part of this line entry. So reduce the length to add to the // remaining bit of the line entry. length = line.Length - (addr - line.Address) } // Finally, mark this chunk of memory as used by the given package. packagePath, filename := findPackagePath(line.File, packagePathMap) program.getPackage(packagePath).addSize(getField, filename, length, line.IsVariable) addr = line.Address + line.Length } if addr < sectionEnd { // There is a gap at the end of the section. addrAligned := (addr + section.Align - 1) &^ (section.Align - 1) if section.Align > 1 && addrAligned >= sectionEnd { // The gap is caused by the section alignment. // For example, if a .rodata section ends with a non-aligned string. program.getPackage("(padding)").addSize(getField, "", sectionEnd-addr, true) } else { program.getPackage("(unknown)").addSize(getField, "", sectionEnd-addr, false) if sizesDebug { fmt.Printf("%08x..%08x %5d: unknown (end), alignment=%d\n", addr, sectionEnd, sectionEnd-addr, section.Align) } } } } // findPackagePath returns the Go package (or a pseudo package) for the given // path. It uses some heuristics, for example for some C libraries. func findPackagePath(path string, packagePathMap map[string]string) (packagePath, filename string) { // Check whether this path is part of one of the compiled packages. packagePath, ok := packagePathMap[filepath.Dir(path)] if ok { // Directory is known as a Go package. // Add the file itself as well. filename = filepath.Base(path) } else { if strings.HasPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")) { // Emit C libraries (in the lib subdirectory of TinyGo) as a single // package, with a "C" prefix. For example: "C picolibc" for the // baremetal libc. libPath := strings.TrimPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")+string(os.PathSeparator)) parts := strings.SplitN(libPath, string(os.PathSeparator), 2) packagePath = "C " + parts[0] filename = parts[1] } else if prefix := filepath.Join(goenv.Get("TINYGOROOT"), "llvm-project", "compiler-rt"); strings.HasPrefix(path, prefix) { packagePath = "C compiler-rt" filename = strings.TrimPrefix(path, prefix+string(os.PathSeparator)) } else if packageSymbolRegexp.MatchString(path) { // Parse symbol names like main$alloc or runtime$string. packagePath = path[:strings.LastIndex(path, "$")] } else if path == "__isr_vector" { packagePath = "C interrupt vector" } else if path == "" { packagePath = "Go types" } else if path == "" { // Interface type assert, generated by the interface lowering pass. packagePath = "Go interface assert" } else if path == "" { // Interface method wrapper (switch over all concrete types), // generated by the interface lowering pass. packagePath = "Go interface method" } else if path == "" { // This can happen when the source code (in Go) doesn't have a // source file and uses "-" as the location. Somewhere this is // converted to "". // Convert this back to the "-" string. Eventually, this should be // fixed in the compiler. packagePath = "-" } else { // This is some other path. Not sure what it is, so just emit its // directory as a fallback. packagePath = filepath.Dir(path) filename = filepath.Base(path) } } return } ================================================ FILE: builder/sizes_test.go ================================================ package builder import ( "regexp" "runtime" "testing" "time" "github.com/tinygo-org/tinygo/compileopts" ) var sema = make(chan struct{}, runtime.NumCPU()) type sizeTest struct { target string path string codeSize uint64 rodataSize uint64 dataSize uint64 bssSize uint64 } // Test whether code and data size is as expected for the given targets. // This tests both the logic of loadProgramSize and checks that code size // doesn't change unintentionally. // // If you find that code or data size is reduced, then great! You can reduce the // number in this test. // If you find that the code or data size is increased, take a look as to why // this is. It could be due to an update (LLVM version, Go version, etc) which // is fine, but it could also mean that a recent change introduced this size // increase. If so, please consider whether this new feature is indeed worth the // size increase for all users. func TestBinarySize(t *testing.T) { if runtime.GOOS == "linux" && !hasBuiltinTools { // Debian LLVM packages are modified a bit and tend to produce // different machine code. Ideally we'd fix this (with some attributes // or something?), but for now skip it. t.Skip("Skip: using external LLVM version so binary size might differ") } // This is a small number of very diverse targets that we want to test. tests := []sizeTest{ // microcontrollers {"hifive1b", "examples/echo", 3668, 280, 0, 2244}, {"microbit", "examples/serial", 2694, 342, 8, 2248}, {"wioterminal", "examples/pininterrupt", 7187, 1489, 116, 6888}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the // output varies by binaryen version. } for _, tc := range tests { tc := tc t.Run(tc.target+"/"+tc.path, func(t *testing.T) { t.Parallel() // Build the binary. result := buildBinary(t, tc.target, tc.path) // Check whether the size of the binary matches the expected size. sizes, err := loadProgramSize(result.Executable, nil) if err != nil { t.Fatal("could not read program size:", err) } if sizes.Code != tc.codeSize || sizes.ROData != tc.rodataSize || sizes.Data != tc.dataSize || sizes.BSS != tc.bssSize { t.Errorf("Unexpected code size when compiling: -target=%s %s", tc.target, tc.path) t.Errorf(" code rodata data bss") t.Errorf("expected: %6d %6d %6d %6d", tc.codeSize, tc.rodataSize, tc.dataSize, tc.bssSize) t.Errorf("actual: %6d %6d %6d %6d", sizes.Code, sizes.ROData, sizes.Data, sizes.BSS) } }) } } // Check that the -size=full flag attributes binary size to the correct package // without filesystem paths and things like that. func TestSizeFull(t *testing.T) { tests := []string{ "microbit", "wasip1", } libMatch := regexp.MustCompile(`^C [a-z -]+$`) // example: "C interrupt vector" pkgMatch := regexp.MustCompile(`^[a-z/]+$`) // example: "internal/task" for _, target := range tests { target := target t.Run(target, func(t *testing.T) { t.Parallel() // Build the binary. result := buildBinary(t, target, "examples/serial") // Check whether the binary doesn't contain any unexpected package // names. sizes, err := loadProgramSize(result.Executable, result.PackagePathMap) if err != nil { t.Fatal("could not read program size:", err) } for _, pkg := range sizes.sortedPackageNames() { if pkg == "(padding)" || pkg == "(unknown)" { // TODO: correctly attribute all unknown binary size. continue } if libMatch.MatchString(pkg) { continue } if pkgMatch.MatchString(pkg) { continue } t.Error("unexpected package name in size output:", pkg) } }) } } func buildBinary(t *testing.T, targetString, pkgName string) BuildResult { options := compileopts.Options{ Target: targetString, Opt: "z", Semaphore: sema, InterpTimeout: 60 * time.Second, Debug: true, VerifyIR: true, } target, err := compileopts.LoadTarget(&options) if err != nil { t.Fatal("could not load target:", err) } config := &compileopts.Config{ Options: &options, Target: target, } result, err := Build(pkgName, "", t.TempDir(), config) if err != nil { t.Fatal("could not build:", err) } return result } ================================================ FILE: builder/tools-builtin.go ================================================ //go:build byollvm package builder import ( "errors" "unsafe" ) /* #cgo CXXFLAGS: -fno-rtti #include #include bool tinygo_clang_driver(int argc, char **argv); bool tinygo_link(int argc, char **argv); */ import "C" const hasBuiltinTools = true // RunTool runs the given tool (such as clang). // // This version actually runs the tools because TinyGo was compiled while // linking statically with LLVM (with the byollvm build tag). func RunTool(tool string, args ...string) error { args = append([]string{tool}, args...) var cflag *C.char buf := C.calloc(C.size_t(len(args)), C.size_t(unsafe.Sizeof(cflag))) defer C.free(buf) cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(args):len(args)] for i, flag := range args { cflag := C.CString(flag) cflags[i] = cflag defer C.free(unsafe.Pointer(cflag)) } var ok C.bool switch tool { case "clang": ok = C.tinygo_clang_driver(C.int(len(args)), (**C.char)(buf)) case "ld.lld", "wasm-ld": ok = C.tinygo_link(C.int(len(args)), (**C.char)(buf)) default: return errors.New("unknown tool: " + tool) } if !ok { return errors.New("failed to run tool: " + tool) } return nil } ================================================ FILE: builder/tools-external.go ================================================ //go:build !byollvm package builder import "errors" const hasBuiltinTools = false // RunTool runs the given tool (such as clang). // // This version doesn't actually run the tool: TinyGo has not been compiled by // statically linking to LLVM. func RunTool(tool string, args ...string) error { return errors.New("cannot run tool: " + tool) } ================================================ FILE: builder/tools.go ================================================ package builder import ( "bytes" "fmt" "go/scanner" "go/token" "os" "os/exec" "regexp" "strconv" "strings" ) // runCCompiler invokes a C compiler with the given arguments. func runCCompiler(flags ...string) error { // Find the right command to run Clang. var cmd *exec.Cmd if hasBuiltinTools { // Compile this with the internal Clang compiler. cmd = exec.Command(os.Args[0], append([]string{"clang"}, flags...)...) } else { // Compile this with an external invocation of the Clang compiler. name, err := LookupCommand("clang") if err != nil { return err } cmd = exec.Command(name, flags...) } cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr // Make sure the command doesn't use any environmental variables. // Most importantly, it should not use C_INCLUDE_PATH and the like. cmd.Env = []string{} // Let some environment variables through. One important one is the // temporary directory, especially on Windows it looks like Clang breaks if // the temporary directory has not been set. // See: https://github.com/tinygo-org/tinygo/issues/4557 // Also see: https://github.com/llvm/llvm-project/blob/release/18.x/llvm/lib/Support/Unix/Path.inc#L1435 for _, env := range os.Environ() { // We could parse the key and look it up in a map, but since there are // only a few keys iterating through them is easier and maybe even // faster. for _, prefix := range []string{"TMPDIR=", "TMP=", "TEMP=", "TEMPDIR="} { if strings.HasPrefix(env, prefix) { cmd.Env = append(cmd.Env, env) break } } } return cmd.Run() } // link invokes a linker with the given name and flags. func link(linker string, flags ...string) error { // We only support LLD. if linker != "ld.lld" && linker != "wasm-ld" { return fmt.Errorf("unexpected: linker %s should be ld.lld or wasm-ld", linker) } var cmd *exec.Cmd if hasBuiltinTools { cmd = exec.Command(os.Args[0], append([]string{linker}, flags...)...) } else { name, err := LookupCommand(linker) if err != nil { return err } cmd = exec.Command(name, flags...) } var buf bytes.Buffer cmd.Stdout = os.Stdout cmd.Stderr = &buf err := cmd.Run() if err != nil { if buf.Len() == 0 { // The linker failed but there was no output. // Therefore, show some output anyway. return fmt.Errorf("failed to run linker: %w", err) } return parseLLDErrors(buf.String()) } return nil } // Split LLD errors into individual erros (including errors that continue on the // next line, using a ">>>" prefix). If possible, replace the raw errors with a // more user-friendly version (and one that's more in a Go style). func parseLLDErrors(text string) error { // Split linker output in separate error messages. lines := strings.Split(text, "\n") var errorLines []string // one or more line (belonging to a single error) per line for _, line := range lines { line = strings.TrimRight(line, "\r") // needed for Windows if len(errorLines) != 0 && strings.HasPrefix(line, ">>> ") { errorLines[len(errorLines)-1] += "\n" + line continue } if line == "" { continue } errorLines = append(errorLines, line) } // Parse error messages. var linkErrors []error var flashOverflow, ramOverflow uint64 for _, message := range errorLines { parsedError := false // Check for undefined symbols. // This can happen in some cases like with CGo and //go:linkname tricker. if matches := regexp.MustCompile(`^ld.lld(-[0-9]+)?: error: undefined symbol: (.*)\n`).FindStringSubmatch(message); matches != nil { symbolName := matches[2] for _, line := range strings.Split(message, "\n") { matches := regexp.MustCompile(`referenced by .* \(((.*):([0-9]+))\)`).FindStringSubmatch(line) if matches != nil { parsedError = true line, _ := strconv.Atoi(matches[3]) // TODO: detect common mistakes like -gc=none? linkErrors = append(linkErrors, scanner.Error{ Pos: token.Position{ Filename: matches[2], Line: line, }, Msg: "linker could not find symbol " + symbolName, }) } } } // Check for flash/RAM overflow. if matches := regexp.MustCompile(`^ld.lld(-[0-9]+)?: error: section '(.*?)' will not fit in region '(.*?)': overflowed by ([0-9]+) bytes$`).FindStringSubmatch(message); matches != nil { region := matches[3] n, err := strconv.ParseUint(matches[4], 10, 64) if err != nil { // Should not happen at all (unless it overflows an uint64 for some reason). continue } // Check which area overflowed. // Some chips use differently named memory areas, but these are by // far the most common. switch region { case "FLASH_TEXT": if n > flashOverflow { flashOverflow = n } parsedError = true case "RAM": if n > ramOverflow { ramOverflow = n } parsedError = true } } // If we couldn't parse the linker error: show the error as-is to // the user. if !parsedError { linkErrors = append(linkErrors, LinkerError{message}) } } if flashOverflow > 0 { linkErrors = append(linkErrors, LinkerError{ Msg: fmt.Sprintf("program too large for this chip (flash overflowed by %d bytes)\n\toptimization guide: https://tinygo.org/docs/guides/optimizing-binaries/", flashOverflow), }) } if ramOverflow > 0 { linkErrors = append(linkErrors, LinkerError{ Msg: fmt.Sprintf("program uses too much static RAM on this chip (RAM overflowed by %d bytes)", ramOverflow), }) } return newMultiError(linkErrors, "") } // LLD linker error that could not be parsed or doesn't refer to a source // location. type LinkerError struct { Msg string } func (e LinkerError) Error() string { return e.Msg } ================================================ FILE: builder/uf2.go ================================================ package builder // This file converts firmware files from BIN to UF2 format before flashing. // // For more information about the UF2 firmware file format, please see: // https://github.com/Microsoft/uf2 // // import ( "bytes" "encoding/binary" "os" "strconv" ) // convertELFFileToUF2File converts an ELF file to a UF2 file. func convertELFFileToUF2File(infile, outfile string, uf2FamilyID string) error { // Read the .text segment. targetAddress, data, err := extractROM(infile) if err != nil { return err } output, _, err := convertBinToUF2(data, uint32(targetAddress), uf2FamilyID) if err != nil { return err } return os.WriteFile(outfile, output, 0644) } // convertBinToUF2 converts the binary bytes in input to UF2 formatted data. func convertBinToUF2(input []byte, targetAddr uint32, uf2FamilyID string) ([]byte, int, error) { blocks := split(input, 256) output := make([]byte, 0) bl, err := newUF2Block(targetAddr, uf2FamilyID) if err != nil { return nil, 0, err } bl.SetNumBlocks(len(blocks)) for i := 0; i < len(blocks); i++ { bl.SetBlockNo(i) bl.SetData(blocks[i]) output = append(output, bl.Bytes()...) bl.IncrementAddress(bl.payloadSize) } return output, len(blocks), nil } const ( uf2MagicStart0 = 0x0A324655 // "UF2\n" uf2MagicStart1 = 0x9E5D5157 // Randomly selected uf2MagicEnd = 0x0AB16F30 // Ditto ) // uf2Block is the structure used for each UF2 code block sent to device. type uf2Block struct { magicStart0 uint32 magicStart1 uint32 flags uint32 targetAddr uint32 payloadSize uint32 blockNo uint32 numBlocks uint32 familyID uint32 data []uint8 magicEnd uint32 } // newUF2Block returns a new uf2Block struct that has been correctly populated func newUF2Block(targetAddr uint32, uf2FamilyID string) (*uf2Block, error) { var flags uint32 var familyID uint32 if uf2FamilyID != "" { flags |= flagFamilyIDPresent v, err := strconv.ParseUint(uf2FamilyID, 0, 32) if err != nil { return nil, err } familyID = uint32(v) } return &uf2Block{magicStart0: uf2MagicStart0, magicStart1: uf2MagicStart1, magicEnd: uf2MagicEnd, targetAddr: targetAddr, flags: flags, familyID: familyID, payloadSize: 256, data: make([]byte, 476), }, nil } const ( flagFamilyIDPresent = 0x00002000 ) // Bytes converts the uf2Block to a slice of bytes that can be written to file. func (b *uf2Block) Bytes() []byte { buf := bytes.NewBuffer(make([]byte, 0, 512)) binary.Write(buf, binary.LittleEndian, b.magicStart0) binary.Write(buf, binary.LittleEndian, b.magicStart1) binary.Write(buf, binary.LittleEndian, b.flags) binary.Write(buf, binary.LittleEndian, b.targetAddr) binary.Write(buf, binary.LittleEndian, b.payloadSize) binary.Write(buf, binary.LittleEndian, b.blockNo) binary.Write(buf, binary.LittleEndian, b.numBlocks) binary.Write(buf, binary.LittleEndian, b.familyID) binary.Write(buf, binary.LittleEndian, b.data) binary.Write(buf, binary.LittleEndian, b.magicEnd) return buf.Bytes() } // IncrementAddress moves the target address pointer forward by count bytes. func (b *uf2Block) IncrementAddress(count uint32) { b.targetAddr += b.payloadSize } // SetData sets the data to be used for the current block. func (b *uf2Block) SetData(d []byte) { b.data = make([]byte, 476) copy(b.data[:], d) } // SetBlockNo sets the current block number to be used. func (b *uf2Block) SetBlockNo(bn int) { b.blockNo = uint32(bn) } // SetNumBlocks sets the total number of blocks for this UF2 file. func (b *uf2Block) SetNumBlocks(total int) { b.numBlocks = uint32(total) } // split splits a slice of bytes into a slice of byte slices of a specific size limit. func split(input []byte, limit int) [][]byte { var block []byte output := make([][]byte, 0, len(input)/limit+1) for len(input) >= limit { // add all blocks block, input = input[:limit], input[limit:] output = append(output, block) } if len(input) > 0 { // add remaining block (that isn't full sized) output = append(output, input) } return output } ================================================ FILE: builder/wasilibc.go ================================================ package builder import ( "fmt" "os" "path/filepath" "strings" "github.com/tinygo-org/tinygo/goenv" ) var libWasiLibc = Library{ name: "wasi-libc", makeHeaders: func(target, includeDir string) error { bits := filepath.Join(includeDir, "bits") err := os.Mkdir(bits, 0777) if err != nil { return err } muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "wasi-libc/libc-top-half/musl") err = buildMuslAllTypes("wasm32", muslDir, bits) if err != nil { return err } // See MUSL_OMIT_HEADERS in the Makefile. omitHeaders := map[string]struct{}{ "syslog.h": {}, "wait.h": {}, "ucontext.h": {}, "paths.h": {}, "utmp.h": {}, "utmpx.h": {}, "lastlog.h": {}, "elf.h": {}, "link.h": {}, "pwd.h": {}, "shadow.h": {}, "grp.h": {}, "mntent.h": {}, "netdb.h": {}, "resolv.h": {}, "pty.h": {}, "dlfcn.h": {}, "setjmp.h": {}, "ulimit.h": {}, "wordexp.h": {}, "spawn.h": {}, "termios.h": {}, "libintl.h": {}, "aio.h": {}, "stdarg.h": {}, "stddef.h": {}, "pthread.h": {}, } for _, glob := range [][2]string{ {"libc-bottom-half/headers/public/*.h", ""}, {"libc-bottom-half/headers/public/wasi/*.h", "wasi"}, {"libc-top-half/musl/arch/wasm32/bits/*.h", "bits"}, {"libc-top-half/musl/include/*.h", ""}, {"libc-top-half/musl/include/netinet/*.h", "netinet"}, {"libc-top-half/musl/include/sys/*.h", "sys"}, } { matches, _ := filepath.Glob(filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc", glob[0])) outDir := filepath.Join(includeDir, glob[1]) os.MkdirAll(outDir, 0o777) for _, match := range matches { name := filepath.Base(match) if _, ok := omitHeaders[name]; ok { continue } data, err := os.ReadFile(match) if err != nil { return err } err = os.WriteFile(filepath.Join(outDir, name), data, 0o666) if err != nil { return err } } } return nil }, cflags: func(target, headerPath string) []string { libcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") return []string{ "-Werror", "-Wall", "-std=gnu11", "-nostdlibinc", "-mnontrapping-fptoint", "-msign-ext", "-mbulk-memory", "-Wno-null-pointer-arithmetic", "-Wno-unused-parameter", "-Wno-sign-compare", "-Wno-unused-variable", "-Wno-unused-function", "-Wno-ignored-attributes", "-Wno-missing-braces", "-Wno-ignored-pragmas", "-Wno-unused-but-set-variable", "-Wno-unknown-warning-option", "-Wno-parentheses", "-Wno-shift-op-parentheses", "-Wno-bitwise-op-parentheses", "-Wno-logical-op-parentheses", "-Wno-string-plus-int", "-Wno-dangling-else", "-Wno-unknown-pragmas", "-DNDEBUG", "-D__wasilibc_printscan_no_long_double", "-D__wasilibc_printscan_full_support_option=\"long double support is disabled\"", "-DBULK_MEMORY_THRESHOLD=32", // default threshold in wasi-libc "-isystem", headerPath, "-I" + libcDir + "/libc-top-half/musl/src/include", "-I" + libcDir + "/libc-top-half/musl/src/internal", "-I" + libcDir + "/libc-top-half/musl/arch/wasm32", "-I" + libcDir + "/libc-top-half/musl/arch/generic", "-I" + libcDir + "/libc-top-half/headers/private", } }, cflagsForFile: func(path string) []string { if strings.HasPrefix(path, "libc-bottom-half"+string(os.PathSeparator)) { libcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") return []string{ "-I" + libcDir + "/libc-bottom-half/headers/private", "-I" + libcDir + "/libc-bottom-half/cloudlibc/src/include", "-I" + libcDir + "/libc-bottom-half/cloudlibc/src", } } return nil }, sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") }, librarySources: func(target string, libcNeedsMalloc bool) ([]string, error) { type filePattern struct { glob string exclude []string } // See: LIBC_TOP_HALF_MUSL_SOURCES in the Makefile globs := []filePattern{ // Top half: mostly musl sources. {glob: "libc-top-half/sources/*.c"}, {glob: "libc-top-half/musl/src/conf/*.c"}, {glob: "libc-top-half/musl/src/internal/*.c", exclude: []string{ "procfdname.c", "syscall.c", "syscall_ret.c", "vdso.c", "version.c", }}, {glob: "libc-top-half/musl/src/locale/*.c", exclude: []string{ "dcngettext.c", "textdomain.c", "bind_textdomain_codeset.c"}}, {glob: "libc-top-half/musl/src/math/*.c", exclude: []string{ "__signbit.c", "__signbitf.c", "__signbitl.c", "__fpclassify.c", "__fpclassifyf.c", "__fpclassifyl.c", "ceilf.c", "ceil.c", "floorf.c", "floor.c", "truncf.c", "trunc.c", "rintf.c", "rint.c", "nearbyintf.c", "nearbyint.c", "sqrtf.c", "sqrt.c", "fabsf.c", "fabs.c", "copysignf.c", "copysign.c", "fminf.c", "fmaxf.c", "fmin.c", "fmax.c,", }}, {glob: "libc-top-half/musl/src/multibyte/*.c"}, {glob: "libc-top-half/musl/src/stdio/*.c", exclude: []string{ "vfwscanf.c", "vfwprintf.c", // long double is unsupported "__lockfile.c", "flockfile.c", "funlockfile.c", "ftrylockfile.c", "rename.c", "tmpnam.c", "tmpfile.c", "tempnam.c", "popen.c", "pclose.c", "remove.c", "gets.c"}}, {glob: "libc-top-half/musl/src/stdlib/*.c"}, {glob: "libc-top-half/musl/src/string/*.c", exclude: []string{ "strsignal.c"}}, // Bottom half: connect top half to WASI equivalents. {glob: "libc-bottom-half/cloudlibc/src/libc/*/*.c"}, {glob: "libc-bottom-half/cloudlibc/src/libc/sys/*/*.c"}, {glob: "libc-bottom-half/sources/*.c"}, } // We're using the Boehm GC, so we need a heap implementation in the libc. if libcNeedsMalloc { globs = append(globs, filePattern{glob: "dlmalloc/src/dlmalloc.c"}) } // See: LIBC_TOP_HALF_MUSL_SOURCES in the Makefile sources := []string{ "libc-top-half/musl/src/misc/a64l.c", "libc-top-half/musl/src/misc/basename.c", "libc-top-half/musl/src/misc/dirname.c", "libc-top-half/musl/src/misc/ffs.c", "libc-top-half/musl/src/misc/ffsl.c", "libc-top-half/musl/src/misc/ffsll.c", "libc-top-half/musl/src/misc/fmtmsg.c", "libc-top-half/musl/src/misc/getdomainname.c", "libc-top-half/musl/src/misc/gethostid.c", "libc-top-half/musl/src/misc/getopt.c", "libc-top-half/musl/src/misc/getopt_long.c", "libc-top-half/musl/src/misc/getsubopt.c", "libc-top-half/musl/src/misc/uname.c", "libc-top-half/musl/src/misc/nftw.c", "libc-top-half/musl/src/errno/strerror.c", "libc-top-half/musl/src/network/htonl.c", "libc-top-half/musl/src/network/htons.c", "libc-top-half/musl/src/network/ntohl.c", "libc-top-half/musl/src/network/ntohs.c", "libc-top-half/musl/src/network/inet_ntop.c", "libc-top-half/musl/src/network/inet_pton.c", "libc-top-half/musl/src/network/inet_aton.c", "libc-top-half/musl/src/network/in6addr_any.c", "libc-top-half/musl/src/network/in6addr_loopback.c", "libc-top-half/musl/src/fenv/fenv.c", "libc-top-half/musl/src/fenv/fesetround.c", "libc-top-half/musl/src/fenv/feupdateenv.c", "libc-top-half/musl/src/fenv/fesetexceptflag.c", "libc-top-half/musl/src/fenv/fegetexceptflag.c", "libc-top-half/musl/src/fenv/feholdexcept.c", "libc-top-half/musl/src/exit/exit.c", "libc-top-half/musl/src/exit/atexit.c", "libc-top-half/musl/src/exit/assert.c", "libc-top-half/musl/src/exit/quick_exit.c", "libc-top-half/musl/src/exit/at_quick_exit.c", "libc-top-half/musl/src/time/strftime.c", "libc-top-half/musl/src/time/asctime.c", "libc-top-half/musl/src/time/asctime_r.c", "libc-top-half/musl/src/time/ctime.c", "libc-top-half/musl/src/time/ctime_r.c", "libc-top-half/musl/src/time/wcsftime.c", "libc-top-half/musl/src/time/strptime.c", "libc-top-half/musl/src/time/difftime.c", "libc-top-half/musl/src/time/timegm.c", "libc-top-half/musl/src/time/ftime.c", "libc-top-half/musl/src/time/gmtime.c", "libc-top-half/musl/src/time/gmtime_r.c", "libc-top-half/musl/src/time/timespec_get.c", "libc-top-half/musl/src/time/getdate.c", "libc-top-half/musl/src/time/localtime.c", "libc-top-half/musl/src/time/localtime_r.c", "libc-top-half/musl/src/time/mktime.c", "libc-top-half/musl/src/time/__tm_to_secs.c", "libc-top-half/musl/src/time/__month_to_secs.c", "libc-top-half/musl/src/time/__secs_to_tm.c", "libc-top-half/musl/src/time/__year_to_secs.c", "libc-top-half/musl/src/time/__tz.c", "libc-top-half/musl/src/fcntl/creat.c", "libc-top-half/musl/src/dirent/alphasort.c", "libc-top-half/musl/src/dirent/versionsort.c", "libc-top-half/musl/src/env/__stack_chk_fail.c", "libc-top-half/musl/src/env/clearenv.c", "libc-top-half/musl/src/env/getenv.c", "libc-top-half/musl/src/env/putenv.c", "libc-top-half/musl/src/env/setenv.c", "libc-top-half/musl/src/env/unsetenv.c", "libc-top-half/musl/src/unistd/posix_close.c", "libc-top-half/musl/src/stat/futimesat.c", "libc-top-half/musl/src/legacy/getpagesize.c", "libc-top-half/musl/src/thread/thrd_sleep.c", } basepath := goenv.Get("TINYGOROOT") + "/lib/wasi-libc/" for _, pattern := range globs { matches, err := filepath.Glob(basepath + pattern.glob) if err != nil { // From the documentation: // > Glob ignores file system errors such as I/O errors reading // > directories. The only possible returned error is // > ErrBadPattern, when pattern is malformed. // So the only possible error is when the (statically defined) // pattern is wrong. In other words, a programming bug. return nil, fmt.Errorf("wasi-libc: could not glob source dirs: %w", err) } if len(matches) == 0 { return nil, fmt.Errorf("wasi-libc: did not find any files for pattern %#v", pattern) } excludeSet := map[string]struct{}{} for _, exclude := range pattern.exclude { excludeSet[exclude] = struct{}{} } for _, match := range matches { if _, ok := excludeSet[filepath.Base(match)]; ok { continue } relpath, err := filepath.Rel(basepath, match) if err != nil { // Not sure if this is even possible. return nil, err } sources = append(sources, relpath) } } return sources, nil }, } ================================================ FILE: builder/wasmbuiltins.go ================================================ package builder import ( "os" "path/filepath" "github.com/tinygo-org/tinygo/goenv" ) var libWasmBuiltins = Library{ name: "wasmbuiltins", makeHeaders: func(target, includeDir string) error { if err := os.Mkdir(includeDir+"/bits", 0o777); err != nil { return err } f, err := os.Create(includeDir + "/bits/alltypes.h") if err != nil { return err } if _, err := f.Write([]byte(wasmAllTypes)); err != nil { return err } return f.Close() }, cflags: func(target, headerPath string) []string { libcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") return []string{ "-Werror", "-Wall", "-std=gnu11", "-nostdlibinc", "-mnontrapping-fptoint", // match wasm-unknown (default on in LLVM 20) "-mno-bulk-memory", // same here "-isystem", libcDir + "/libc-top-half/musl/arch/wasm32", "-isystem", libcDir + "/libc-top-half/musl/arch/generic", "-isystem", libcDir + "/libc-top-half/musl/src/internal", "-isystem", libcDir + "/libc-top-half/musl/src/include", "-isystem", libcDir + "/libc-top-half/musl/include", "-isystem", libcDir + "/libc-bottom-half/headers/public", "-I" + headerPath, } }, sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") }, librarySources: func(target string, _ bool) ([]string, error) { return []string{ // memory builtins needed for llvm.memcpy.*, llvm.memmove.*, and // llvm.memset.* LLVM intrinsics. "libc-top-half/musl/src/string/memcpy.c", "libc-top-half/musl/src/string/memmove.c", "libc-top-half/musl/src/string/memset.c", // exp, exp2, and log are needed for LLVM math builtin functions // like llvm.exp.*. "libc-top-half/musl/src/math/__math_divzero.c", "libc-top-half/musl/src/math/__math_invalid.c", "libc-top-half/musl/src/math/__math_oflow.c", "libc-top-half/musl/src/math/__math_uflow.c", "libc-top-half/musl/src/math/__math_xflow.c", "libc-top-half/musl/src/math/exp.c", "libc-top-half/musl/src/math/exp_data.c", "libc-top-half/musl/src/math/exp2.c", "libc-top-half/musl/src/math/log.c", "libc-top-half/musl/src/math/log_data.c", }, nil }, } // alltypes.h for wasm-libc, using the types as defined inside Clang. const wasmAllTypes = ` typedef __SIZE_TYPE__ size_t; typedef __INT8_TYPE__ int8_t; typedef __INT16_TYPE__ int16_t; typedef __INT32_TYPE__ int32_t; typedef __INT64_TYPE__ int64_t; typedef __UINT8_TYPE__ uint8_t; typedef __UINT16_TYPE__ uint16_t; typedef __UINT32_TYPE__ uint32_t; typedef __UINT64_TYPE__ uint64_t; typedef __UINTPTR_TYPE__ uintptr_t; // This type is used internally in wasi-libc. typedef double double_t; ` ================================================ FILE: cgo/cgo.go ================================================ // Package cgo implements CGo by modifying a loaded AST. It does this by parsing // the `import "C"` statements found in the source code with libclang and // generating stub function and global declarations. // // There are a few advantages to modifying the AST directly instead of doing CGo // as a preprocessing step, with the main advantage being that debug information // is kept intact as much as possible. package cgo // This file extracts the `import "C"` statement from the source and modifies // the AST for CGo. It does not use libclang directly: see libclang.go for the C // source file parsing. import ( "fmt" "go/ast" "go/parser" "go/scanner" "go/token" "path/filepath" "sort" "strconv" "strings" "github.com/google/shlex" "golang.org/x/tools/go/ast/astutil" ) // Function that's only defined in Go 1.22. var setASTFileFields = func(f *ast.File, start, end token.Pos) { } // cgoPackage holds all CGo-related information of a package. type cgoPackage struct { generated *ast.File packageName string cgoFiles []*ast.File generatedPos token.Pos errors []error currentDir string // current working directory packageDir string // full path to the package to process importPath string fset *token.FileSet tokenFiles map[string]*token.File definedGlobally map[string]ast.Node noescapingFuncs map[string]*noescapingFunc // #cgo noescape lines anonDecls map[interface{}]string cflags []string // CFlags from #cgo lines ldflags []string // LDFlags from #cgo lines visitedFiles map[string][]byte cgoHeaders []string } // cgoFile holds information only for a single Go file (with one or more // `import "C"` statements). type cgoFile struct { *cgoPackage file *ast.File index int defined map[string]ast.Node names map[string]clangCursor } // elaboratedTypeInfo contains some information about an elaborated type // (struct, union) found in the C AST. type elaboratedTypeInfo struct { typeExpr *ast.StructType pos token.Pos bitfields []bitfieldInfo unionSize int64 // union size in bytes, nonzero when union getters/setters should be created unionAlign int64 // union alignment in bytes } // bitfieldInfo contains information about a single bitfield in a struct. It // keeps information about the start, end, and the special (renamed) base field // of this bitfield. type bitfieldInfo struct { field *ast.Field name string pos token.Pos startBit int64 endBit int64 // may be 0 meaning "until the end of the field" } // Information about a #cgo noescape line in the source code. type noescapingFunc struct { name string pos token.Pos used bool // true if used somewhere in the source (for proper error reporting) } // cgoAliases list type aliases between Go and C, for types that are equivalent // in both languages. See addTypeAliases. var cgoAliases = map[string]string{ "_Cgo_int8_t": "int8", "_Cgo_int16_t": "int16", "_Cgo_int32_t": "int32", "_Cgo_int64_t": "int64", "_Cgo_uint8_t": "uint8", "_Cgo_uint16_t": "uint16", "_Cgo_uint32_t": "uint32", "_Cgo_uint64_t": "uint64", "_Cgo_uintptr_t": "uintptr", "_Cgo_float": "float32", "_Cgo_double": "float64", "_Cgo__Bool": "bool", } // builtinAliases are handled specially because they only exist on the Go side // of CGo, not on the CGo side (they're prefixed with "_Cgo_" there). var builtinAliases = []string{ "char", "schar", "uchar", "short", "ushort", "int", "uint", "long", "ulong", "longlong", "ulonglong", } // builtinAliasTypedefs lists some C types with ambiguous sizes that must be // retrieved somehow from C. This is done by adding some typedefs to get the // size of each type. const builtinAliasTypedefs = ` # 1 "" typedef char _Cgo_char; typedef signed char _Cgo_schar; typedef unsigned char _Cgo_uchar; typedef short _Cgo_short; typedef unsigned short _Cgo_ushort; typedef int _Cgo_int; typedef unsigned int _Cgo_uint; typedef long _Cgo_long; typedef unsigned long _Cgo_ulong; typedef long long _Cgo_longlong; typedef unsigned long long _Cgo_ulonglong; ` // First part of the generated Go file. Written here as Go because that's much // easier than constructing the entire AST in memory. // The string/bytes functions below implement C.CString etc. To make sure the // runtime doesn't need to know the C int type, lengths are converted to uintptr // first. const generatedGoFilePrefixBase = ` import "syscall" import "unsafe" var _ unsafe.Pointer //go:linkname _Cgo_CString runtime.cgo_CString func _Cgo_CString(string) *_Cgo_char //go:linkname _Cgo_GoString runtime.cgo_GoString func _Cgo_GoString(*_Cgo_char) string //go:linkname _Cgo___GoStringN runtime.cgo_GoStringN func _Cgo___GoStringN(*_Cgo_char, uintptr) string func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { return _Cgo___GoStringN(cstr, uintptr(length)) } //go:linkname _Cgo___GoBytes runtime.cgo_GoBytes func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { return _Cgo___GoBytes(ptr, uintptr(length)) } //go:linkname _Cgo___CBytes runtime.cgo_CBytes func _Cgo___CBytes([]byte) unsafe.Pointer func _Cgo_CBytes(b []byte) unsafe.Pointer { return _Cgo___CBytes(b) } //go:linkname _Cgo___get_errno_num runtime.cgo_errno func _Cgo___get_errno_num() uintptr ` const generatedGoFilePrefixOther = generatedGoFilePrefixBase + ` func _Cgo___get_errno() error { return syscall.Errno(_Cgo___get_errno_num()) } ` // Windows uses fake errno values in the syscall package. // See for example: https://github.com/golang/go/issues/23468 // TinyGo uses mingw-w64 though, which does have defined errno values. Since the // syscall package is the standard library one we can't change it, but we can // map the errno values to match the values in the syscall package. // Source of the errno values: lib/mingw-w64/mingw-w64-headers/crt/errno.h const generatedGoFilePrefixWindows = generatedGoFilePrefixBase + ` var _Cgo___errno_mapping = [...]syscall.Errno{ 1: syscall.EPERM, 2: syscall.ENOENT, 3: syscall.ESRCH, 4: syscall.EINTR, 5: syscall.EIO, 6: syscall.ENXIO, 7: syscall.E2BIG, 8: syscall.ENOEXEC, 9: syscall.EBADF, 10: syscall.ECHILD, 11: syscall.EAGAIN, 12: syscall.ENOMEM, 13: syscall.EACCES, 14: syscall.EFAULT, 16: syscall.EBUSY, 17: syscall.EEXIST, 18: syscall.EXDEV, 19: syscall.ENODEV, 20: syscall.ENOTDIR, 21: syscall.EISDIR, 22: syscall.EINVAL, 23: syscall.ENFILE, 24: syscall.EMFILE, 25: syscall.ENOTTY, 27: syscall.EFBIG, 28: syscall.ENOSPC, 29: syscall.ESPIPE, 30: syscall.EROFS, 31: syscall.EMLINK, 32: syscall.EPIPE, 33: syscall.EDOM, 34: syscall.ERANGE, 36: syscall.EDEADLK, 38: syscall.ENAMETOOLONG, 39: syscall.ENOLCK, 40: syscall.ENOSYS, 41: syscall.ENOTEMPTY, 42: syscall.EILSEQ, } func _Cgo___get_errno() error { num := _Cgo___get_errno_num() if num < uintptr(len(_Cgo___errno_mapping)) { if mapped := _Cgo___errno_mapping[num]; mapped != 0 { return mapped } } return syscall.Errno(num) } ` // Process extracts `import "C"` statements from the AST, parses the comment // with libclang, and modifies the AST to use this information. It returns a // newly created *ast.File that should be added to the list of to-be-parsed // files, the CGo header snippets that should be compiled (for inline // functions), the CFLAGS and LDFLAGS found in #cgo lines, and a map of file // hashes of the accessed C header files. If there is one or more error, it // returns these in the []error slice but still modifies the AST. func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string, goos string) ([]*ast.File, []string, []string, []string, map[string][]byte, []error) { p := &cgoPackage{ packageName: files[0].Name.Name, currentDir: dir, importPath: importPath, fset: fset, tokenFiles: map[string]*token.File{}, definedGlobally: map[string]ast.Node{}, noescapingFuncs: map[string]*noescapingFunc{}, anonDecls: map[interface{}]string{}, visitedFiles: map[string][]byte{}, } // Add a new location for the following file. generatedTokenPos := p.fset.AddFile(dir+"/!cgo.go", -1, 0) generatedTokenPos.SetLines([]int{0}) p.generatedPos = generatedTokenPos.Pos(0) // Find the absolute path for this package. packagePath, err := filepath.Abs(fset.File(files[0].Pos()).Name()) if err != nil { return nil, nil, nil, nil, nil, []error{ scanner.Error{ Pos: fset.Position(files[0].Pos()), Msg: "cgo: cannot find absolute path: " + err.Error(), // TODO: wrap this error }, } } p.packageDir = filepath.Dir(packagePath) // Construct a new in-memory AST for CGo declarations of this package. // The first part is written as Go code that is then parsed, but more code // is added later to the AST to declare functions, globals, etc. goCode := "package " + files[0].Name.Name + "\n\n" if goos == "windows" { goCode += generatedGoFilePrefixWindows } else { goCode += generatedGoFilePrefixOther } p.generated, err = parser.ParseFile(fset, dir+"/!cgo.go", goCode, parser.ParseComments) if err != nil { // This is always a bug in the cgo package. panic("unexpected error: " + err.Error()) } p.cgoFiles = append(p.cgoFiles, p.generated) // If the Comments field is not set to nil, the go/format package will get // confused about where comments should go. p.generated.Comments = nil // Find `import "C"` C fragments in the file. p.cgoHeaders = make([]string, len(files)) // combined CGo header fragment for each file for i, f := range files { var cgoHeader string for i := 0; i < len(f.Decls); i++ { decl := f.Decls[i] genDecl, ok := decl.(*ast.GenDecl) if !ok { continue } if len(genDecl.Specs) != 1 { continue } spec, ok := genDecl.Specs[0].(*ast.ImportSpec) if !ok { continue } path, err := strconv.Unquote(spec.Path.Value) if err != nil { // This should not happen. An import path that is not properly // quoted should not exist in a correct AST. panic("could not parse import path: " + err.Error()) } if path != "C" { continue } // Remove this import declaration. f.Decls = append(f.Decls[:i], f.Decls[i+1:]...) i-- if genDecl.Doc == nil { continue } // Iterate through all parts of the CGo header. Note that every // // line is a new comment. position := fset.Position(genDecl.Doc.Pos()) fragment := fmt.Sprintf("# %d %#v\n", position.Line, position.Filename) for _, comment := range genDecl.Doc.List { // Find all #cgo lines, extract and use their contents, and // replace the lines with spaces (to preserve locations). c := p.parseCGoPreprocessorLines(comment.Text, comment.Slash) // Change the comment (which is still in Go syntax, with // and // /* */ ) to a regular string by replacing the start/end // markers of comments with spaces. // It is similar to the Text() method but differs in that it // doesn't strip anything and tries to keep all offsets correct // by adding spaces and newlines where necessary. if c[1] == '/' { /* comment */ c = " " + c[2:] } else { // comment c = " " + c[2:len(c)-2] } fragment += c + "\n" } cgoHeader += fragment } p.cgoHeaders[i] = cgoHeader } // Define CFlags that will be used while parsing the package. // Disable _FORTIFY_SOURCE as it causes problems on macOS. // Note that it is only disabled for memcpy (etc) calls made from Go, which // have better alternatives anyway. cflagsForCGo := append([]string{"-D_FORTIFY_SOURCE=0"}, cflags...) cflagsForCGo = append(cflagsForCGo, p.cflags...) // Retrieve types such as C.int, C.longlong, etc from C. p.newCGoFile(nil, -1).readNames(builtinAliasTypedefs, cflagsForCGo, "", func(names map[string]clangCursor) { gen := &ast.GenDecl{ TokPos: token.NoPos, Tok: token.TYPE, } for _, name := range builtinAliases { typeSpec := p.getIntegerType("_Cgo_"+name, names["_Cgo_"+name]) gen.Specs = append(gen.Specs, typeSpec) } p.generated.Decls = append(p.generated.Decls, gen) }) // Process CGo imports for each file. for i, f := range files { cf := p.newCGoFile(f, i) // These types are aliased with the corresponding types in C. For // example, float in C is always float32 in Go. cf.names["float"] = clangCursor{} cf.names["double"] = clangCursor{} cf.names["_Bool"] = clangCursor{} // Now read all the names (identifies) that C defines in the header // snippet. cf.readNames(p.cgoHeaders[i], cflagsForCGo, filepath.Base(fset.File(f.Pos()).Name()), func(names map[string]clangCursor) { for _, name := range builtinAliases { // Names such as C.int should not be obtained from C. // This works around an issue in picolibc that has `#define int` // in a header file. delete(names, name) } astutil.Apply(f, func(cursor *astutil.Cursor) bool { return cf.walker(cursor, names) }, nil) }) } // Show an error when a #cgo noescape line isn't used in practice. // This matches upstream Go. I think the goal is to avoid issues with // misspelled function names, which seems very useful. var unusedNoescapeLines []*noescapingFunc for _, value := range p.noescapingFuncs { if !value.used { unusedNoescapeLines = append(unusedNoescapeLines, value) } } sort.SliceStable(unusedNoescapeLines, func(i, j int) bool { return unusedNoescapeLines[i].pos < unusedNoescapeLines[j].pos }) for _, value := range unusedNoescapeLines { p.addError(value.pos, fmt.Sprintf("function %#v in #cgo noescape line is not used", value.name)) } // Print the newly generated in-memory AST, for debugging. //ast.Print(fset, p.generated) return p.cgoFiles, p.cgoHeaders, p.cflags, p.ldflags, p.visitedFiles, p.errors } func (p *cgoPackage) newCGoFile(file *ast.File, index int) *cgoFile { return &cgoFile{ cgoPackage: p, file: file, index: index, defined: make(map[string]ast.Node), names: make(map[string]clangCursor), } } // makePathsAbsolute converts some common path compiler flags (-I, -L) from // relative flags into absolute flags, if they are relative. This is necessary // because the C compiler is usually not invoked from the package path. func (p *cgoPackage) makePathsAbsolute(args []string) { nextIsPath := false for i, arg := range args { if nextIsPath { if !filepath.IsAbs(arg) { args[i] = filepath.Join(p.packageDir, arg) } } if arg == "-I" || arg == "-L" { nextIsPath = true continue } if strings.HasPrefix(arg, "-I") || strings.HasPrefix(arg, "-L") { path := arg[2:] if !filepath.IsAbs(path) { args[i] = arg[:2] + filepath.Join(p.packageDir, path) } } } } // parseCGoPreprocessorLines reads #cgo pseudo-preprocessor lines in the source // text (import "C" fragment), stores their information such as CFLAGS, and // returns the same text but with those #cgo lines replaced by spaces (to keep // position offsets the same). func (p *cgoPackage) parseCGoPreprocessorLines(text string, pos token.Pos) string { for { // Extract the #cgo line, and replace it with spaces. // Replacing with spaces makes sure that error locations are // still correct, while not interfering with parsing in any way. lineStart := strings.Index(text, "#cgo ") if lineStart < 0 { break } lineLen := strings.IndexByte(text[lineStart:], '\n') if lineLen < 0 { lineLen = len(text) - lineStart } lineEnd := lineStart + lineLen line := text[lineStart:lineEnd] spaces := make([]byte, len(line)) for i := range spaces { spaces[i] = ' ' } text = text[:lineStart] + string(spaces) + text[lineEnd:] allFields := strings.Fields(line[4:]) switch allFields[0] { case "noescape": // The code indicates that pointer parameters will not be captured // by the called C function. if len(allFields) < 2 { p.addErrorAfter(pos, text[:lineStart], "missing function name in #cgo noescape line") continue } if len(allFields) > 2 { p.addErrorAfter(pos, text[:lineStart], "multiple function names in #cgo noescape line") continue } name := allFields[1] p.noescapingFuncs[name] = &noescapingFunc{ name: name, pos: pos, used: false, } continue case "nocallback": // We don't do anything special when calling a C function, so there // appears to be no optimization that we can do here. // Accept, but ignore the parameter for compatibility. continue } // Get the text before the colon in the #cgo directive. colon := strings.IndexByte(line, ':') if colon < 0 { p.addErrorAfter(pos, text[:lineStart], "missing colon in #cgo line") continue } // Extract the fields before the colon. These fields are a list // of build tags and the C environment variable. fields := strings.Fields(line[4:colon]) if len(fields) == 0 { p.addErrorAfter(pos, text[:lineStart+colon-1], "invalid #cgo line") continue } if len(fields) > 1 { p.addErrorAfter(pos, text[:lineStart+5], "not implemented: build constraints in #cgo line") continue } name := fields[len(fields)-1] value := line[colon+1:] switch name { case "CFLAGS": flags, err := shlex.Split(value) if err != nil { // TODO: find the exact location where the error happened. p.addErrorAfter(pos, text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error()) continue } if err := checkCompilerFlags(name, flags); err != nil { p.addErrorAfter(pos, text[:lineStart+colon+1], err.Error()) continue } p.makePathsAbsolute(flags) p.cflags = append(p.cflags, flags...) case "LDFLAGS": flags, err := shlex.Split(value) if err != nil { // TODO: find the exact location where the error happened. p.addErrorAfter(pos, text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error()) continue } if err := checkLinkerFlags(name, flags); err != nil { p.addErrorAfter(pos, text[:lineStart+colon+1], err.Error()) continue } p.makePathsAbsolute(flags) p.ldflags = append(p.ldflags, flags...) default: startPos := strings.LastIndex(line[4:colon], name) + 4 p.addErrorAfter(pos, text[:lineStart+startPos], "invalid #cgo line: "+name) continue } } return text } // makeUnionField creates a new struct from an existing *elaboratedTypeInfo, // that has just a single field that must be accessed through special accessors. // It returns nil when there is an error. In case of an error, that error has // already been added to the list of errors using p.addError. func (p *cgoPackage) makeUnionField(typ *elaboratedTypeInfo) *ast.StructType { unionFieldTypeName, ok := map[int64]string{ 1: "uint8", 2: "uint16", 4: "uint32", 8: "uint64", }[typ.unionAlign] if !ok { p.addError(typ.typeExpr.Struct, fmt.Sprintf("expected union alignment to be one of 1, 2, 4, or 8, but got %d", typ.unionAlign)) return nil } var unionFieldType ast.Expr = &ast.Ident{ NamePos: token.NoPos, Name: unionFieldTypeName, } if typ.unionSize != typ.unionAlign { // A plain struct{uintX} isn't enough, we have to make a // struct{[N]uintX} to make the union big enough. if typ.unionSize/typ.unionAlign*typ.unionAlign != typ.unionSize { p.addError(typ.typeExpr.Struct, fmt.Sprintf("union alignment (%d) must be a multiple of union alignment (%d)", typ.unionSize, typ.unionAlign)) return nil } unionFieldType = &ast.ArrayType{ Len: &ast.BasicLit{ Kind: token.INT, Value: strconv.FormatInt(typ.unionSize/typ.unionAlign, 10), }, Elt: unionFieldType, } } return &ast.StructType{ Struct: typ.typeExpr.Struct, Fields: &ast.FieldList{ Opening: typ.typeExpr.Fields.Opening, List: []*ast.Field{{ Names: []*ast.Ident{ { NamePos: typ.typeExpr.Fields.Opening, Name: "$union", }, }, Type: unionFieldType, }}, Closing: typ.typeExpr.Fields.Closing, }, } } // createUnionAccessor creates a function that returns a typed pointer to a // union field for each field in a union. For example: // // func (union *C.union_1) unionfield_d() *float64 { // return (*float64)(unsafe.Pointer(&union.$union)) // } // // Where C.union_1 is defined as: // // type C.union_1 struct{ // $union uint64 // } // // The returned pointer can be used to get or set the field, or get the pointer // to a subfield. func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) { if len(field.Names) != 1 { panic("number of names in union field must be exactly 1") } fieldName := field.Names[0] pos := fieldName.NamePos // The method receiver. receiver := &ast.SelectorExpr{ X: &ast.Ident{ NamePos: pos, Name: "union", Obj: nil, }, Sel: &ast.Ident{ NamePos: pos, Name: "$union", }, } // Get the address of the $union field. receiverPtr := &ast.UnaryExpr{ Op: token.AND, X: receiver, } // Cast to unsafe.Pointer. sourcePointer := &ast.CallExpr{ Fun: &ast.SelectorExpr{ X: &ast.Ident{Name: "unsafe"}, Sel: &ast.Ident{Name: "Pointer"}, }, Args: []ast.Expr{receiverPtr}, } // Cast to the target pointer type. targetPointer := &ast.CallExpr{ Lparen: pos, Fun: &ast.ParenExpr{ Lparen: pos, X: &ast.StarExpr{ X: field.Type, }, Rparen: pos, }, Args: []ast.Expr{sourcePointer}, Rparen: pos, } // Create the accessor function. accessor := &ast.FuncDecl{ Recv: &ast.FieldList{ Opening: pos, List: []*ast.Field{ { Names: []*ast.Ident{ { NamePos: pos, Name: "union", }, }, Type: &ast.StarExpr{ Star: pos, X: &ast.Ident{ NamePos: pos, Name: typeName, Obj: nil, }, }, }, }, Closing: pos, }, Name: &ast.Ident{ NamePos: pos, Name: "unionfield_" + fieldName.Name, }, Type: &ast.FuncType{ Func: pos, Params: &ast.FieldList{ Opening: pos, Closing: pos, }, Results: &ast.FieldList{ List: []*ast.Field{ { Type: &ast.StarExpr{ Star: pos, X: field.Type, }, }, }, }, }, Body: &ast.BlockStmt{ Lbrace: pos, List: []ast.Stmt{ &ast.ReturnStmt{ Return: pos, Results: []ast.Expr{ targetPointer, }, }, }, Rbrace: pos, }, } p.generated.Decls = append(p.generated.Decls, accessor) } // createBitfieldGetter creates a bitfield getter function like the following: // // func (s *C.struct_foo) bitfield_b() byte { // return (s.__bitfield_1 >> 5) & 0x1 // } func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string) { // The value to return from the getter. // Not complete: this is just an expression to get the complete field. var result ast.Expr = &ast.SelectorExpr{ X: &ast.Ident{ NamePos: bitfield.pos, Name: "s", Obj: nil, }, Sel: &ast.Ident{ NamePos: bitfield.pos, Name: bitfield.field.Names[0].Name, }, } if bitfield.startBit != 0 { // Shift to the right by .startBit so that fields that come before are // shifted off. result = &ast.BinaryExpr{ X: result, OpPos: bitfield.pos, Op: token.SHR, Y: &ast.BasicLit{ ValuePos: bitfield.pos, Kind: token.INT, Value: strconv.FormatInt(bitfield.startBit, 10), }, } } if bitfield.endBit != 0 { // Mask off the high bits so that fields that come after this field are // masked off. and := (uint64(1) << uint64(bitfield.endBit-bitfield.startBit)) - 1 result = &ast.BinaryExpr{ X: result, OpPos: bitfield.pos, Op: token.AND, Y: &ast.BasicLit{ ValuePos: bitfield.pos, Kind: token.INT, Value: "0x" + strconv.FormatUint(and, 16), }, } } // Create the getter function. getter := &ast.FuncDecl{ Recv: &ast.FieldList{ Opening: bitfield.pos, List: []*ast.Field{ { Names: []*ast.Ident{ { NamePos: bitfield.pos, Name: "s", Obj: &ast.Object{ Kind: ast.Var, Name: "s", Decl: nil, }, }, }, Type: &ast.StarExpr{ Star: bitfield.pos, X: &ast.Ident{ NamePos: bitfield.pos, Name: typeName, Obj: nil, }, }, }, }, Closing: bitfield.pos, }, Name: &ast.Ident{ NamePos: bitfield.pos, Name: "bitfield_" + bitfield.name, }, Type: &ast.FuncType{ Func: bitfield.pos, Params: &ast.FieldList{ Opening: bitfield.pos, Closing: bitfield.pos, }, Results: &ast.FieldList{ List: []*ast.Field{ { Type: bitfield.field.Type, }, }, }, }, Body: &ast.BlockStmt{ Lbrace: bitfield.pos, List: []ast.Stmt{ &ast.ReturnStmt{ Return: bitfield.pos, Results: []ast.Expr{ result, }, }, }, Rbrace: bitfield.pos, }, } p.generated.Decls = append(p.generated.Decls, getter) } // createBitfieldSetter creates a bitfield setter function like the following: // // func (s *C.struct_foo) set_bitfield_b(value byte) { // s.__bitfield_1 = s.__bitfield_1 ^ 0x60 | ((value & 1) << 5) // } // // Or the following: // // func (s *C.struct_foo) set_bitfield_c(value byte) { // s.__bitfield_1 = s.__bitfield_1 & 0x3f | (value << 6) // } func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string) { // The full field with all bitfields. var field ast.Expr = &ast.SelectorExpr{ X: &ast.Ident{ NamePos: bitfield.pos, Name: "s", Obj: nil, }, Sel: &ast.Ident{ NamePos: bitfield.pos, Name: bitfield.field.Names[0].Name, }, } // The value to insert into the field. var valueToInsert ast.Expr = &ast.Ident{ NamePos: bitfield.pos, Name: "value", } if bitfield.endBit != 0 { // Make sure the value is in range with a mask. valueToInsert = &ast.BinaryExpr{ X: valueToInsert, OpPos: bitfield.pos, Op: token.AND, Y: &ast.BasicLit{ ValuePos: bitfield.pos, Kind: token.INT, Value: "0x" + strconv.FormatUint((uint64(1)< expressions to literal "C." // expressions. Such expressions are impossible to write in Go (a dot cannot be // used in the middle of a name) so in practice all C identifiers live in a // separate namespace (no _Cgo_ hacks like in gc). func (f *cgoFile) walker(cursor *astutil.Cursor, names map[string]clangCursor) bool { switch node := cursor.Node().(type) { case *ast.AssignStmt: // An assign statement could be something like this: // // val, errno := C.some_func() // // Check whether it looks like that, and if so, read the errno value and // return it as the second return value. The call will be transformed // into something like this: // // val, errno := C.some_func(), C.__get_errno() if len(node.Lhs) != 2 || len(node.Rhs) != 1 { return true } rhs, ok := node.Rhs[0].(*ast.CallExpr) if !ok { return true } fun, ok := rhs.Fun.(*ast.SelectorExpr) if !ok { return true } x, ok := fun.X.(*ast.Ident) if !ok { return true } if found, ok := names[fun.Sel.Name]; ok && x.Name == "C" { // Replace "C"."some_func" into "C.somefunc". rhs.Fun = &ast.Ident{ NamePos: x.NamePos, Name: f.getASTDeclName(fun.Sel.Name, found, true), } // Add the errno value as the second value in the statement. node.Rhs = append(node.Rhs, &ast.CallExpr{ Fun: &ast.Ident{ NamePos: node.Lhs[1].End(), Name: "_Cgo___get_errno", }, }) } case *ast.CallExpr: fun, ok := node.Fun.(*ast.SelectorExpr) if !ok { return true } x, ok := fun.X.(*ast.Ident) if !ok { return true } if found, ok := names[fun.Sel.Name]; ok && x.Name == "C" { node.Fun = &ast.Ident{ NamePos: x.NamePos, Name: f.getASTDeclName(fun.Sel.Name, found, true), } } case *ast.SelectorExpr: x, ok := node.X.(*ast.Ident) if !ok { return true } if x.Name == "C" { name := "_Cgo_" + node.Sel.Name if found, ok := names[node.Sel.Name]; ok { name = f.getASTDeclName(node.Sel.Name, found, false) } cursor.Replace(&ast.Ident{ NamePos: x.NamePos, Name: name, }) } } return true } // renameFieldKeywords renames all reserved words in Go to some other field name // with a "_" prefix. For example, it renames `type` to `_type`. // // See: https://golang.org/cmd/cgo/#hdr-Go_references_to_C func renameFieldKeywords(fieldList *ast.FieldList) { renameFieldName(fieldList, "type") } // renameFieldName renames a given field name to a name with a "_" prepended. It // makes sure to do the same thing for any field sharing the same name. func renameFieldName(fieldList *ast.FieldList, name string) { var ident *ast.Ident for _, f := range fieldList.List { for _, n := range f.Names { if n.Name == name { ident = n } } } if ident == nil { return } renameFieldName(fieldList, "_"+name) ident.Name = "_" + ident.Name } ================================================ FILE: cgo/cgo_go122.go ================================================ //go:build go1.22 package cgo // Code specifically for Go 1.22. import ( "go/ast" "go/token" ) func init() { setASTFileFields = func(f *ast.File, start, end token.Pos) { f.FileStart = start f.FileEnd = end } } ================================================ FILE: cgo/cgo_test.go ================================================ package cgo import ( "bytes" "flag" "fmt" "go/ast" "go/format" "go/parser" "go/scanner" "go/token" "go/types" "os" "path/filepath" "regexp" "runtime" "strings" "testing" ) // Pass -update to go test to update the output of the test files. var flagUpdate = flag.Bool("update", false, "Update images based on test output.") // normalizeResult normalizes Go source code that comes out of tests across // platforms and Go versions. func normalizeResult(t *testing.T, result string) string { result = strings.ReplaceAll(result, "\r\n", "\n") // This changed to 'undefined:', in Go 1.20. result = strings.ReplaceAll(result, ": undeclared name:", ": undefined:") // Go 1.20 added a bit more detail result = regexp.MustCompile(`(unknown field z in struct literal).*`).ReplaceAllString(result, "$1") return result } func TestCGo(t *testing.T) { var cflags = []string{"--target=armv6m-unknown-unknown-eabi"} for _, name := range []string{ "basic", "errors", "types", "symbols", "flags", "const", } { name := name // avoid a race condition t.Run(name, func(t *testing.T) { // Read the AST in memory. path := filepath.Join("testdata", name+".go") fset := token.NewFileSet() f, err := parser.ParseFile(fset, path, nil, parser.ParseComments) if err != nil { t.Fatal("could not parse Go source file:", err) } // Process the AST with CGo. cgoFiles, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags, "linux") // Check the AST for type errors. var typecheckErrors []error config := types.Config{ Error: func(err error) { typecheckErrors = append(typecheckErrors, err) }, Importer: newSimpleImporter(), Sizes: types.SizesFor("gccgo", "arm"), } _, err = config.Check("", fset, append([]*ast.File{f}, cgoFiles...), nil) if err != nil && len(typecheckErrors) == 0 { // Only report errors when no type errors are found (an // unexpected condition). t.Error(err) } // Store the (formatted) output in a buffer. Format it, so it // becomes easier to read (and will hopefully change less with CGo // changes). buf := &bytes.Buffer{} if len(cgoErrors) != 0 { buf.WriteString("// CGo errors:\n") for _, err := range cgoErrors { buf.WriteString(formatDiagnostic(err)) } buf.WriteString("\n") } if len(typecheckErrors) != 0 { buf.WriteString("// Type checking errors after CGo processing:\n") for _, err := range typecheckErrors { buf.WriteString(formatDiagnostic(err)) } buf.WriteString("\n") } err = format.Node(buf, fset, cgoFiles[0]) if err != nil { t.Errorf("could not write out CGo AST: %v", err) } actual := normalizeResult(t, buf.String()) // Read the file with the expected output, to compare against. outfile := filepath.Join("testdata", name+".out.go") expectedBytes, err := os.ReadFile(outfile) if err != nil { t.Fatalf("could not read expected output: %v", err) } expected := strings.ReplaceAll(string(expectedBytes), "\r\n", "\n") // Check whether the output is as expected. if expected != actual { // It is not. Test failed. if *flagUpdate { // Update the file with the expected data. err := os.WriteFile(outfile, []byte(actual), 0666) if err != nil { t.Error("could not write updated output file:", err) } return } t.Errorf("output did not match:\n%s", string(actual)) } }) } } func Test_cgoPackage_isEquivalentAST(t *testing.T) { fieldA := &ast.Field{Type: &ast.BasicLit{Kind: token.STRING, Value: "a"}} fieldB := &ast.Field{Type: &ast.BasicLit{Kind: token.STRING, Value: "b"}} listOfFieldA := &ast.FieldList{List: []*ast.Field{fieldA}} listOfFieldB := &ast.FieldList{List: []*ast.Field{fieldB}} funcDeclA := &ast.FuncDecl{Name: &ast.Ident{Name: "a"}, Type: &ast.FuncType{Params: &ast.FieldList{}, Results: listOfFieldA}} funcDeclB := &ast.FuncDecl{Name: &ast.Ident{Name: "b"}, Type: &ast.FuncType{Params: &ast.FieldList{}, Results: listOfFieldB}} funcDeclNoResults := &ast.FuncDecl{Name: &ast.Ident{Name: "C"}, Type: &ast.FuncType{Params: &ast.FieldList{}}} testCases := []struct { name string a, b ast.Node expected bool }{ { name: "both nil", expected: true, }, { name: "not same type", a: fieldA, b: &ast.FuncDecl{}, expected: false, }, { name: "Field same", a: fieldA, b: fieldA, expected: true, }, { name: "Field different", a: fieldA, b: fieldB, expected: false, }, { name: "FuncDecl Type Results nil", a: funcDeclNoResults, b: funcDeclNoResults, expected: true, }, { name: "FuncDecl Type Results same", a: funcDeclA, b: funcDeclA, expected: true, }, { name: "FuncDecl Type Results different", a: funcDeclA, b: funcDeclB, expected: false, }, { name: "FuncDecl Type Results a nil", a: funcDeclNoResults, b: funcDeclB, expected: false, }, { name: "FuncDecl Type Results b nil", a: funcDeclA, b: funcDeclNoResults, expected: false, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { p := &cgoPackage{} if got := p.isEquivalentAST(tc.a, tc.b); tc.expected != got { t.Errorf("expected %v, got %v", tc.expected, got) } }) } } // simpleImporter implements the types.Importer interface, but only allows // importing the syscall and unsafe packages. type simpleImporter struct { syscallPkg *types.Package } func newSimpleImporter() *simpleImporter { i := &simpleImporter{} // Implement a dummy syscall package with the Errno type. i.syscallPkg = types.NewPackage("syscall", "syscall") obj := types.NewTypeName(token.NoPos, i.syscallPkg, "Errno", nil) named := types.NewNamed(obj, nil, nil) i.syscallPkg.Scope().Insert(obj) named.SetUnderlying(types.Typ[types.Uintptr]) sig := types.NewSignatureType(nil, nil, nil, types.NewTuple(), types.NewTuple(types.NewParam(token.NoPos, i.syscallPkg, "", types.Typ[types.String])), false) named.AddMethod(types.NewFunc(token.NoPos, i.syscallPkg, "Error", sig)) i.syscallPkg.MarkComplete() return i } // Import implements the Importer interface. For testing usage only: it only // supports importing the unsafe package. func (i *simpleImporter) Import(path string) (*types.Package, error) { switch path { case "syscall": return i.syscallPkg, nil case "unsafe": return types.Unsafe, nil default: return nil, fmt.Errorf("importer not implemented for package %s", path) } } // formatDiagnostic formats the error message to be an indented comment. It // also fixes Windows path name issues (backward slashes). func formatDiagnostic(err error) string { var msg string switch err := err.(type) { case scanner.Error: msg = err.Pos.String() + ": " + err.Msg default: msg = err.Error() } if runtime.GOOS == "windows" { // Fix Windows path slashes. msg = strings.ReplaceAll(msg, "testdata\\", "testdata/") } return "// " + msg + "\n" } ================================================ FILE: cgo/const.go ================================================ package cgo // This file implements a parser of a subset of the C language, just enough to // parse common #define statements to Go constant expressions. import ( "fmt" "go/ast" "go/scanner" "go/token" "strings" ) var ( prefixParseFns map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error) precedences = map[token.Token]int{ token.OR: precedenceOr, token.XOR: precedenceXor, token.AND: precedenceAnd, token.SHL: precedenceShift, token.SHR: precedenceShift, token.ADD: precedenceAdd, token.SUB: precedenceAdd, token.MUL: precedenceMul, token.QUO: precedenceMul, token.REM: precedenceMul, } ) // See: https://en.cppreference.com/w/c/language/operator_precedence const ( precedenceLowest = iota + 1 precedenceOr precedenceXor precedenceAnd precedenceShift precedenceAdd precedenceMul precedencePrefix ) func init() { // This must be done in an init function to avoid an initialization order // failure. prefixParseFns = map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error){ token.IDENT: parseIdent, token.INT: parseBasicLit, token.FLOAT: parseBasicLit, token.STRING: parseBasicLit, token.CHAR: parseBasicLit, token.LPAREN: parseParenExpr, token.SUB: parseUnaryExpr, } } // parseConst parses the given string as a C constant. func parseConst(pos token.Pos, fset *token.FileSet, value string, params []ast.Expr, callerPos token.Pos, f *cgoFile) (ast.Expr, *scanner.Error) { t := newTokenizer(pos, fset, value, f) // If params is non-nil (could be a zero length slice), this const is // actually a function-call like expression from another macro. // This means we have to parse a string like "(a, b) (a+b)". // We do this by parsing the parameters at the start and then treating the // following like a normal constant expression. if params != nil { // Parse opening paren. if t.curToken != token.LPAREN { return nil, unexpectedToken(t, token.LPAREN) } t.Next() // Parse parameters (identifiers) and closing paren. var paramIdents []string for i := 0; ; i++ { if i == 0 && t.curToken == token.RPAREN { // No parameters, break early. t.Next() break } // Read the parameter name. if t.curToken != token.IDENT { return nil, unexpectedToken(t, token.IDENT) } paramIdents = append(paramIdents, t.curValue) t.Next() // Read the next token: either a continuation (comma) or end of list // (rparen). if t.curToken == token.RPAREN { // End of parameter list. t.Next() break } else if t.curToken == token.COMMA { // Comma, so there will be another parameter name. t.Next() } else { return nil, &scanner.Error{ Pos: t.fset.Position(t.curPos), Msg: "unexpected token " + t.curToken.String() + " inside macro parameters, expected ',' or ')'", } } } // Report an error if there is a mismatch in parameter length. // The error is reported at the location of the closing paren from the // caller location. if len(params) != len(paramIdents) { return nil, &scanner.Error{ Pos: t.fset.Position(callerPos), Msg: fmt.Sprintf("unexpected number of parameters: expected %d, got %d", len(paramIdents), len(params)), } } // Assign values to the parameters. // These parameter names are closer in 'scope' than other identifiers so // will be used first when parsing an identifier. for i, name := range paramIdents { t.params[name] = params[i] } } expr, err := parseConstExpr(t, precedenceLowest) t.Next() if t.curToken != token.EOF { return nil, &scanner.Error{ Pos: t.fset.Position(t.curPos), Msg: "unexpected token " + t.curToken.String() + ", expected end of expression", } } return expr, err } // parseConstExpr parses a stream of C tokens to a Go expression. func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) { if t.curToken == token.EOF { return nil, &scanner.Error{ Pos: t.fset.Position(t.curPos), Msg: "empty constant", } } prefix := prefixParseFns[t.curToken] if prefix == nil { return nil, &scanner.Error{ Pos: t.fset.Position(t.curPos), Msg: fmt.Sprintf("unexpected token %s", t.curToken), } } leftExpr, err := prefix(t) for t.peekToken != token.EOF && precedence < precedences[t.peekToken] { switch t.peekToken { case token.OR, token.XOR, token.AND, token.SHL, token.SHR, token.ADD, token.SUB, token.MUL, token.QUO, token.REM: t.Next() leftExpr, err = parseBinaryExpr(t, leftExpr) } } return leftExpr, err } func parseIdent(t *tokenizer) (ast.Expr, *scanner.Error) { // If the identifier is one of the parameters of this function-like macro, // use the parameter value. if val, ok := t.params[t.curValue]; ok { return val, nil } if t.f != nil { // Check whether this identifier is actually a macro "call" with // parameters. In that case, we should parse the parameters and pass it // on to a new invocation of parseConst. if t.peekToken == token.LPAREN { if cursor, ok := t.f.names[t.curValue]; ok && t.f.isFunctionLikeMacro(cursor) { // We know the current and peek tokens (the peek one is the '(' // token). So skip ahead until the current token is the first // unknown token. t.Next() t.Next() // Parse the list of parameters until ')' (rparen) is found. params := []ast.Expr{} for i := 0; ; i++ { if i == 0 && t.curToken == token.RPAREN { break } x, err := parseConstExpr(t, precedenceLowest) if err != nil { return nil, err } params = append(params, x) t.Next() if t.curToken == token.COMMA { t.Next() } else if t.curToken == token.RPAREN { break } else { return nil, &scanner.Error{ Pos: t.fset.Position(t.curPos), Msg: "unexpected token " + t.curToken.String() + ", ',' or ')'", } } } // Evaluate the macro value and use it as the identifier value. rparen := t.curPos pos, text := t.f.getMacro(cursor) return parseConst(pos, t.fset, text, params, rparen, t.f) } } // Normally the name is something defined in the file (like another // macro) which we get the declaration from using getASTDeclName. // This ensures that names that are only referenced inside a macro are // still getting defined. if cursor, ok := t.f.names[t.curValue]; ok { return &ast.Ident{ NamePos: t.curPos, Name: t.f.getASTDeclName(t.curValue, cursor, false), }, nil } } // t.f is nil during testing. This is a fallback. return &ast.Ident{ NamePos: t.curPos, Name: "C." + t.curValue, }, nil } func parseBasicLit(t *tokenizer) (ast.Expr, *scanner.Error) { return &ast.BasicLit{ ValuePos: t.curPos, Kind: t.curToken, Value: t.curValue, }, nil } func parseParenExpr(t *tokenizer) (ast.Expr, *scanner.Error) { lparen := t.curPos t.Next() x, err := parseConstExpr(t, precedenceLowest) if err != nil { return nil, err } t.Next() if t.curToken != token.RPAREN { return nil, unexpectedToken(t, token.RPAREN) } expr := &ast.ParenExpr{ Lparen: lparen, X: x, Rparen: t.curPos, } return expr, nil } func parseBinaryExpr(t *tokenizer, left ast.Expr) (ast.Expr, *scanner.Error) { expression := &ast.BinaryExpr{ X: left, Op: t.curToken, OpPos: t.curPos, } precedence := precedences[t.curToken] t.Next() right, err := parseConstExpr(t, precedence) expression.Y = right return expression, err } func parseUnaryExpr(t *tokenizer) (ast.Expr, *scanner.Error) { expression := &ast.UnaryExpr{ OpPos: t.curPos, Op: t.curToken, } t.Next() x, err := parseConstExpr(t, precedencePrefix) expression.X = x return expression, err } // unexpectedToken returns an error of the form "unexpected token FOO, expected // BAR". func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error { return &scanner.Error{ Pos: t.fset.Position(t.curPos), Msg: fmt.Sprintf("unexpected token %s, expected %s", t.curToken, expected), } } // tokenizer reads C source code and converts it to Go tokens. type tokenizer struct { f *cgoFile curPos, peekPos token.Pos fset *token.FileSet curToken, peekToken token.Token curValue, peekValue string buf string params map[string]ast.Expr } // newTokenizer initializes a new tokenizer, positioned at the first token in // the string. func newTokenizer(start token.Pos, fset *token.FileSet, buf string, f *cgoFile) *tokenizer { t := &tokenizer{ f: f, peekPos: start, fset: fset, buf: buf, peekToken: token.ILLEGAL, params: make(map[string]ast.Expr), } // Parse the first two tokens (cur and peek). t.Next() t.Next() return t } // Next consumes the next token in the stream. There is no return value, read // the next token from the pos, token and value properties. func (t *tokenizer) Next() { // The previous peek is now the current token. t.curPos = t.peekPos t.curToken = t.peekToken t.curValue = t.peekValue // Parse the next peek token. if t.peekPos != token.NoPos { t.peekPos += token.Pos(len(t.curValue)) } for { if len(t.buf) == 0 { t.peekToken = token.EOF return } c := t.buf[0] switch { case c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v': // Skip whitespace. // Based on this source, not sure whether it represents C whitespace: // https://en.cppreference.com/w/cpp/string/byte/isspace if t.peekPos != token.NoPos { t.peekPos++ } t.buf = t.buf[1:] case len(t.buf) >= 2 && (string(t.buf[:2]) == "||" || string(t.buf[:2]) == "&&" || string(t.buf[:2]) == "<<" || string(t.buf[:2]) == ">>"): // Two-character tokens. switch c { case '&': t.peekToken = token.LAND case '|': t.peekToken = token.LOR case '<': t.peekToken = token.SHL case '>': t.peekToken = token.SHR default: panic("unreachable") } t.peekValue = t.buf[:2] t.buf = t.buf[2:] return case c == '(' || c == ')' || c == ',' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || c == '&' || c == '|' || c == '^': // Single-character tokens. // TODO: ++ (increment) and -- (decrement) operators. switch c { case '(': t.peekToken = token.LPAREN case ')': t.peekToken = token.RPAREN case ',': t.peekToken = token.COMMA case '+': t.peekToken = token.ADD case '-': t.peekToken = token.SUB case '*': t.peekToken = token.MUL case '/': t.peekToken = token.QUO case '%': t.peekToken = token.REM case '&': t.peekToken = token.AND case '|': t.peekToken = token.OR case '^': t.peekToken = token.XOR } t.peekValue = t.buf[:1] t.buf = t.buf[1:] return case c >= '0' && c <= '9': // Numeric constant (int, float, etc.). // Find the last non-numeric character. tokenLen := len(t.buf) hasDot := false for i, c := range t.buf { if c == '.' { hasDot = true } if c >= '0' && c <= '9' || c == '.' || c == '_' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' { tokenLen = i + 1 } else { break } } t.peekValue = t.buf[:tokenLen] t.buf = t.buf[tokenLen:] if hasDot { // Integer constants are more complicated than this but this is // a close approximation. // https://en.cppreference.com/w/cpp/language/integer_literal t.peekToken = token.FLOAT t.peekValue = strings.TrimRight(t.peekValue, "f") } else { t.peekToken = token.INT t.peekValue = strings.TrimRight(t.peekValue, "uUlL") } return case c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_': // Identifier. Find all remaining tokens that are part of this // identifier. tokenLen := len(t.buf) for i, c := range t.buf { if c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_' { tokenLen = i + 1 } else { break } } t.peekValue = t.buf[:tokenLen] t.buf = t.buf[tokenLen:] t.peekToken = token.IDENT return case c == '"': // String constant. Find the first '"' character that is not // preceded by a backslash. escape := false tokenLen := len(t.buf) for i, c := range t.buf { if i != 0 && c == '"' && !escape { tokenLen = i + 1 break } if !escape { escape = c == '\\' } } t.peekToken = token.STRING t.peekValue = t.buf[:tokenLen] t.buf = t.buf[tokenLen:] return case c == '\'': // Char (rune) constant. Find the first '\'' character that is not // preceded by a backslash. escape := false tokenLen := len(t.buf) for i, c := range t.buf { if i != 0 && c == '\'' && !escape { tokenLen = i + 1 break } if !escape { escape = c == '\\' } } t.peekToken = token.CHAR t.peekValue = t.buf[:tokenLen] t.buf = t.buf[tokenLen:] return default: t.peekToken = token.ILLEGAL return } } } ================================================ FILE: cgo/const_test.go ================================================ package cgo import ( "bytes" "go/format" "go/token" "strings" "testing" ) func TestParseConst(t *testing.T) { // Test converting a C constant to a Go constant. for _, tc := range []struct { C string Go string }{ {`5`, `5`}, {`(5)`, `(5)`}, {`(((5)))`, `(5)`}, {`)`, `error: 1:1: unexpected token )`}, {`5)`, `error: 1:2: unexpected token ), expected end of expression`}, {" \t)", `error: 1:4: unexpected token )`}, {`5.8f`, `5.8`}, {`foo`, `C.foo`}, {``, `error: 1:1: empty constant`}, // empty constants not allowed in Go {`"foo"`, `"foo"`}, {`"a\\n"`, `"a\\n"`}, {`"a\n"`, `"a\n"`}, {`"a\""`, `"a\""`}, {`'a'`, `'a'`}, {`0b10`, `0b10`}, {`0x1234_5678`, `0x1234_5678`}, {`5 5`, `error: 1:3: unexpected token INT, expected end of expression`}, // test for a bugfix // Binary operators. {`5+5`, `5 + 5`}, {`5-5`, `5 - 5`}, {`5*5`, `5 * 5`}, {`5/5`, `5 / 5`}, {`5%5`, `5 % 5`}, {`5&5`, `5 & 5`}, {`5|5`, `5 | 5`}, {`5^5`, `5 ^ 5`}, {`5<<5`, `5 << 5`}, {`5>>5`, `5 >> 5`}, {`5>>5 + 3`, `5 >> (5 + 3)`}, {`5>>5 ^ 3`, `5>>5 ^ 3`}, {`5||5`, `error: 1:2: unexpected token ||, expected end of expression`}, // logical binops aren't supported yet {`(5/5)`, `(5 / 5)`}, {`1 - 2`, `1 - 2`}, {`1 - 2 + 3`, `1 - 2 + 3`}, {`1 - 2 * 3`, `1 - 2*3`}, {`(1 - 2) * 3`, `(1 - 2) * 3`}, {`1 * 2 - 3`, `1*2 - 3`}, {`1 * (2 - 3)`, `1 * (2 - 3)`}, // Unary operators. {`-5`, `-5`}, {`-5-2`, `-5 - 2`}, {`5 - - 2`, `5 - -2`}, } { fset := token.NewFileSet() startPos := fset.AddFile("", -1, 1000).Pos(0) expr, err := parseConst(startPos, fset, tc.C, nil, token.NoPos, nil) s := "" if err != nil { if !strings.HasPrefix(tc.Go, "error: ") { t.Errorf("expected value %#v for C constant %#v but got error %#v", tc.Go, tc.C, err.Error()) continue } s = "error: " + err.Error() } else if expr != nil { // Serialize the Go constant to a string, for more readable test // cases. buf := &bytes.Buffer{} err := format.Node(buf, fset, expr) if err != nil { t.Errorf("could not format expr from C constant %#v: %v", tc.C, err) continue } s = buf.String() } if s != tc.Go { t.Errorf("C constant %#v was parsed to %#v while expecting %#v", tc.C, s, tc.Go) } } } ================================================ FILE: cgo/libclang.go ================================================ package cgo // This file parses a fragment of C with libclang and stores the result for AST // modification. It does not touch the AST itself. import ( "bytes" "crypto/sha256" "crypto/sha512" "encoding/hex" "fmt" "go/ast" "go/scanner" "go/token" "path/filepath" "strconv" "strings" "unsafe" "tinygo.org/x/go-llvm" ) /* #include // If this fails, libclang headers aren't available. Please take a look here: https://tinygo.org/docs/guides/build/ #include #include #include // This struct should be ABI-compatible on all platforms (uintptr_t has the same // alignment etc. as void*) but does not include void* pointers that are not // always real pointers. // The Go garbage collector assumes that all non-nil pointer-typed integers are // actually pointers. This is not always true, as data[1] often contains 0x1, // which is clearly not a valid pointer. Usually the GC won't catch this issue, // but occasionally it will leading to a crash with a vague error message. typedef struct { enum CXCursorKind kind; int xdata; uintptr_t data[3]; } GoCXCursor; // Forwarding functions. They are implemented in libclang_stubs.c and forward to // the real functions without doing anything else, thus they are entirely // compatible with the versions without tinygo_ prefix. The only difference is // the CXCursor type, which has been replaced with GoCXCursor. GoCXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu); unsigned tinygo_clang_visitChildren(GoCXCursor parent, CXCursorVisitor visitor, CXClientData client_data); CXString tinygo_clang_getCursorSpelling(GoCXCursor c); CXString tinygo_clang_getCursorPrettyPrinted(GoCXCursor c, CXPrintingPolicy Policy); CXPrintingPolicy tinygo_clang_getCursorPrintingPolicy(GoCXCursor c); enum CXCursorKind tinygo_clang_getCursorKind(GoCXCursor c); CXType tinygo_clang_getCursorType(GoCXCursor c); GoCXCursor tinygo_clang_getTypeDeclaration(CXType t); CXType tinygo_clang_getTypedefDeclUnderlyingType(GoCXCursor c); CXType tinygo_clang_getCursorResultType(GoCXCursor c); int tinygo_clang_Cursor_getNumArguments(GoCXCursor c); GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i); enum CX_StorageClass tinygo_clang_Cursor_getStorageClass(GoCXCursor c); CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c); CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c); CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c); long long tinygo_clang_getEnumConstantDeclValue(GoCXCursor c); CXType tinygo_clang_getEnumDeclIntegerType(GoCXCursor c); unsigned tinygo_clang_Cursor_isAnonymous(GoCXCursor c); unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c); unsigned tinygo_clang_Cursor_isMacroFunctionLike(GoCXCursor c); // Fix some warnings on Windows ARM. Without the __declspec(dllexport), it gives warnings like this: // In file included from _cgo_export.c:4: // cgo-gcc-export-header-prolog:49:34: warning: redeclaration of 'tinygo_clang_globals_visitor' should not add 'dllexport' attribute [-Wdll-attribute-on-redeclaration] // libclang.go:68:5: note: previous declaration is here // See: https://github.com/golang/go/issues/49721 #if defined(_WIN32) #define CGO_DECL __declspec(dllexport) #else #define CGO_DECL #endif CGO_DECL int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); CGO_DECL int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); CGO_DECL void tinygo_clang_inclusion_visitor(CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data); */ import "C" // storedRefs stores references to types, used for clang_visitChildren. var storedRefs refMap var diagnosticSeverity = [...]string{ C.CXDiagnostic_Ignored: "ignored", C.CXDiagnostic_Note: "note", C.CXDiagnostic_Warning: "warning", C.CXDiagnostic_Error: "error", C.CXDiagnostic_Fatal: "fatal", } // Alias so that cgo.go (which doesn't import Clang related stuff and is in // theory decoupled from Clang) can also use this type. type clangCursor = C.GoCXCursor func init() { // Check that we haven't messed up LLVM versioning. // This can happen when llvm_config_*.go files in either this or the // tinygo.org/x/go-llvm packages is incorrect. It should not ever happen // with byollvm. if C.LLVM_VERSION_STRING != llvm.Version { panic("incorrect build: using LLVM version " + llvm.Version + " in the tinygo.org/x/llvm package, and version " + C.LLVM_VERSION_STRING + " in the ./cgo package") } } func (f *cgoFile) readNames(fragment string, cflags []string, filename string, callback func(map[string]clangCursor)) { index := C.clang_createIndex(0, 0) defer C.clang_disposeIndex(index) // pretend to be a .c file filenameC := C.CString(filename + "!cgo.c") defer C.free(unsafe.Pointer(filenameC)) fragmentC := C.CString(fragment) defer C.free(unsafe.Pointer(fragmentC)) unsavedFile := C.struct_CXUnsavedFile{ Filename: filenameC, Length: C.ulong(len(fragment)), Contents: fragmentC, } // convert Go slice of strings to C array of strings. cmdargsC := C.malloc(C.size_t(len(cflags)) * C.size_t(unsafe.Sizeof(uintptr(0)))) defer C.free(cmdargsC) cmdargs := (*[1 << 16]*C.char)(cmdargsC) for i, cflag := range cflags { s := C.CString(cflag) cmdargs[i] = s defer C.free(unsafe.Pointer(s)) } var unit C.CXTranslationUnit errCode := C.clang_parseTranslationUnit2( index, filenameC, (**C.char)(cmdargsC), C.int(len(cflags)), // command line args &unsavedFile, 1, // unsaved files C.CXTranslationUnit_DetailedPreprocessingRecord, &unit) if errCode != 0 { // This is probably a bug in the usage of libclang. panic("cgo: failed to parse source with libclang") } defer C.clang_disposeTranslationUnit(unit) // Report parser and type errors. if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 { addDiagnostic := func(diagnostic C.CXDiagnostic) { spelling := getString(C.clang_getDiagnosticSpelling(diagnostic)) severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)] location := C.clang_getDiagnosticLocation(diagnostic) pos := f.getClangLocationPosition(location, unit) f.addError(pos, severity+": "+spelling) } for i := 0; i < numDiagnostics; i++ { diagnostic := C.clang_getDiagnostic(unit, C.uint(i)) addDiagnostic(diagnostic) // Child diagnostics (like notes on redefinitions). diagnostics := C.clang_getChildDiagnostics(diagnostic) for j := 0; j < int(C.clang_getNumDiagnosticsInSet(diagnostics)); j++ { addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j))) } } } // Extract information required by CGo. ref := storedRefs.Put(f) defer storedRefs.Remove(ref) cursor := C.tinygo_clang_getTranslationUnitCursor(unit) C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref)) // Determine files read during CGo processing, for caching. inclusionCallback := func(includedFile C.CXFile) { // Get full file path. path := getString(C.clang_getFileName(includedFile)) // Get contents of file (that should be in-memory). size := C.size_t(0) rawData := C.clang_getFileContents(unit, includedFile, &size) if rawData == nil { // Sanity check. This should (hopefully) never trigger. panic("libclang: file contents was not loaded") } data := (*[1 << 24]byte)(unsafe.Pointer(rawData))[:size] // Hash the contents if it isn't hashed yet. if _, ok := f.visitedFiles[path]; !ok { // already stored sum := sha512.Sum512_224(data) f.visitedFiles[path] = sum[:] } } inclusionCallbackRef := storedRefs.Put(inclusionCallback) defer storedRefs.Remove(inclusionCallbackRef) C.clang_getInclusions(unit, C.CXInclusionVisitor(C.tinygo_clang_inclusion_visitor), C.CXClientData(inclusionCallbackRef)) // Do all the C AST operations inside a callback. This makes sure that // libclang related memory is only freed after it is not necessary anymore. callback(f.names) } // Convert the AST node under the given Clang cursor to a Go AST node and return // it. func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { kind := C.tinygo_clang_getCursorKind(c) pos := f.getCursorPosition(c) switch kind { case C.CXCursor_FunctionDecl: cursorType := C.tinygo_clang_getCursorType(c) numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c)) obj := &ast.Object{ Kind: ast.Fun, Name: "_Cgo_" + name, } exportName := name localName := name var stringSignature string if C.tinygo_clang_Cursor_getStorageClass(c) == C.CX_SC_Static { // A static function is assigned a globally unique symbol name based // on the file path (like _Cgo_static_2d09198adbf58f4f4655_foo) and // has a different Go name in the form of C.foo!symbols.go instead // of just C.foo. path := f.importPath + "/" + filepath.Base(f.fset.File(f.file.Pos()).Name()) staticIDBuf := sha256.Sum256([]byte(path)) staticID := hex.EncodeToString(staticIDBuf[:10]) exportName = "_Cgo_static_" + staticID + "_" + name localName = name + "!" + filepath.Base(path) // Create a signature. This is necessary for MacOS to forward the // call, because MacOS doesn't support aliases like ELF and PE do. // (There is N_INDR but __attribute__((alias("..."))) doesn't work). policy := C.tinygo_clang_getCursorPrintingPolicy(c) defer C.clang_PrintingPolicy_dispose(policy) C.clang_PrintingPolicy_setProperty(policy, C.CXPrintingPolicy_TerseOutput, 1) stringSignature = getString(C.tinygo_clang_getCursorPrettyPrinted(c, policy)) stringSignature = strings.Replace(stringSignature, " "+name+"(", " "+exportName+"(", 1) stringSignature = strings.TrimPrefix(stringSignature, "static ") } args := make([]*ast.Field, numArgs) decl := &ast.FuncDecl{ Doc: &ast.CommentGroup{ List: []*ast.Comment{ { Slash: pos - 1, Text: "//export " + exportName, }, }, }, Name: &ast.Ident{ NamePos: pos, Name: "_Cgo_" + localName, Obj: obj, }, Type: &ast.FuncType{ Func: pos, Params: &ast.FieldList{ Opening: pos, List: args, Closing: pos, }, }, } var doc []string if C.clang_isFunctionTypeVariadic(cursorType) != 0 { doc = append(doc, "//go:variadic") } if _, ok := f.noescapingFuncs[name]; ok { doc = append(doc, "//go:noescape") f.noescapingFuncs[name].used = true } if len(doc) != 0 { decl.Doc.List = append(decl.Doc.List, &ast.Comment{ Slash: pos - 1, Text: strings.Join(doc, "\n"), }) } for i := 0; i < numArgs; i++ { arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i)) argName := getString(C.tinygo_clang_getCursorSpelling(arg)) argType := C.clang_getArgType(cursorType, C.uint(i)) if argName == "" { argName = "$" + strconv.Itoa(i) } args[i] = &ast.Field{ Names: []*ast.Ident{ { NamePos: pos, Name: argName, Obj: &ast.Object{ Kind: ast.Var, Name: argName, Decl: decl, }, }, }, Type: f.makeDecayingASTType(argType, pos), } } resultType := C.tinygo_clang_getCursorResultType(c) if resultType.kind != C.CXType_Void { decl.Type.Results = &ast.FieldList{ List: []*ast.Field{ { Type: f.makeASTType(resultType, pos), }, }, } } obj.Decl = decl return decl, stringSignature case C.CXCursor_StructDecl, C.CXCursor_UnionDecl: typ := f.makeASTRecordType(c, pos) typeName := "_Cgo_" + name typeExpr := typ.typeExpr if typ.unionSize != 0 { // Convert to a single-field struct type. typeExpr = f.makeUnionField(typ) } obj := &ast.Object{ Kind: ast.Typ, Name: typeName, } typeSpec := &ast.TypeSpec{ Name: &ast.Ident{ NamePos: typ.pos, Name: typeName, Obj: obj, }, Type: typeExpr, } obj.Decl = typeSpec return typeSpec, typ case C.CXCursor_TypedefDecl: typeName := "_Cgo_" + name underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c) obj := &ast.Object{ Kind: ast.Typ, Name: typeName, } typeSpec := &ast.TypeSpec{ Name: &ast.Ident{ NamePos: pos, Name: typeName, Obj: obj, }, Type: f.makeASTType(underlyingType, pos), } if underlyingType.kind != C.CXType_Enum { typeSpec.Assign = pos } obj.Decl = typeSpec return typeSpec, nil case C.CXCursor_VarDecl: cursorType := C.tinygo_clang_getCursorType(c) typeExpr := f.makeASTType(cursorType, pos) gen := &ast.GenDecl{ TokPos: pos, Tok: token.VAR, Lparen: token.NoPos, Rparen: token.NoPos, Doc: &ast.CommentGroup{ List: []*ast.Comment{ { Slash: pos - 1, Text: "//go:extern " + name, }, }, }, } obj := &ast.Object{ Kind: ast.Var, Name: "_Cgo_" + name, } valueSpec := &ast.ValueSpec{ Names: []*ast.Ident{{ NamePos: pos, Name: "_Cgo_" + name, Obj: obj, }}, Type: typeExpr, } obj.Decl = valueSpec gen.Specs = append(gen.Specs, valueSpec) return gen, nil case C.CXCursor_MacroDefinition: tokenPos, value := f.getMacro(c) expr, scannerError := parseConst(tokenPos, f.fset, value, nil, token.NoPos, f) if scannerError != nil { f.errors = append(f.errors, *scannerError) return nil, nil } gen := &ast.GenDecl{ TokPos: token.NoPos, Tok: token.CONST, Lparen: token.NoPos, Rparen: token.NoPos, } obj := &ast.Object{ Kind: ast.Con, Name: "_Cgo_" + name, } valueSpec := &ast.ValueSpec{ Names: []*ast.Ident{{ NamePos: pos, Name: "_Cgo_" + name, Obj: obj, }}, Values: []ast.Expr{expr}, } obj.Decl = valueSpec gen.Specs = append(gen.Specs, valueSpec) return gen, nil case C.CXCursor_EnumDecl: obj := &ast.Object{ Kind: ast.Typ, Name: "_Cgo_" + name, } underlying := C.tinygo_clang_getEnumDeclIntegerType(c) // TODO: gc's CGo implementation uses types such as `uint32` for enums // instead of types such as C.int, which are used here. typeSpec := &ast.TypeSpec{ Name: &ast.Ident{ NamePos: pos, Name: "_Cgo_" + name, Obj: obj, }, Assign: pos, Type: f.makeASTType(underlying, pos), } obj.Decl = typeSpec return typeSpec, nil case C.CXCursor_EnumConstantDecl: value := C.tinygo_clang_getEnumConstantDeclValue(c) expr := &ast.BasicLit{ ValuePos: pos, Kind: token.INT, Value: strconv.FormatInt(int64(value), 10), } gen := &ast.GenDecl{ TokPos: token.NoPos, Tok: token.CONST, Lparen: token.NoPos, Rparen: token.NoPos, } obj := &ast.Object{ Kind: ast.Con, Name: "_Cgo_" + name, } valueSpec := &ast.ValueSpec{ Names: []*ast.Ident{{ NamePos: pos, Name: "_Cgo_" + name, Obj: obj, }}, Values: []ast.Expr{expr}, } obj.Decl = valueSpec gen.Specs = append(gen.Specs, valueSpec) return gen, nil default: f.addError(pos, fmt.Sprintf("internal error: unknown cursor type: %d", kind)) return nil, nil } } // Return whether this is a macro that's also function-like, like this: // // #define add(a, b) (a+b) func (f *cgoFile) isFunctionLikeMacro(c clangCursor) bool { if C.tinygo_clang_getCursorKind(c) != C.CXCursor_MacroDefinition { return false } return C.tinygo_clang_Cursor_isMacroFunctionLike(c) != 0 } // Get the macro value: the position in the source file and the string value of // the macro. func (f *cgoFile) getMacro(c clangCursor) (pos token.Pos, value string) { // Extract tokens from the Clang tokenizer. // See: https://stackoverflow.com/a/19074846/559350 sourceRange := C.tinygo_clang_getCursorExtent(c) tu := C.tinygo_clang_Cursor_getTranslationUnit(c) var rawTokens *C.CXToken var numTokens C.unsigned C.clang_tokenize(tu, sourceRange, &rawTokens, &numTokens) tokens := unsafe.Slice(rawTokens, numTokens) defer C.clang_disposeTokens(tu, rawTokens, numTokens) // Convert this range of tokens back to source text. // Ugly, but it works well enough. sourceBuf := &bytes.Buffer{} var startOffset int for i, token := range tokens { spelling := getString(C.clang_getTokenSpelling(tu, token)) location := C.clang_getTokenLocation(tu, token) var tokenOffset C.unsigned C.clang_getExpansionLocation(location, nil, nil, nil, &tokenOffset) if i == 0 { // The first token is the macro name itself. // Skip it (after using its location). startOffset = int(tokenOffset) } else { // Later tokens are the macro contents. for int(tokenOffset) > (startOffset + sourceBuf.Len()) { // Pad the source text with whitespace (that must have been // present in the original source as well). sourceBuf.WriteByte(' ') } sourceBuf.WriteString(spelling) } } value = sourceBuf.String() // Obtain the position of this token. This is the position of the first // character in the 'value' string and is used to report errors at the // correct location in the source file. pos = f.getCursorPosition(c) return } func getString(clangString C.CXString) (s string) { rawString := C.clang_getCString(clangString) s = C.GoString(rawString) C.clang_disposeString(clangString) return } //export tinygo_clang_globals_visitor func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int { f := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoFile) switch C.tinygo_clang_getCursorKind(c) { case C.CXCursor_FunctionDecl: name := getString(C.tinygo_clang_getCursorSpelling(c)) f.names[name] = c case C.CXCursor_StructDecl: name := getString(C.tinygo_clang_getCursorSpelling(c)) if name != "" { f.names["struct_"+name] = c } case C.CXCursor_UnionDecl: name := getString(C.tinygo_clang_getCursorSpelling(c)) if name != "" { f.names["union_"+name] = c } case C.CXCursor_TypedefDecl: typedefType := C.tinygo_clang_getCursorType(c) name := getString(C.clang_getTypedefName(typedefType)) f.names[name] = c case C.CXCursor_VarDecl: name := getString(C.tinygo_clang_getCursorSpelling(c)) f.names[name] = c case C.CXCursor_MacroDefinition: name := getString(C.tinygo_clang_getCursorSpelling(c)) f.names[name] = c case C.CXCursor_EnumDecl: name := getString(C.tinygo_clang_getCursorSpelling(c)) if name != "" { // Named enum, which can be referenced from Go using C.enum_foo. f.names["enum_"+name] = c } // The enum fields are in global scope, so recurse to visit them. return C.CXChildVisit_Recurse case C.CXCursor_EnumConstantDecl: // We arrive here because of the "Recurse" above. name := getString(C.tinygo_clang_getCursorSpelling(c)) f.names[name] = c } return C.CXChildVisit_Continue } // Get the precise location in the source code. Used for uniquely identifying // source locations. func (f *cgoFile) getUniqueLocationID(pos token.Pos, cursor C.GoCXCursor) interface{} { clangLocation := C.tinygo_clang_getCursorLocation(cursor) var file C.CXFile var line C.unsigned var column C.unsigned C.clang_getFileLocation(clangLocation, &file, &line, &column, nil) location := token.Position{ Filename: getString(C.clang_getFileName(file)), Line: int(line), Column: int(column), } if location.Filename == "" || location.Line == 0 { // Not sure when this would happen, but protect from it anyway. f.addError(pos, "could not find file/line information") } return location } // getCursorPosition returns a usable token.Pos from a libclang cursor. func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos { return p.getClangLocationPosition(C.tinygo_clang_getCursorLocation(cursor), C.tinygo_clang_Cursor_getTranslationUnit(cursor)) } // getClangLocationPosition returns a usable token.Pos based on a libclang // location and translation unit. If the file for this cursor has not been seen // before, it is read from libclang (which already has the file in memory) and // added to the token.FileSet. func (p *cgoPackage) getClangLocationPosition(location C.CXSourceLocation, tu C.CXTranslationUnit) token.Pos { var file C.CXFile var line C.unsigned var column C.unsigned var offset C.unsigned C.clang_getExpansionLocation(location, &file, &line, &column, &offset) if line == 0 || file == nil { // Invalid token. return token.NoPos } filename := getString(C.clang_getFileName(file)) if _, ok := p.tokenFiles[filename]; !ok { // File has not been seen before in this package, add line information // now by reading the file from libclang. var size C.size_t sourcePtr := C.clang_getFileContents(tu, file, &size) source := ((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[:size:size] lines := []int{0} for i := 0; i < len(source)-1; i++ { if source[i] == '\n' { lines = append(lines, i+1) } } f := p.fset.AddFile(filename, -1, int(size)) f.SetLines(lines) p.tokenFiles[filename] = f // Add dummy file AST, to satisfy the type checker. astFile := &ast.File{ Package: f.Pos(0), Name: ast.NewIdent(p.packageName), } setASTFileFields(astFile, f.Pos(0), f.Pos(int(size))) p.cgoFiles = append(p.cgoFiles, astFile) } positionFile := p.tokenFiles[filename] // Check for alternative line/column information (set with a line directive). var filename2String C.CXString var line2 C.unsigned var column2 C.unsigned C.clang_getPresumedLocation(location, &filename2String, &line2, &column2) filename2 := getString(filename2String) if filename2 != filename || line2 != line || column2 != column { // The location was changed with a preprocessor directive. // TODO: this only works for locations that are added in order. Adding // line/column info to a file that already has line/column info after // the given offset is ignored. positionFile.AddLineColumnInfo(int(offset), filename2, int(line2), int(column2)) } return positionFile.Pos(int(offset)) } // addError is a utility function to add an error to the list of errors. It will // convert the token position to a line/column position first, and call // addErrorAt. func (p *cgoPackage) addError(pos token.Pos, msg string) { p.addErrorAt(p.fset.PositionFor(pos, true), msg) } // addErrorAfter is like addError, but adds the text `after` to the source // location. func (p *cgoPackage) addErrorAfter(pos token.Pos, after, msg string) { position := p.fset.PositionFor(pos, true) lines := strings.Split(after, "\n") if len(lines) != 1 { // Adjust lines. // For why we can't just do pos+token.Pos(len(after)), see: // https://github.com/golang/go/issues/35803 position.Line += len(lines) - 1 position.Column = len(lines[len(lines)-1]) + 1 } else { position.Column += len(after) } p.addErrorAt(position, msg) } // addErrorAt is a utility function to add an error to the list of errors. func (p *cgoPackage) addErrorAt(position token.Position, msg string) { p.errors = append(p.errors, scanner.Error{ Pos: position, Msg: msg, }) } // makeDecayingASTType does the same as makeASTType but takes care of decaying // types (arrays in function parameters, etc). It is otherwise identical to // makeASTType. func (f *cgoFile) makeDecayingASTType(typ C.CXType, pos token.Pos) ast.Expr { // Strip typedefs, if any. underlyingType := typ if underlyingType.kind == C.CXType_Elaborated { // Starting with LLVM 16, the elaborated type is used for more types. // According to the Clang documentation, the elaborated type has no // semantic meaning so can be stripped (it is used to better convey type // name information). // Source: // https://clang.llvm.org/doxygen/classclang_1_1ElaboratedType.html#details // > The type itself is always "sugar", used to express what was written // > in the source code but containing no additional semantic information. underlyingType = C.clang_Type_getNamedType(underlyingType) } if underlyingType.kind == C.CXType_Typedef { c := C.tinygo_clang_getTypeDeclaration(underlyingType) underlyingType = C.tinygo_clang_getTypedefDeclUnderlyingType(c) // TODO: support a chain of typedefs. At the moment, it seems to get // stuck in an endless loop when trying to get to the most underlying // type. } // Check for decaying type. An example would be an array type in a // parameter. This declaration: // void foo(char buf[6]); // is the same as this one: // void foo(char *buf); // But this one: // void bar(char buf[6][4]); // equals this: // void bar(char *buf[4]); // so not all array dimensions should be stripped, just the first one. // TODO: there are more kinds of decaying types. if underlyingType.kind == C.CXType_ConstantArray { // Apply type decaying. pointeeType := C.clang_getElementType(underlyingType) return &ast.StarExpr{ Star: pos, X: f.makeASTType(pointeeType, pos), } } return f.makeASTType(typ, pos) } // makeASTType return the ast.Expr for the given libclang type. In other words, // it converts a libclang type to a type in the Go AST. func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr { var typeName string switch typ.kind { case C.CXType_Char_S, C.CXType_Char_U: typeName = "_Cgo_char" case C.CXType_SChar: typeName = "_Cgo_schar" case C.CXType_UChar: typeName = "_Cgo_uchar" case C.CXType_Short: typeName = "_Cgo_short" case C.CXType_UShort: typeName = "_Cgo_ushort" case C.CXType_Int: typeName = "_Cgo_int" case C.CXType_UInt: typeName = "_Cgo_uint" case C.CXType_Long: typeName = "_Cgo_long" case C.CXType_ULong: typeName = "_Cgo_ulong" case C.CXType_LongLong: typeName = "_Cgo_longlong" case C.CXType_ULongLong: typeName = "_Cgo_ulonglong" case C.CXType_Bool: typeName = "bool" case C.CXType_Float, C.CXType_Double, C.CXType_LongDouble: switch C.clang_Type_getSizeOf(typ) { case 4: typeName = "float32" case 8: typeName = "float64" default: // Don't do anything, rely on the fallback code to show a somewhat // sensible error message like "undeclared name: C.long double". } case C.CXType_Complex: switch C.clang_Type_getSizeOf(typ) { case 8: typeName = "complex64" case 16: typeName = "complex128" } case C.CXType_Pointer: pointeeType := C.clang_getPointeeType(typ) if pointeeType.kind == C.CXType_Void { // void* type is translated to Go as unsafe.Pointer return &ast.SelectorExpr{ X: &ast.Ident{ NamePos: pos, Name: "unsafe", }, Sel: &ast.Ident{ NamePos: pos, Name: "Pointer", }, } } return &ast.StarExpr{ Star: pos, X: f.makeASTType(pointeeType, pos), } case C.CXType_ConstantArray: return &ast.ArrayType{ Lbrack: pos, Len: &ast.BasicLit{ ValuePos: pos, Kind: token.INT, Value: strconv.FormatInt(int64(C.clang_getArraySize(typ)), 10), }, Elt: f.makeASTType(C.clang_getElementType(typ), pos), } case C.CXType_FunctionProto: // Be compatible with gc, which uses the *[0]byte type for function // pointer types. // Return type [0]byte because this is a function type, not a pointer to // this function type. return &ast.ArrayType{ Lbrack: pos, Len: &ast.BasicLit{ ValuePos: pos, Kind: token.INT, Value: "0", }, Elt: &ast.Ident{ NamePos: pos, Name: "byte", }, } case C.CXType_Typedef: name := getString(C.clang_getTypedefName(typ)) c := C.tinygo_clang_getTypeDeclaration(typ) return &ast.Ident{ NamePos: pos, Name: f.getASTDeclName(name, c, false), } case C.CXType_Elaborated: underlying := C.clang_Type_getNamedType(typ) switch underlying.kind { case C.CXType_Record: return f.makeASTType(underlying, pos) case C.CXType_Enum: return f.makeASTType(underlying, pos) case C.CXType_Typedef: return f.makeASTType(underlying, pos) default: typeKindSpelling := getString(C.clang_getTypeKindSpelling(underlying.kind)) f.addError(pos, fmt.Sprintf("unknown elaborated type (libclang type kind %s)", typeKindSpelling)) typeName = "" } case C.CXType_Record: cursor := C.tinygo_clang_getTypeDeclaration(typ) name := getString(C.tinygo_clang_getCursorSpelling(cursor)) var cgoRecordPrefix string switch C.tinygo_clang_getCursorKind(cursor) { case C.CXCursor_StructDecl: cgoRecordPrefix = "struct_" case C.CXCursor_UnionDecl: cgoRecordPrefix = "union_" default: // makeASTRecordType will create an appropriate error. cgoRecordPrefix = "record_" } if name == "" || C.tinygo_clang_Cursor_isAnonymous(cursor) != 0 { // Anonymous record, probably inside a typedef. location := f.getUniqueLocationID(pos, cursor) name = f.getUnnamedDeclName("_Ctype_"+cgoRecordPrefix+"__", location) } else { name = cgoRecordPrefix + name } return &ast.Ident{ NamePos: pos, Name: f.getASTDeclName(name, cursor, false), } case C.CXType_Enum: cursor := C.tinygo_clang_getTypeDeclaration(typ) name := getString(C.tinygo_clang_getCursorSpelling(cursor)) if name == "" { // Anonymous enum, probably inside a typedef. location := f.getUniqueLocationID(pos, cursor) name = f.getUnnamedDeclName("_Ctype_enum___", location) } else { name = "enum_" + name } return &ast.Ident{ NamePos: pos, Name: f.getASTDeclName(name, cursor, false), } } if typeName == "" { // Report this as an error. typeSpelling := getString(C.clang_getTypeSpelling(typ)) typeKindSpelling := getString(C.clang_getTypeKindSpelling(typ.kind)) f.addError(pos, fmt.Sprintf("unknown C type: %v (libclang type kind %s)", typeSpelling, typeKindSpelling)) typeName = "_Cgo_" } return &ast.Ident{ NamePos: pos, Name: typeName, } } // getIntegerType returns an AST node that defines types such as C.int. func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSpec { pos := p.getCursorPosition(cursor) // Find a Go type that matches the size and signedness of the given C type. underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(cursor) var goName string typeSize := C.clang_Type_getSizeOf(underlyingType) switch name { case "_Cgo_char": if typeSize != 1 { // This happens for some very special purpose architectures // (DSPs etc.) that are not currently targeted. // https://www.embecosm.com/2017/04/18/non-8-bit-char-support-in-clang-and-llvm/ p.addError(pos, fmt.Sprintf("unknown char width: %d", typeSize)) } switch underlyingType.kind { case C.CXType_Char_S: goName = "int8" case C.CXType_Char_U: goName = "uint8" } case "_Cgo_schar", "_Cgo_short", "_Cgo_int", "_Cgo_long", "_Cgo_longlong": switch typeSize { case 1: goName = "int8" case 2: goName = "int16" case 4: goName = "int32" case 8: goName = "int64" } case "_Cgo_uchar", "_Cgo_ushort", "_Cgo_uint", "_Cgo_ulong", "_Cgo_ulonglong": switch typeSize { case 1: goName = "uint8" case 2: goName = "uint16" case 4: goName = "uint32" case 8: goName = "uint64" } } if goName == "" { // should not happen p.addError(pos, "internal error: did not find Go type for C type "+name) goName = "int" } // Construct an *ast.TypeSpec for this type. obj := &ast.Object{ Kind: ast.Typ, Name: name, } spec := &ast.TypeSpec{ Name: &ast.Ident{ NamePos: pos, Name: name, Obj: obj, }, Type: &ast.Ident{ NamePos: pos, Name: goName, }, } obj.Decl = spec return spec } // makeASTRecordType parses a C record (struct or union) and translates it into // a Go struct type. func (f *cgoFile) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) *elaboratedTypeInfo { fieldList := &ast.FieldList{ Opening: pos, Closing: pos, } var bitfieldList []bitfieldInfo inBitfield := false bitfieldNum := 0 ref := storedRefs.Put(struct { fieldList *ast.FieldList file *cgoFile inBitfield *bool bitfieldNum *int bitfieldList *[]bitfieldInfo }{fieldList, f, &inBitfield, &bitfieldNum, &bitfieldList}) defer storedRefs.Remove(ref) C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref)) renameFieldKeywords(fieldList) switch C.tinygo_clang_getCursorKind(cursor) { case C.CXCursor_StructDecl: return &elaboratedTypeInfo{ typeExpr: &ast.StructType{ Struct: pos, Fields: fieldList, }, pos: pos, bitfields: bitfieldList, } case C.CXCursor_UnionDecl: typeInfo := &elaboratedTypeInfo{ typeExpr: &ast.StructType{ Struct: pos, Fields: fieldList, }, pos: pos, bitfields: bitfieldList, } if len(fieldList.List) <= 1 { // Useless union, treat it as a regular struct. return typeInfo } if bitfieldList != nil { // This is valid C... but please don't do this. f.addError(pos, "bitfield in a union is not supported") } typ := C.tinygo_clang_getCursorType(cursor) alignInBytes := int64(C.clang_Type_getAlignOf(typ)) sizeInBytes := int64(C.clang_Type_getSizeOf(typ)) if sizeInBytes == 0 { f.addError(pos, "zero-length union is not supported") } typeInfo.unionSize = sizeInBytes typeInfo.unionAlign = alignInBytes return typeInfo default: cursorKind := C.tinygo_clang_getCursorKind(cursor) cursorKindSpelling := getString(C.clang_getCursorKindSpelling(cursorKind)) f.addError(pos, fmt.Sprintf("expected StructDecl or UnionDecl, not %s", cursorKindSpelling)) return &elaboratedTypeInfo{ typeExpr: &ast.StructType{ Struct: pos, }, pos: pos, } } } //export tinygo_clang_struct_visitor func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int { passed := storedRefs.Get(unsafe.Pointer(client_data)).(struct { fieldList *ast.FieldList file *cgoFile inBitfield *bool bitfieldNum *int bitfieldList *[]bitfieldInfo }) fieldList := passed.fieldList f := passed.file inBitfield := passed.inBitfield bitfieldNum := passed.bitfieldNum bitfieldList := passed.bitfieldList pos := f.getCursorPosition(c) switch cursorKind := C.tinygo_clang_getCursorKind(c); cursorKind { case C.CXCursor_FieldDecl: // Expected. This is a regular field. case C.CXCursor_StructDecl, C.CXCursor_UnionDecl: // Ignore. The next field will be the struct/union itself. return C.CXChildVisit_Continue default: cursorKindSpelling := getString(C.clang_getCursorKindSpelling(cursorKind)) f.addError(pos, fmt.Sprintf("expected FieldDecl in struct or union, not %s", cursorKindSpelling)) return C.CXChildVisit_Continue } name := getString(C.tinygo_clang_getCursorSpelling(c)) if name == "" { // Assume this is a bitfield of 0 bits. // Warning: this is not necessarily true! return C.CXChildVisit_Continue } typ := C.tinygo_clang_getCursorType(c) field := &ast.Field{ Type: f.makeASTType(typ, f.getCursorPosition(c)), } offsetof := int64(C.clang_Type_getOffsetOf(C.tinygo_clang_getCursorType(parent), C.CString(name))) alignOf := int64(C.clang_Type_getAlignOf(typ) * 8) bitfieldOffset := offsetof % alignOf if bitfieldOffset != 0 { if C.tinygo_clang_Cursor_isBitField(c) != 1 { f.addError(pos, "expected a bitfield") return C.CXChildVisit_Continue } if !*inBitfield { *bitfieldNum++ } bitfieldName := "__bitfield_" + strconv.Itoa(*bitfieldNum) prevField := fieldList.List[len(fieldList.List)-1] if !*inBitfield { // The previous element also was a bitfield, but wasn't noticed // then. Add it now. *inBitfield = true *bitfieldList = append(*bitfieldList, bitfieldInfo{ field: prevField, name: prevField.Names[0].Name, startBit: 0, pos: prevField.Names[0].NamePos, }) prevField.Names[0].Name = bitfieldName prevField.Names[0].Obj.Name = bitfieldName } prevBitfield := &(*bitfieldList)[len(*bitfieldList)-1] prevBitfield.endBit = bitfieldOffset *bitfieldList = append(*bitfieldList, bitfieldInfo{ field: prevField, name: name, startBit: bitfieldOffset, pos: pos, }) return C.CXChildVisit_Continue } *inBitfield = false field.Names = []*ast.Ident{ { NamePos: pos, Name: name, Obj: &ast.Object{ Kind: ast.Var, Name: name, Decl: field, }, }, } fieldList.List = append(fieldList.List, field) return C.CXChildVisit_Continue } //export tinygo_clang_inclusion_visitor func tinygo_clang_inclusion_visitor(includedFile C.CXFile, inclusionStack *C.CXSourceLocation, includeLen C.unsigned, clientData C.CXClientData) { callback := storedRefs.Get(unsafe.Pointer(clientData)).(func(C.CXFile)) callback(includedFile) } ================================================ FILE: cgo/libclang_config_llvm15.go ================================================ //go:build !byollvm && llvm15 package cgo /* #cgo linux CFLAGS: -I/usr/lib/llvm-15/include #cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@15/include #cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@15/include #cgo freebsd CFLAGS: -I/usr/local/llvm15/include #cgo linux LDFLAGS: -L/usr/lib/llvm-15/lib -lclang #cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@15/lib -lclang #cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@15/lib -lclang #cgo freebsd LDFLAGS: -L/usr/local/llvm15/lib -lclang */ import "C" ================================================ FILE: cgo/libclang_config_llvm16.go ================================================ //go:build !byollvm && llvm16 package cgo // As of 2023-05-05, there is a packaging issue with LLVM 16 on Debian: // https://github.com/llvm/llvm-project/issues/62199 // A workaround is to fix this locally, using something like this: // // ln -sf ../../x86_64-linux-gnu/libclang-16.so.1 /usr/lib/llvm-16/lib/libclang.so /* #cgo linux CFLAGS: -I/usr/lib/llvm-16/include #cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@16/include #cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@16/include #cgo freebsd CFLAGS: -I/usr/local/llvm16/include #cgo linux LDFLAGS: -L/usr/lib/llvm-16/lib -lclang #cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@16/lib -lclang #cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@16/lib -lclang #cgo freebsd LDFLAGS: -L/usr/local/llvm16/lib -lclang */ import "C" ================================================ FILE: cgo/libclang_config_llvm17.go ================================================ //go:build !byollvm && llvm17 package cgo /* #cgo linux CFLAGS: -I/usr/include/llvm-17 -I/usr/include/llvm-c-17 -I/usr/lib/llvm-17/include #cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@17/include #cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@17/include #cgo freebsd CFLAGS: -I/usr/local/llvm17/include #cgo linux LDFLAGS: -L/usr/lib/llvm-17/lib -lclang #cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@17/lib -lclang #cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@17/lib -lclang #cgo freebsd LDFLAGS: -L/usr/local/llvm17/lib -lclang */ import "C" ================================================ FILE: cgo/libclang_config_llvm18.go ================================================ //go:build !byollvm && llvm18 package cgo /* #cgo linux CFLAGS: -I/usr/include/llvm-18 -I/usr/include/llvm-c-18 -I/usr/lib/llvm-18/include #cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@18/include #cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@18/include #cgo freebsd CFLAGS: -I/usr/local/llvm18/include #cgo linux LDFLAGS: -L/usr/lib/llvm-18/lib -lclang #cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@18/lib -lclang #cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@18/lib -lclang #cgo freebsd LDFLAGS: -L/usr/local/llvm18/lib -lclang */ import "C" ================================================ FILE: cgo/libclang_config_llvm19.go ================================================ //go:build !byollvm && llvm19 package cgo /* #cgo linux CFLAGS: -I/usr/include/llvm-19 -I/usr/include/llvm-c-19 -I/usr/lib/llvm-19/include #cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@19/include #cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@19/include #cgo freebsd CFLAGS: -I/usr/local/llvm19/include #cgo linux LDFLAGS: -L/usr/lib/llvm-19/lib -lclang #cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@19/lib -lclang #cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@19/lib -lclang #cgo freebsd LDFLAGS: -L/usr/local/llvm19/lib -lclang */ import "C" ================================================ FILE: cgo/libclang_config_llvm20.go ================================================ //go:build !byollvm && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19 package cgo /* #cgo linux CFLAGS: -I/usr/include/llvm-20 -I/usr/include/llvm-c-20 -I/usr/lib/llvm-20/include #cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@20/include #cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@20/include #cgo freebsd CFLAGS: -I/usr/local/llvm20/include #cgo linux LDFLAGS: -L/usr/lib/llvm-20/lib -lclang #cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@20/lib -lclang #cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@20/lib -lclang #cgo freebsd LDFLAGS: -L/usr/local/llvm20/lib -lclang */ import "C" ================================================ FILE: cgo/libclang_stubs.c ================================================ // This file implements some small trampoline functions. The signatures // are slightly different from the ones defined in libclang.go, but they // should be ABI compatible. #include // If this fails, libclang headers aren't available. Please take a look here: https://tinygo.org/docs/guides/build/ CXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu) { return clang_getTranslationUnitCursor(tu); } unsigned tinygo_clang_visitChildren(CXCursor parent, CXCursorVisitor visitor, CXClientData client_data) { return clang_visitChildren(parent, visitor, client_data); } CXString tinygo_clang_getCursorSpelling(CXCursor c) { return clang_getCursorSpelling(c); } CXString tinygo_clang_getCursorPrettyPrinted(CXCursor c, CXPrintingPolicy policy) { return clang_getCursorPrettyPrinted(c, policy); } CXPrintingPolicy tinygo_clang_getCursorPrintingPolicy(CXCursor c) { return clang_getCursorPrintingPolicy(c); } enum CXCursorKind tinygo_clang_getCursorKind(CXCursor c) { return clang_getCursorKind(c); } CXType tinygo_clang_getCursorType(CXCursor c) { return clang_getCursorType(c); } CXCursor tinygo_clang_getTypeDeclaration(CXType t) { return clang_getTypeDeclaration(t); } CXType tinygo_clang_getTypedefDeclUnderlyingType(CXCursor c) { return clang_getTypedefDeclUnderlyingType(c); } CXType tinygo_clang_getCursorResultType(CXCursor c) { return clang_getCursorResultType(c); } int tinygo_clang_Cursor_getNumArguments(CXCursor c) { return clang_Cursor_getNumArguments(c); } CXCursor tinygo_clang_Cursor_getArgument(CXCursor c, unsigned i) { return clang_Cursor_getArgument(c, i); } enum CX_StorageClass tinygo_clang_Cursor_getStorageClass(CXCursor c) { return clang_Cursor_getStorageClass(c); } CXSourceLocation tinygo_clang_getCursorLocation(CXCursor c) { return clang_getCursorLocation(c); } CXSourceRange tinygo_clang_getCursorExtent(CXCursor c) { return clang_getCursorExtent(c); } CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(CXCursor c) { return clang_Cursor_getTranslationUnit(c); } long long tinygo_clang_getEnumConstantDeclValue(CXCursor c) { return clang_getEnumConstantDeclValue(c); } CXType tinygo_clang_getEnumDeclIntegerType(CXCursor c) { return clang_getEnumDeclIntegerType(c); } unsigned tinygo_clang_Cursor_isAnonymous(CXCursor c) { return clang_Cursor_isAnonymous(c); } unsigned tinygo_clang_Cursor_isBitField(CXCursor c) { return clang_Cursor_isBitField(c); } unsigned tinygo_clang_Cursor_isMacroFunctionLike(CXCursor c) { return clang_Cursor_isMacroFunctionLike(c); } ================================================ FILE: cgo/security.go ================================================ // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // This file has been copied from the Go 1.13 release tree. // Checking of compiler and linker flags. // We must avoid flags like -fplugin=, which can allow // arbitrary code execution during the build. // Do not make changes here without carefully // considering the implications. // (That's why the code is isolated in a file named security.go.) // // Note that -Wl,foo means split foo on commas and pass to // the linker, so that -Wl,-foo,bar means pass -foo bar to // the linker. Similarly -Wa,foo for the assembler and so on. // If any of these are permitted, the wildcard portion must // disallow commas. // // Note also that GNU binutils accept any argument @foo // as meaning "read more flags from the file foo", so we must // guard against any command-line argument beginning with @, // even things like "-I @foo". // We use safeArg (which is even more conservative) // to reject these. // // Even worse, gcc -I@foo (one arg) turns into cc1 -I @foo (two args), // so although gcc doesn't expand the @foo, cc1 will. // So out of paranoia, we reject @ at the beginning of every // flag argument that might be split into its own argument. package cgo import ( "fmt" "os" "regexp" "strings" "unicode/utf8" ) var re = regexp.MustCompile var validCompilerFlags = []*regexp.Regexp{ re(`-D([A-Za-z_].*)`), re(`-F([^@\-].*)`), re(`-I([^@\-].*)`), re(`-O`), re(`-O([^@\-].*)`), re(`-W`), re(`-W([^@,]+)`), // -Wall but not -Wa,-foo. re(`-Wa,-mbig-obj`), re(`-Wp,-D([A-Za-z_].*)`), re(`-ansi`), re(`-f(no-)?asynchronous-unwind-tables`), re(`-f(no-)?blocks`), re(`-f(no-)builtin-[a-zA-Z0-9_]*`), re(`-f(no-)?common`), re(`-f(no-)?constant-cfstrings`), re(`-fdiagnostics-show-note-include-stack`), re(`-f(no-)?eliminate-unused-debug-types`), re(`-f(no-)?exceptions`), re(`-f(no-)?fast-math`), re(`-f(no-)?inline-functions`), re(`-finput-charset=([^@\-].*)`), re(`-f(no-)?fat-lto-objects`), re(`-f(no-)?keep-inline-dllexport`), re(`-f(no-)?lto`), re(`-fmacro-backtrace-limit=(.+)`), re(`-fmessage-length=(.+)`), re(`-f(no-)?modules`), re(`-f(no-)?objc-arc`), re(`-f(no-)?objc-nonfragile-abi`), re(`-f(no-)?objc-legacy-dispatch`), re(`-f(no-)?omit-frame-pointer`), re(`-f(no-)?openmp(-simd)?`), re(`-f(no-)?permissive`), re(`-f(no-)?(pic|PIC|pie|PIE)`), re(`-f(no-)?plt`), re(`-f(no-)?rtti`), re(`-f(no-)?split-stack`), re(`-f(no-)?stack-(.+)`), re(`-f(no-)?strict-aliasing`), re(`-f(un)signed-char`), re(`-f(no-)?use-linker-plugin`), // safe if -B is not used; we don't permit -B re(`-f(no-)?visibility-inlines-hidden`), re(`-fsanitize=(.+)`), re(`-ftemplate-depth-(.+)`), re(`-fvisibility=(.+)`), re(`-g([^@\-].*)?`), re(`-m32`), re(`-m64`), re(`-m(abi|arch|cpu|fpu|tune)=([^@\-].*)`), re(`-m(no-)?v?aes`), re(`-marm`), re(`-m(no-)?avx[0-9a-z]*`), re(`-mfloat-abi=([^@\-].*)`), re(`-mfpmath=[0-9a-z,+]*`), re(`-m(no-)?avx[0-9a-z.]*`), re(`-m(no-)?ms-bitfields`), re(`-m(no-)?stack-(.+)`), re(`-mmacosx-(.+)`), re(`-mios-simulator-version-min=(.+)`), re(`-miphoneos-version-min=(.+)`), re(`-mtvos-simulator-version-min=(.+)`), re(`-mtvos-version-min=(.+)`), re(`-mwatchos-simulator-version-min=(.+)`), re(`-mwatchos-version-min=(.+)`), re(`-mnop-fun-dllimport`), re(`-m(no-)?sse[0-9.]*`), re(`-m(no-)?ssse3`), re(`-mthumb(-interwork)?`), re(`-mthreads`), re(`-mwindows`), re(`--param=ssp-buffer-size=[0-9]*`), re(`-pedantic(-errors)?`), re(`-pipe`), re(`-pthread`), re(`-?-std=([^@\-].*)`), re(`-?-stdlib=([^@\-].*)`), re(`--sysroot=([^@\-].*)`), re(`-w`), re(`-x([^@\-].*)`), re(`-v`), } var validCompilerFlagsWithNextArg = []string{ "-arch", "-D", "-I", "-framework", "-isysroot", "-isystem", "--sysroot", "-target", "-x", } var validLinkerFlags = []*regexp.Regexp{ re(`-F([^@\-].*)`), re(`-l([^@\-].*)`), re(`-L([^@\-].*)`), re(`-O`), re(`-O([^@\-].*)`), re(`--export=([^@\-].*)`), re(`-f(no-)?(pic|PIC|pie|PIE)`), re(`-f(no-)?openmp(-simd)?`), re(`-fsanitize=([^@\-].*)`), re(`-flat_namespace`), re(`-g([^@\-].*)?`), re(`-headerpad_max_install_names`), re(`-m(abi|arch|cpu|fpu|tune)=([^@\-].*)`), re(`-mfloat-abi=([^@\-].*)`), re(`-mmacosx-(.+)`), re(`-mios-simulator-version-min=(.+)`), re(`-miphoneos-version-min=(.+)`), re(`-mthreads`), re(`-mwindows`), re(`-(pic|PIC|pie|PIE)`), re(`-pthread`), re(`-rdynamic`), re(`-shared`), re(`-?-static([-a-z0-9+]*)`), re(`-?-stdlib=([^@\-].*)`), re(`-v`), // Note that any wildcards in -Wl need to exclude comma, // since -Wl splits its argument at commas and passes // them all to the linker uninterpreted. Allowing comma // in a wildcard would allow tunnelling arbitrary additional // linker arguments through one of these. re(`-Wl,--(no-)?allow-multiple-definition`), re(`-Wl,--(no-)?allow-shlib-undefined`), re(`-Wl,--(no-)?as-needed`), re(`-Wl,-Bdynamic`), re(`-Wl,-berok`), re(`-Wl,-Bstatic`), re(`-WL,-O([^@,\-][^,]*)?`), re(`-Wl,-d[ny]`), re(`-Wl,--disable-new-dtags`), re(`-Wl,-e[=,][a-zA-Z0-9]*`), re(`-Wl,--enable-new-dtags`), re(`-Wl,--end-group`), re(`-Wl,--(no-)?export-dynamic`), re(`-Wl,-framework,[^,@\-][^,]+`), re(`-Wl,-headerpad_max_install_names`), re(`-Wl,--no-undefined`), re(`-Wl,-R([^@\-][^,@]*$)`), re(`-Wl,--just-symbols[=,]([^,@\-][^,@]+)`), re(`-Wl,-rpath(-link)?[=,]([^,@\-][^,]+)`), re(`-Wl,-s`), re(`-Wl,-search_paths_first`), re(`-Wl,-sectcreate,([^,@\-][^,]+),([^,@\-][^,]+),([^,@\-][^,]+)`), re(`-Wl,--start-group`), re(`-Wl,-?-static`), re(`-Wl,-?-subsystem,(native|windows|console|posix|xbox)`), re(`-Wl,-syslibroot[=,]([^,@\-][^,]+)`), re(`-Wl,-undefined[=,]([^,@\-][^,]+)`), re(`-Wl,-?-unresolved-symbols=[^,]+`), re(`-Wl,--(no-)?warn-([^,]+)`), re(`-Wl,-z,(no)?execstack`), re(`-Wl,-z,relro`), re(`[a-zA-Z0-9_/].*\.(a|o|obj|dll|dylib|so)`), // direct linker inputs: x.o or libfoo.so (but not -foo.o or @foo.o) re(`\./.*\.(a|o|obj|dll|dylib|so)`), } var validLinkerFlagsWithNextArg = []string{ "-arch", "-F", "-l", "-L", "-framework", "-isysroot", "--sysroot", "-target", "-Wl,-framework", "-Wl,-rpath", "-Wl,-R", "-Wl,--just-symbols", "-Wl,-undefined", } func checkCompilerFlags(name string, list []string) error { return checkFlags(name, list, validCompilerFlags, validCompilerFlagsWithNextArg) } func checkLinkerFlags(name string, list []string) error { return checkFlags(name, list, validLinkerFlags, validLinkerFlagsWithNextArg) } func checkFlags(name string, list []string, valid []*regexp.Regexp, validNext []string) error { // Let users override rules with $CGO_CFLAGS_ALLOW, $CGO_CFLAGS_DISALLOW, etc. var ( allow *regexp.Regexp disallow *regexp.Regexp ) if env := os.Getenv("CGO_" + name + "_ALLOW"); env != "" { r, err := regexp.Compile(env) if err != nil { return fmt.Errorf("parsing $CGO_%s_ALLOW: %v", name, err) } allow = r } if env := os.Getenv("CGO_" + name + "_DISALLOW"); env != "" { r, err := regexp.Compile(env) if err != nil { return fmt.Errorf("parsing $CGO_%s_DISALLOW: %v", name, err) } disallow = r } Args: for i := 0; i < len(list); i++ { arg := list[i] if disallow != nil && disallow.FindString(arg) == arg { goto Bad } if allow != nil && allow.FindString(arg) == arg { continue Args } for _, re := range valid { if re.FindString(arg) == arg { // must be complete match continue Args } } for _, x := range validNext { if arg == x { if i+1 < len(list) && safeArg(list[i+1]) { i++ continue Args } // Permit -Wl,-framework -Wl,name. if i+1 < len(list) && strings.HasPrefix(arg, "-Wl,") && strings.HasPrefix(list[i+1], "-Wl,") && safeArg(list[i+1][4:]) && !strings.Contains(list[i+1][4:], ",") { i++ continue Args } if i+1 < len(list) { return fmt.Errorf("invalid flag: %s %s (see https://golang.org/s/invalidflag)", arg, list[i+1]) } return fmt.Errorf("invalid flag: %s without argument (see https://golang.org/s/invalidflag)", arg) } } Bad: return fmt.Errorf("invalid flag: %s", arg) } return nil } func safeArg(name string) bool { if name == "" { return false } c := name[0] return '0' <= c && c <= '9' || 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || c == '.' || c == '_' || c == '/' || c >= utf8.RuneSelf } ================================================ FILE: cgo/security_test.go ================================================ // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // This file has been copied from the Go 1.13 release tree. package cgo import ( "os" "testing" ) var goodCompilerFlags = [][]string{ {"-DFOO"}, {"-Dfoo=bar"}, {"-F/Qt"}, {"-I/"}, {"-I/etc/passwd"}, {"-I."}, {"-O"}, {"-O2"}, {"-Osmall"}, {"-W"}, {"-Wall"}, {"-fobjc-arc"}, {"-fno-objc-arc"}, {"-fomit-frame-pointer"}, {"-fno-omit-frame-pointer"}, {"-fpic"}, {"-fno-pic"}, {"-fPIC"}, {"-fno-PIC"}, {"-fpie"}, {"-fno-pie"}, {"-fPIE"}, {"-fno-PIE"}, {"-fsplit-stack"}, {"-fno-split-stack"}, {"-fstack-xxx"}, {"-fno-stack-xxx"}, {"-fsanitize=hands"}, {"-g"}, {"-ggdb"}, {"-march=souza"}, {"-mcpu=123"}, {"-mfpu=123"}, {"-mtune=happybirthday"}, {"-mstack-overflow"}, {"-mno-stack-overflow"}, {"-mmacosx-version"}, {"-mnop-fun-dllimport"}, {"-pthread"}, {"-std=c99"}, {"-xc"}, {"-D", "FOO"}, {"-D", "foo=bar"}, {"-I", "."}, {"-I", "/etc/passwd"}, {"-I", "世界"}, {"-framework", "Chocolate"}, {"-x", "c"}, {"-v"}, } var badCompilerFlags = [][]string{ {"-D@X"}, {"-D-X"}, {"-F@dir"}, {"-F-dir"}, {"-I@dir"}, {"-I-dir"}, {"-O@1"}, {"-Wa,-foo"}, {"-W@foo"}, {"-g@gdb"}, {"-g-gdb"}, {"-march=@dawn"}, {"-march=-dawn"}, {"-std=@c99"}, {"-std=-c99"}, {"-x@c"}, {"-x-c"}, {"-D", "@foo"}, {"-D", "-foo"}, {"-I", "@foo"}, {"-I", "-foo"}, {"-framework", "-Caffeine"}, {"-framework", "@Home"}, {"-x", "--c"}, {"-x", "@obj"}, } func TestCheckCompilerFlags(t *testing.T) { for _, f := range goodCompilerFlags { if err := checkCompilerFlags("test", f); err != nil { t.Errorf("unexpected error for %q: %v", f, err) } } for _, f := range badCompilerFlags { if err := checkCompilerFlags("test", f); err == nil { t.Errorf("missing error for %q", f) } } } var goodLinkerFlags = [][]string{ {"-Fbar"}, {"-lbar"}, {"-Lbar"}, {"--export=my_symbol"}, {"-fpic"}, {"-fno-pic"}, {"-fPIC"}, {"-fno-PIC"}, {"-fpie"}, {"-fno-pie"}, {"-fPIE"}, {"-fno-PIE"}, {"-fsanitize=hands"}, {"-g"}, {"-ggdb"}, {"-march=souza"}, {"-mcpu=123"}, {"-mfpu=123"}, {"-mtune=happybirthday"}, {"-pic"}, {"-pthread"}, {"-Wl,-rpath,foo"}, {"-Wl,-rpath,$ORIGIN/foo"}, {"-Wl,-R", "/foo"}, {"-Wl,-R", "foo"}, {"-Wl,-R,foo"}, {"-Wl,--just-symbols=foo"}, {"-Wl,--just-symbols,foo"}, {"-Wl,--warn-error"}, {"-Wl,--no-warn-error"}, {"foo.so"}, {"_世界.dll"}, {"./x.o"}, {"libcgosotest.dylib"}, {"-F", "framework"}, {"-l", "."}, {"-l", "/etc/passwd"}, {"-l", "世界"}, {"-L", "framework"}, {"-framework", "Chocolate"}, {"-v"}, {"-Wl,-framework", "-Wl,Chocolate"}, {"-Wl,-framework,Chocolate"}, {"-Wl,-unresolved-symbols=ignore-all"}, } var badLinkerFlags = [][]string{ {"-DFOO"}, {"-Dfoo=bar"}, {"-W"}, {"-Wall"}, {"-fobjc-arc"}, {"-fno-objc-arc"}, {"-fomit-frame-pointer"}, {"-fno-omit-frame-pointer"}, {"-fsplit-stack"}, {"-fno-split-stack"}, {"-fstack-xxx"}, {"-fno-stack-xxx"}, {"-mstack-overflow"}, {"-mno-stack-overflow"}, {"-mnop-fun-dllimport"}, {"-std=c99"}, {"-xc"}, {"-D", "FOO"}, {"-D", "foo=bar"}, {"-I", "FOO"}, {"-L", "@foo"}, {"-L", "-foo"}, {"-x", "c"}, {"-D@X"}, {"-D-X"}, {"-I@dir"}, {"-I-dir"}, {"-O@1"}, {"-Wa,-foo"}, {"-W@foo"}, {"-g@gdb"}, {"-g-gdb"}, {"-march=@dawn"}, {"-march=-dawn"}, {"-std=@c99"}, {"-std=-c99"}, {"-x@c"}, {"-x-c"}, {"-D", "@foo"}, {"-D", "-foo"}, {"-I", "@foo"}, {"-I", "-foo"}, {"-l", "@foo"}, {"-l", "-foo"}, {"-framework", "-Caffeine"}, {"-framework", "@Home"}, {"-Wl,-framework,-Caffeine"}, {"-Wl,-framework", "-Wl,@Home"}, {"-Wl,-framework", "@Home"}, {"-Wl,-framework,Chocolate,@Home"}, {"-x", "--c"}, {"-x", "@obj"}, {"-Wl,-rpath,@foo"}, {"-Wl,-R,foo,bar"}, {"-Wl,-R,@foo"}, {"-Wl,--just-symbols,@foo"}, {"../x.o"}, } func TestCheckLinkerFlags(t *testing.T) { for _, f := range goodLinkerFlags { if err := checkLinkerFlags("test", f); err != nil { t.Errorf("unexpected error for %q: %v", f, err) } } for _, f := range badLinkerFlags { if err := checkLinkerFlags("test", f); err == nil { t.Errorf("missing error for %q", f) } } } func TestCheckFlagAllowDisallow(t *testing.T) { if err := checkCompilerFlags("TEST", []string{"-disallow"}); err == nil { t.Fatalf("missing error for -disallow") } os.Setenv("CGO_TEST_ALLOW", "-disallo") if err := checkCompilerFlags("TEST", []string{"-disallow"}); err == nil { t.Fatalf("missing error for -disallow with CGO_TEST_ALLOW=-disallo") } os.Setenv("CGO_TEST_ALLOW", "-disallow") if err := checkCompilerFlags("TEST", []string{"-disallow"}); err != nil { t.Fatalf("unexpected error for -disallow with CGO_TEST_ALLOW=-disallow: %v", err) } os.Unsetenv("CGO_TEST_ALLOW") if err := checkCompilerFlags("TEST", []string{"-Wall"}); err != nil { t.Fatalf("unexpected error for -Wall: %v", err) } os.Setenv("CGO_TEST_DISALLOW", "-Wall") if err := checkCompilerFlags("TEST", []string{"-Wall"}); err == nil { t.Fatalf("missing error for -Wall with CGO_TEST_DISALLOW=-Wall") } os.Setenv("CGO_TEST_ALLOW", "-Wall") // disallow wins if err := checkCompilerFlags("TEST", []string{"-Wall"}); err == nil { t.Fatalf("missing error for -Wall with CGO_TEST_DISALLOW=-Wall and CGO_TEST_ALLOW=-Wall") } os.Setenv("CGO_TEST_ALLOW", "-fplugin.*") os.Setenv("CGO_TEST_DISALLOW", "-fplugin=lint.so") if err := checkCompilerFlags("TEST", []string{"-fplugin=faster.so"}); err != nil { t.Fatalf("unexpected error for -fplugin=faster.so: %v", err) } if err := checkCompilerFlags("TEST", []string{"-fplugin=lint.so"}); err == nil { t.Fatalf("missing error for -fplugin=lint.so: %v", err) } } ================================================ FILE: cgo/sync.go ================================================ package cgo import ( "sync" "unsafe" ) // #include import "C" // refMap is a convenient way to store opaque references that can be passed to // C. It is useful if an API uses function pointers and you cannot pass a Go // pointer but only a C pointer. type refMap struct { refs map[unsafe.Pointer]interface{} lock sync.Mutex } // Put stores a value in the map. It can later be retrieved using Get. It must // be removed using Remove to avoid memory leaks. func (m *refMap) Put(v interface{}) unsafe.Pointer { m.lock.Lock() defer m.lock.Unlock() if m.refs == nil { m.refs = make(map[unsafe.Pointer]interface{}, 1) } ref := C.malloc(1) m.refs[ref] = v return ref } // Get returns a stored value previously inserted with Put. Use the same // reference as you got from Put. func (m *refMap) Get(ref unsafe.Pointer) interface{} { m.lock.Lock() defer m.lock.Unlock() return m.refs[ref] } // Remove deletes a single reference from the map. func (m *refMap) Remove(ref unsafe.Pointer) { m.lock.Lock() defer m.lock.Unlock() delete(m.refs, ref) C.free(ref) } ================================================ FILE: cgo/testdata/basic.go ================================================ package main import "C" ================================================ FILE: cgo/testdata/basic.out.go ================================================ package main import "syscall" import "unsafe" var _ unsafe.Pointer //go:linkname _Cgo_CString runtime.cgo_CString func _Cgo_CString(string) *_Cgo_char //go:linkname _Cgo_GoString runtime.cgo_GoString func _Cgo_GoString(*_Cgo_char) string //go:linkname _Cgo___GoStringN runtime.cgo_GoStringN func _Cgo___GoStringN(*_Cgo_char, uintptr) string func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { return _Cgo___GoStringN(cstr, uintptr(length)) } //go:linkname _Cgo___GoBytes runtime.cgo_GoBytes func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { return _Cgo___GoBytes(ptr, uintptr(length)) } //go:linkname _Cgo___CBytes runtime.cgo_CBytes func _Cgo___CBytes([]byte) unsafe.Pointer func _Cgo_CBytes(b []byte) unsafe.Pointer { return _Cgo___CBytes(b) } //go:linkname _Cgo___get_errno_num runtime.cgo_errno func _Cgo___get_errno_num() uintptr func _Cgo___get_errno() error { return syscall.Errno(_Cgo___get_errno_num()) } type ( _Cgo_char uint8 _Cgo_schar int8 _Cgo_uchar uint8 _Cgo_short int16 _Cgo_ushort uint16 _Cgo_int int32 _Cgo_uint uint32 _Cgo_long int32 _Cgo_ulong uint32 _Cgo_longlong int64 _Cgo_ulonglong uint64 ) ================================================ FILE: cgo/testdata/const.go ================================================ package main /* #define foo 3 #define bar foo #define unreferenced 4 #define referenced unreferenced #define fnlike() 5 #define fnlike_val fnlike() #define square(n) (n*n) #define square_val square(20) #define add(a, b) (a + b) #define add_val add(3, 5) */ import "C" const ( Foo = C.foo Bar = C.bar Baz = C.referenced fnlike = C.fnlike_val square = C.square_val add = C.add_val ) ================================================ FILE: cgo/testdata/const.out.go ================================================ package main import "syscall" import "unsafe" var _ unsafe.Pointer //go:linkname _Cgo_CString runtime.cgo_CString func _Cgo_CString(string) *_Cgo_char //go:linkname _Cgo_GoString runtime.cgo_GoString func _Cgo_GoString(*_Cgo_char) string //go:linkname _Cgo___GoStringN runtime.cgo_GoStringN func _Cgo___GoStringN(*_Cgo_char, uintptr) string func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { return _Cgo___GoStringN(cstr, uintptr(length)) } //go:linkname _Cgo___GoBytes runtime.cgo_GoBytes func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { return _Cgo___GoBytes(ptr, uintptr(length)) } //go:linkname _Cgo___CBytes runtime.cgo_CBytes func _Cgo___CBytes([]byte) unsafe.Pointer func _Cgo_CBytes(b []byte) unsafe.Pointer { return _Cgo___CBytes(b) } //go:linkname _Cgo___get_errno_num runtime.cgo_errno func _Cgo___get_errno_num() uintptr func _Cgo___get_errno() error { return syscall.Errno(_Cgo___get_errno_num()) } type ( _Cgo_char uint8 _Cgo_schar int8 _Cgo_uchar uint8 _Cgo_short int16 _Cgo_ushort uint16 _Cgo_int int32 _Cgo_uint uint32 _Cgo_long int32 _Cgo_ulong uint32 _Cgo_longlong int64 _Cgo_ulonglong uint64 ) const _Cgo_foo = 3 const _Cgo_bar = _Cgo_foo const _Cgo_unreferenced = 4 const _Cgo_referenced = _Cgo_unreferenced const _Cgo_fnlike_val = 5 const _Cgo_square_val = (20 * 20) const _Cgo_add_val = (3 + 5) ================================================ FILE: cgo/testdata/errors.go ================================================ package main /* #warning some warning typedef struct { int x; int y; } point_t; typedef someType noType; // undefined type // Some invalid noescape lines #cgo noescape #cgo noescape foo bar #cgo noescape unusedFunction #define SOME_CONST_1 5) // invalid const syntax #define SOME_CONST_2 6) // const not used (so no error) #define SOME_CONST_3 1234 // const too large for byte #define SOME_CONST_b 3 ) // const with lots of weird whitespace (to test error locations) # define SOME_CONST_startspace 3) */ // // // #define SOME_CONST_4 8) // after some empty lines // #cgo CFLAGS: -DSOME_PARAM_CONST_invalid=3/+3 // #cgo CFLAGS: -DSOME_PARAM_CONST_valid=3+4 import "C" // #warning another warning import "C" // #define add(a, b) (a+b) // #define add_toomuch add(1, 2, 3) // #define add_toolittle add(1) import "C" // Make sure that errors for the following lines won't change with future // additions to the CGo preamble. // //line errors.go:100 var ( // constant too large _ C.char = 2 << 10 // z member does not exist _ C.point_t = C.point_t{z: 3} // constant has syntax error _ = C.SOME_CONST_1 _ byte = C.SOME_CONST_3 _ = C.SOME_CONST_4 _ = C.SOME_CONST_b _ = C.SOME_CONST_startspace // constants passed by a command line parameter _ = C.SOME_PARAM_CONST_invalid _ = C.SOME_PARAM_CONST_valid _ = C.add_toomuch _ = C.add_toolittle ) ================================================ FILE: cgo/testdata/errors.out.go ================================================ // CGo errors: // testdata/errors.go:14:1: missing function name in #cgo noescape line // testdata/errors.go:15:1: multiple function names in #cgo noescape line // testdata/errors.go:4:2: warning: some warning // testdata/errors.go:11:9: error: unknown type name 'someType' // testdata/errors.go:31:5: warning: another warning // testdata/errors.go:18:23: unexpected token ), expected end of expression // testdata/errors.go:26:26: unexpected token ), expected end of expression // testdata/errors.go:21:33: unexpected token ), expected end of expression // testdata/errors.go:22:34: unexpected token ), expected end of expression // -: unexpected token INT, expected end of expression // testdata/errors.go:35:35: unexpected number of parameters: expected 2, got 3 // testdata/errors.go:36:31: unexpected number of parameters: expected 2, got 1 // testdata/errors.go:3:1: function "unusedFunction" in #cgo noescape line is not used // Type checking errors after CGo processing: // testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as _Cgo_char value in variable declaration (overflows) // testdata/errors.go:105: unknown field z in struct literal // testdata/errors.go:108: undefined: _Cgo_SOME_CONST_1 // testdata/errors.go:110: cannot use _Cgo_SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows) // testdata/errors.go:112: undefined: _Cgo_SOME_CONST_4 // testdata/errors.go:114: undefined: _Cgo_SOME_CONST_b // testdata/errors.go:116: undefined: _Cgo_SOME_CONST_startspace // testdata/errors.go:119: undefined: _Cgo_SOME_PARAM_CONST_invalid // testdata/errors.go:122: undefined: _Cgo_add_toomuch // testdata/errors.go:123: undefined: _Cgo_add_toolittle package main import "syscall" import "unsafe" var _ unsafe.Pointer //go:linkname _Cgo_CString runtime.cgo_CString func _Cgo_CString(string) *_Cgo_char //go:linkname _Cgo_GoString runtime.cgo_GoString func _Cgo_GoString(*_Cgo_char) string //go:linkname _Cgo___GoStringN runtime.cgo_GoStringN func _Cgo___GoStringN(*_Cgo_char, uintptr) string func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { return _Cgo___GoStringN(cstr, uintptr(length)) } //go:linkname _Cgo___GoBytes runtime.cgo_GoBytes func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { return _Cgo___GoBytes(ptr, uintptr(length)) } //go:linkname _Cgo___CBytes runtime.cgo_CBytes func _Cgo___CBytes([]byte) unsafe.Pointer func _Cgo_CBytes(b []byte) unsafe.Pointer { return _Cgo___CBytes(b) } //go:linkname _Cgo___get_errno_num runtime.cgo_errno func _Cgo___get_errno_num() uintptr func _Cgo___get_errno() error { return syscall.Errno(_Cgo___get_errno_num()) } type ( _Cgo_char uint8 _Cgo_schar int8 _Cgo_uchar uint8 _Cgo_short int16 _Cgo_ushort uint16 _Cgo_int int32 _Cgo_uint uint32 _Cgo_long int32 _Cgo_ulong uint32 _Cgo_longlong int64 _Cgo_ulonglong uint64 ) type _Cgo_struct_point_t struct { x _Cgo_int y _Cgo_int } type _Cgo_point_t = _Cgo_struct_point_t const _Cgo_SOME_CONST_3 = 1234 const _Cgo_SOME_PARAM_CONST_valid = 3 + 4 ================================================ FILE: cgo/testdata/flags.go ================================================ package main /* // this name doesn't exist #cgo NOFLAGS: -foo // unknown flag #cgo CFLAGS: -fdoes-not-exist -DNOTDEFINED #cgo CFLAGS: -DFOO #cgo CFLAGS: -Iinclude #include "foo.h" #if defined(FOO) #define BAR 3 #else #define BAR 5 #endif #if defined(NOTDEFINED) #warning flag must not be defined #endif // Check Compiler flags #cgo LDFLAGS: -lc // This flag is not valid ldflags #cgo LDFLAGS: -does-not-exists */ import "C" var ( _ = C.BAR _ = C.FOO_H ) ================================================ FILE: cgo/testdata/flags.out.go ================================================ // CGo errors: // testdata/flags.go:5:7: invalid #cgo line: NOFLAGS // testdata/flags.go:8:13: invalid flag: -fdoes-not-exist // testdata/flags.go:29:14: invalid flag: -does-not-exists package main import "syscall" import "unsafe" var _ unsafe.Pointer //go:linkname _Cgo_CString runtime.cgo_CString func _Cgo_CString(string) *_Cgo_char //go:linkname _Cgo_GoString runtime.cgo_GoString func _Cgo_GoString(*_Cgo_char) string //go:linkname _Cgo___GoStringN runtime.cgo_GoStringN func _Cgo___GoStringN(*_Cgo_char, uintptr) string func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { return _Cgo___GoStringN(cstr, uintptr(length)) } //go:linkname _Cgo___GoBytes runtime.cgo_GoBytes func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { return _Cgo___GoBytes(ptr, uintptr(length)) } //go:linkname _Cgo___CBytes runtime.cgo_CBytes func _Cgo___CBytes([]byte) unsafe.Pointer func _Cgo_CBytes(b []byte) unsafe.Pointer { return _Cgo___CBytes(b) } //go:linkname _Cgo___get_errno_num runtime.cgo_errno func _Cgo___get_errno_num() uintptr func _Cgo___get_errno() error { return syscall.Errno(_Cgo___get_errno_num()) } type ( _Cgo_char uint8 _Cgo_schar int8 _Cgo_uchar uint8 _Cgo_short int16 _Cgo_ushort uint16 _Cgo_int int32 _Cgo_uint uint32 _Cgo_long int32 _Cgo_ulong uint32 _Cgo_longlong int64 _Cgo_ulonglong uint64 ) const _Cgo_BAR = 3 const _Cgo_FOO_H = 1 ================================================ FILE: cgo/testdata/include/foo.h ================================================ #define FOO_H 1 ================================================ FILE: cgo/testdata/symbols.go ================================================ package main /* // Function signatures. int foo(int a, int b); void variadic0(); void variadic2(int x, int y, ...); static void staticfunc(int x); // Global variable signatures. extern int someValue; void notEscapingFunction(int *a); #cgo noescape notEscapingFunction */ import "C" // Test function signatures. func accessFunctions() { C.foo(3, 4) C.variadic0() C.variadic2(3, 5) C.staticfunc(3) C.notEscapingFunction(nil) } func accessGlobals() { _ = C.someValue } ================================================ FILE: cgo/testdata/symbols.out.go ================================================ package main import "syscall" import "unsafe" var _ unsafe.Pointer //go:linkname _Cgo_CString runtime.cgo_CString func _Cgo_CString(string) *_Cgo_char //go:linkname _Cgo_GoString runtime.cgo_GoString func _Cgo_GoString(*_Cgo_char) string //go:linkname _Cgo___GoStringN runtime.cgo_GoStringN func _Cgo___GoStringN(*_Cgo_char, uintptr) string func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { return _Cgo___GoStringN(cstr, uintptr(length)) } //go:linkname _Cgo___GoBytes runtime.cgo_GoBytes func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { return _Cgo___GoBytes(ptr, uintptr(length)) } //go:linkname _Cgo___CBytes runtime.cgo_CBytes func _Cgo___CBytes([]byte) unsafe.Pointer func _Cgo_CBytes(b []byte) unsafe.Pointer { return _Cgo___CBytes(b) } //go:linkname _Cgo___get_errno_num runtime.cgo_errno func _Cgo___get_errno_num() uintptr func _Cgo___get_errno() error { return syscall.Errno(_Cgo___get_errno_num()) } type ( _Cgo_char uint8 _Cgo_schar int8 _Cgo_uchar uint8 _Cgo_short int16 _Cgo_ushort uint16 _Cgo_int int32 _Cgo_uint uint32 _Cgo_long int32 _Cgo_ulong uint32 _Cgo_longlong int64 _Cgo_ulonglong uint64 ) //export foo func _Cgo_foo(a _Cgo_int, b _Cgo_int) _Cgo_int var _Cgo_foo$funcaddr unsafe.Pointer //export variadic0 //go:variadic func _Cgo_variadic0() var _Cgo_variadic0$funcaddr unsafe.Pointer //export variadic2 //go:variadic func _Cgo_variadic2(x _Cgo_int, y _Cgo_int) var _Cgo_variadic2$funcaddr unsafe.Pointer //export _Cgo_static_173c95a79b6df1980521_staticfunc func _Cgo_staticfunc!symbols.go(x _Cgo_int) var _Cgo_staticfunc!symbols.go$funcaddr unsafe.Pointer //export notEscapingFunction //go:noescape func _Cgo_notEscapingFunction(a *_Cgo_int) var _Cgo_notEscapingFunction$funcaddr unsafe.Pointer //go:extern someValue var _Cgo_someValue _Cgo_int ================================================ FILE: cgo/testdata/types.go ================================================ package main /* // Simple typedef. typedef int myint; // Structs, with or without name. typedef struct { int x; int y; } point2d_t; typedef struct point3d { int x; int y; int z; } point3d_t; // Structs with reserved field names. struct type1 { // All these fields should be renamed. int type; int _type; int __type; }; struct type2 { // This field should not be renamed. int _type; }; // Unions. typedef union { // Union should be treated as a struct. int i; } union1_t; typedef union { // Union must contain a single field and have special getters/setters. int i; double d; short s; } union3_t; typedef union union2d { int i; double d[2]; } union2d_t; typedef union { unsigned char arr[10]; } unionarray_t; // Nested structs and unions. typedef struct { point2d_t begin; point2d_t end; int tag; union { point2d_t area; point3d_t solid; } coord; } struct_nested_t; typedef union { point3d_t point; unionarray_t array; union3_t thing; } union_nested_t; // Enums. These define constant numbers. All these constants must be given the // correct number. typedef enum option { optionA, optionB, optionC = -5, optionD, optionE = 10, optionF, optionG, } option_t; enum unused { unused1 = 5, }; // Anonymous enum. typedef enum { option2A = 20, } option2_t; // Various types that are usually translated directly to Go types, but storing // them in a struct reveals them. typedef struct { float f; double d; int *ptr; } types_t; // Arrays. typedef int myIntArray[10]; // Bitfields. typedef struct { unsigned char start; unsigned char a : 5; unsigned char b : 1; unsigned char c : 2; unsigned char :0; // new field unsigned char d : 6; unsigned char e : 3; // Note that C++ allows bitfields bigger than the underlying type. } bitfield_t; */ import "C" // // Test that we can refer from this CGo fragment to the fragment above. // typedef myint myint2; import "C" var ( // aliases _ C.float _ C.double // Simple typedefs. _ C.myint // Structs. _ C.point2d_t _ C.point3d_t _ C.struct_point3d // Structs with reserved field names. _ C.struct_type1 _ C.struct_type2 // Unions. _ C.union1_t _ C.union3_t _ C.union2d_t _ C.unionarray_t // Nested structs and unions. _ C.struct_nested_t _ C.union_nested_t // Enums (anonymous and named). _ C.option_t _ C.enum_option _ C.option2_t // Various types. _ C.types_t // Arrays. _ C.myIntArray ) // Test bitfield accesses. func accessBitfields() { var x C.bitfield_t x.start = 3 x.set_bitfield_a(4) x.set_bitfield_b(1) x.set_bitfield_c(2) x.d = 10 x.e = 5 var _ C.uchar = x.bitfield_a() } // Test union accesses. func accessUnion() { var union1 C.union1_t union1.i = 5 var union2d C.union2d_t var _ *C.int = union2d.unionfield_i() var _ *[2]float64 = union2d.unionfield_d() } ================================================ FILE: cgo/testdata/types.out.go ================================================ package main import "syscall" import "unsafe" var _ unsafe.Pointer //go:linkname _Cgo_CString runtime.cgo_CString func _Cgo_CString(string) *_Cgo_char //go:linkname _Cgo_GoString runtime.cgo_GoString func _Cgo_GoString(*_Cgo_char) string //go:linkname _Cgo___GoStringN runtime.cgo_GoStringN func _Cgo___GoStringN(*_Cgo_char, uintptr) string func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { return _Cgo___GoStringN(cstr, uintptr(length)) } //go:linkname _Cgo___GoBytes runtime.cgo_GoBytes func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { return _Cgo___GoBytes(ptr, uintptr(length)) } //go:linkname _Cgo___CBytes runtime.cgo_CBytes func _Cgo___CBytes([]byte) unsafe.Pointer func _Cgo_CBytes(b []byte) unsafe.Pointer { return _Cgo___CBytes(b) } //go:linkname _Cgo___get_errno_num runtime.cgo_errno func _Cgo___get_errno_num() uintptr func _Cgo___get_errno() error { return syscall.Errno(_Cgo___get_errno_num()) } type ( _Cgo_char uint8 _Cgo_schar int8 _Cgo_uchar uint8 _Cgo_short int16 _Cgo_ushort uint16 _Cgo_int int32 _Cgo_uint uint32 _Cgo_long int32 _Cgo_ulong uint32 _Cgo_longlong int64 _Cgo_ulonglong uint64 ) type _Cgo_myint = _Cgo_int type _Cgo_struct_point2d_t struct { x _Cgo_int y _Cgo_int } type _Cgo_point2d_t = _Cgo_struct_point2d_t type _Cgo_struct_point3d struct { x _Cgo_int y _Cgo_int z _Cgo_int } type _Cgo_point3d_t = _Cgo_struct_point3d type _Cgo_struct_type1 struct { _type _Cgo_int __type _Cgo_int ___type _Cgo_int } type _Cgo_struct_type2 struct{ _type _Cgo_int } type _Cgo_union_union1_t struct{ i _Cgo_int } type _Cgo_union1_t = _Cgo_union_union1_t type _Cgo_union_union3_t struct{ $union uint64 } func (union *_Cgo_union_union3_t) unionfield_i() *_Cgo_int { return (*_Cgo_int)(unsafe.Pointer(&union.$union)) } func (union *_Cgo_union_union3_t) unionfield_d() *float64 { return (*float64)(unsafe.Pointer(&union.$union)) } func (union *_Cgo_union_union3_t) unionfield_s() *_Cgo_short { return (*_Cgo_short)(unsafe.Pointer(&union.$union)) } type _Cgo_union3_t = _Cgo_union_union3_t type _Cgo_union_union2d struct{ $union [2]uint64 } func (union *_Cgo_union_union2d) unionfield_i() *_Cgo_int { return (*_Cgo_int)(unsafe.Pointer(&union.$union)) } func (union *_Cgo_union_union2d) unionfield_d() *[2]float64 { return (*[2]float64)(unsafe.Pointer(&union.$union)) } type _Cgo_union2d_t = _Cgo_union_union2d type _Cgo_union_unionarray_t struct{ arr [10]_Cgo_uchar } type _Cgo_unionarray_t = _Cgo_union_unionarray_t type _Cgo__Ctype_union___0 struct{ $union [3]uint32 } func (union *_Cgo__Ctype_union___0) unionfield_area() *_Cgo_point2d_t { return (*_Cgo_point2d_t)(unsafe.Pointer(&union.$union)) } func (union *_Cgo__Ctype_union___0) unionfield_solid() *_Cgo_point3d_t { return (*_Cgo_point3d_t)(unsafe.Pointer(&union.$union)) } type _Cgo_struct_struct_nested_t struct { begin _Cgo_point2d_t end _Cgo_point2d_t tag _Cgo_int coord _Cgo__Ctype_union___0 } type _Cgo_struct_nested_t = _Cgo_struct_struct_nested_t type _Cgo_union_union_nested_t struct{ $union [2]uint64 } func (union *_Cgo_union_union_nested_t) unionfield_point() *_Cgo_point3d_t { return (*_Cgo_point3d_t)(unsafe.Pointer(&union.$union)) } func (union *_Cgo_union_union_nested_t) unionfield_array() *_Cgo_unionarray_t { return (*_Cgo_unionarray_t)(unsafe.Pointer(&union.$union)) } func (union *_Cgo_union_union_nested_t) unionfield_thing() *_Cgo_union3_t { return (*_Cgo_union3_t)(unsafe.Pointer(&union.$union)) } type _Cgo_union_nested_t = _Cgo_union_union_nested_t type _Cgo_enum_option = _Cgo_int type _Cgo_option_t = _Cgo_enum_option type _Cgo_enum_option2_t = _Cgo_uint type _Cgo_option2_t = _Cgo_enum_option2_t type _Cgo_struct_types_t struct { f float32 d float64 ptr *_Cgo_int } type _Cgo_types_t = _Cgo_struct_types_t type _Cgo_myIntArray = [10]_Cgo_int type _Cgo_struct_bitfield_t struct { start _Cgo_uchar __bitfield_1 _Cgo_uchar d _Cgo_uchar e _Cgo_uchar } func (s *_Cgo_struct_bitfield_t) bitfield_a() _Cgo_uchar { return s.__bitfield_1 & 0x1f } func (s *_Cgo_struct_bitfield_t) set_bitfield_a(value _Cgo_uchar) { s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0 } func (s *_Cgo_struct_bitfield_t) bitfield_b() _Cgo_uchar { return s.__bitfield_1 >> 5 & 0x1 } func (s *_Cgo_struct_bitfield_t) set_bitfield_b(value _Cgo_uchar) { s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5 } func (s *_Cgo_struct_bitfield_t) bitfield_c() _Cgo_uchar { return s.__bitfield_1 >> 6 } func (s *_Cgo_struct_bitfield_t) set_bitfield_c(value _Cgo_uchar, ) { s.__bitfield_1 = s.__bitfield_1&0x3f | value<<6 } type _Cgo_bitfield_t = _Cgo_struct_bitfield_t ================================================ FILE: colorwriter.go ================================================ package main import ( "io" ) // ANSI escape codes for terminal colors. const ( TermColorReset = "\x1b[0m" TermColorYellow = "\x1b[93m" ) // ColorWriter wraps an io.Writer but adds a prefix and a terminal color. type ColorWriter struct { Out io.Writer Color string Prefix string line []byte } // Write implements io.Writer, but with an added prefix and terminal color. func (w *ColorWriter) Write(p []byte) (n int, err error) { for _, c := range p { if c == '\n' { w.line = append(w.line, []byte(TermColorReset)...) w.line = append(w.line, '\n') // Write this line. _, err := w.Out.Write(w.line) w.line = w.line[:0] w.line = append(w.line, []byte(w.Color+w.Prefix)...) if err != nil { return 0, err } } else { w.line = append(w.line, c) } } return len(p), nil } ================================================ FILE: compileopts/config.go ================================================ // Package compileopts contains the configuration for a single to-be-built // binary. package compileopts import ( "errors" "fmt" "os" "path/filepath" "regexp" "strconv" "strings" "github.com/google/shlex" "github.com/tinygo-org/tinygo/goenv" ) // Library versions. Whenever an existing library is changed, this number should // be added/increased so that existing caches are invalidated. // // (This is a bit of a layering violation, this should really be part of the // builder.Library struct but that's hard to do since we want to know the // library path in advance in several places). var libVersions = map[string]int{ "musl": 3, "bdwgc": 2, } // Config keeps all configuration affecting the build in a single struct. type Config struct { Options *Options Target *TargetSpec GoMinorVersion int TestConfig TestConfig } // Triple returns the LLVM target triple, like armv6m-unknown-unknown-eabi. func (c *Config) Triple() string { return c.Target.Triple } // CPU returns the LLVM CPU name, like atmega328p or arm7tdmi. It may return an // empty string if the CPU name is not known. func (c *Config) CPU() string { return c.Target.CPU } // The current build mode (like the `-buildmode` command line flag). func (c *Config) BuildMode() string { if c.Options.BuildMode != "" { return c.Options.BuildMode } if c.Target.BuildMode != "" { return c.Target.BuildMode } return "default" } // Features returns a list of features this CPU supports. For example, for a // RISC-V processor, that could be "+a,+c,+m". For many targets, an empty list // will be returned. func (c *Config) Features() string { if c.Target.Features == "" { return c.Options.LLVMFeatures } if c.Options.LLVMFeatures == "" { return c.Target.Features } return c.Target.Features + "," + c.Options.LLVMFeatures } // ABI returns the -mabi= flag for this target (like -mabi=lp64). A zero-length // string is returned if the target doesn't specify an ABI. func (c *Config) ABI() string { return c.Target.ABI } // GOOS returns the GOOS of the target. This might not always be the actual OS: // for example, bare-metal targets will usually pretend to be linux to get the // standard library to compile. func (c *Config) GOOS() string { return c.Target.GOOS } // GOARCH returns the GOARCH of the target. This might not always be the actual // architecture: for example, the AVR target is not supported by the Go standard // library so such targets will usually pretend to be linux/arm. func (c *Config) GOARCH() string { return c.Target.GOARCH } // GOARM will return the GOARM environment variable given to the compiler when // building a program. func (c *Config) GOARM() string { return c.Options.GOARM } // GOMIPS will return the GOMIPS environment variable given to the compiler when // building a program. func (c *Config) GOMIPS() string { return c.Options.GOMIPS } // BuildTags returns the complete list of build tags used during this build. func (c *Config) BuildTags() []string { tags := append([]string(nil), c.Target.BuildTags...) // copy slice (avoid a race) tags = append(tags, []string{ "tinygo", // that's the compiler "purego", // to get various crypto packages to work "osusergo", // to get os/user to work "math_big_pure_go", // to get math/big to work "gc." + c.GC(), "scheduler." + c.Scheduler(), // used inside the runtime package "serial." + c.Serial()}...) // used inside the machine package switch c.Scheduler() { case "threads", "cores": default: tags = append(tags, "tinygo.unicore") } for i := 1; i <= c.GoMinorVersion; i++ { tags = append(tags, fmt.Sprintf("go1.%d", i)) } tags = append(tags, c.Options.Tags...) return tags } // GC returns the garbage collection strategy in use on this platform. Valid // values are "none", "leaking", "conservative" and "precise". func (c *Config) GC() string { if c.Options.GC != "" { return c.Options.GC } if c.Target.GC != "" { return c.Target.GC } return "conservative" } // NeedsStackObjects returns true if the compiler should insert stack objects // that can be traced by the garbage collector. func (c *Config) NeedsStackObjects() bool { switch c.GC() { case "conservative", "custom", "precise", "boehm": for _, tag := range c.BuildTags() { if tag == "tinygo.wasm" { return true } } return false default: return false } } // Scheduler returns the scheduler implementation. Valid values are "none", // "asyncify" and "tasks". func (c *Config) Scheduler() string { if c.Options.Scheduler != "" { return c.Options.Scheduler } if c.Target.Scheduler != "" { return c.Target.Scheduler } // Fall back to none. return "none" } // Serial returns the serial implementation for this build configuration: uart, // usb (meaning USB-CDC), or none. func (c *Config) Serial() string { if c.Options.Serial != "" { return c.Options.Serial } if c.Target.Serial != "" { return c.Target.Serial } return "none" } // OptLevels returns the optimization level (0-2), size level (0-2), and inliner // threshold as used in the LLVM optimization pipeline. func (c *Config) OptLevel() (level string, speedLevel, sizeLevel int) { switch c.Options.Opt { case "none", "0": return "O0", 0, 0 case "1": return "O1", 1, 0 case "2": return "O2", 2, 0 case "s": return "Os", 2, 1 case "z": return "Oz", 2, 2 // default default: // This is not shown to the user: valid choices are already checked as // part of Options.Verify(). It is here as a sanity check. panic("unknown optimization level: -opt=" + c.Options.Opt) } } // PanicStrategy returns the panic strategy selected for this target. Valid // values are "print" (print the panic value, then exit) or "trap" (issue a trap // instruction). func (c *Config) PanicStrategy() string { return c.Options.PanicStrategy } // AutomaticStackSize returns whether goroutine stack sizes should be determined // automatically at compile time, if possible. If it is false, no attempt is // made. func (c *Config) AutomaticStackSize() bool { if c.Target.AutoStackSize != nil && c.Scheduler() == "tasks" { return *c.Target.AutoStackSize } return false } // StackSize returns the default stack size to be used for goroutines, if the // stack size could not be determined automatically at compile time. func (c *Config) StackSize() uint64 { if c.Options.StackSize != 0 { return c.Options.StackSize } return c.Target.DefaultStackSize } // MaxStackAlloc returns the size of the maximum allocation to put on the stack vs heap. func (c *Config) MaxStackAlloc() uint64 { if c.StackSize() >= 16*1024 { return 1024 } return 256 } // RP2040BootPatch returns whether the RP2040 boot patch should be applied that // calculates and patches in the checksum for the 2nd stage bootloader. func (c *Config) RP2040BootPatch() bool { if c.Target.RP2040BootPatch != nil { return *c.Target.RP2040BootPatch } return false } // Return a canonicalized architecture name, so we don't have to deal with arm* // vs thumb* vs arm64. func CanonicalArchName(triple string) string { arch := strings.Split(triple, "-")[0] if arch == "arm64" { return "aarch64" } if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") { return "arm" } if arch == "mipsel" { return "mips" } return arch } // MuslArchitecture returns the architecture name as used in musl libc. It is // usually the same as the first part of the LLVM triple, but not always. func MuslArchitecture(triple string) string { return CanonicalArchName(triple) } // Returns true if the libc needs to include malloc, for the libcs where this // matters. func (c *Config) LibcNeedsMalloc() bool { if c.GC() == "boehm" && c.Target.Libc == "wasi-libc" { return true } return false } // LibraryPath returns the path to the library build directory. The path will be // a library path in the cache directory (which might not yet be built). func (c *Config) LibraryPath(name string) string { archname := c.Triple() if c.CPU() != "" { archname += "-" + c.CPU() } if c.ABI() != "" { archname += "-" + c.ABI() } if c.Target.SoftFloat { archname += "-softfloat" } if name == "bdwgc" { // Boehm GC is compiled against a particular libc. archname += "-" + c.Target.Libc } // Append a version string, if this library has a version. if v, ok := libVersions[name]; ok { archname += "-v" + strconv.Itoa(v) } options := "" if c.LibcNeedsMalloc() { options += "+malloc" } // No precompiled library found. Determine the path name that will be used // in the build cache. return filepath.Join(goenv.Get("GOCACHE"), name+options+"-"+archname) } // DefaultBinaryExtension returns the default extension for binaries, such as // .exe, .wasm, or no extension (depending on the target). func (c *Config) DefaultBinaryExtension() string { parts := strings.Split(c.Triple(), "-") if parts[0] == "wasm32" { // WebAssembly files always have the .wasm file extension. return ".wasm" } if len(parts) >= 3 && parts[2] == "windows" { // Windows uses .exe. return ".exe" } if len(parts) >= 3 && parts[2] == "unknown" { // There appears to be a convention to use the .elf file extension for // ELF files intended for microcontrollers. I'm not aware of the origin // of this, it's just something that is used by many projects. // I think it's a good tradition, so let's keep it. return ".elf" } // Linux, MacOS, etc, don't use a file extension. Use it as a fallback. return "" } // CFlags returns the flags to pass to the C compiler. This is necessary for CGo // preprocessing. func (c *Config) CFlags(libclang bool) []string { var cflags []string for _, flag := range c.Target.CFlags { cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT"))) } resourceDir := goenv.ClangResourceDir(libclang) if resourceDir != "" { // The resource directory contains the built-in clang headers like // stdbool.h, stdint.h, float.h, etc. // It is left empty if we're using an external compiler (that already // knows these headers). cflags = append(cflags, "-resource-dir="+resourceDir, ) } cflags = append(cflags, c.LibcCFlags()...) // Always emit debug information. It is optionally stripped at link time. cflags = append(cflags, "-gdwarf-4") // Use the same optimization level as TinyGo. cflags = append(cflags, "-O"+c.Options.Opt) // Set the LLVM target triple. cflags = append(cflags, "--target="+c.Triple()) // Set the -mcpu (or similar) flag. if c.Target.CPU != "" { if c.GOARCH() == "amd64" || c.GOARCH() == "386" { // x86 prefers the -march flag (-mcpu is deprecated there). cflags = append(cflags, "-march="+c.Target.CPU) } else if strings.HasPrefix(c.Triple(), "avr") { // AVR MCUs use -mmcu instead of -mcpu. cflags = append(cflags, "-mmcu="+c.Target.CPU) } else { // The rest just uses -mcpu. cflags = append(cflags, "-mcpu="+c.Target.CPU) } } // Set the -mabi flag, if needed. if c.ABI() != "" { cflags = append(cflags, "-mabi="+c.ABI()) } return cflags } // LibcCFlags returns the C compiler flags for the configured libc. // It only uses flags that are part of the libc path (triple, cpu, abi, libc // name) so it can safely be used to compile another C library. func (c *Config) LibcCFlags() []string { switch c.Target.Libc { case "darwin-libSystem": root := goenv.Get("TINYGOROOT") return []string{ "-nostdlibinc", "-isystem", filepath.Join(root, "lib/macos-minimal-sdk/src/usr/include"), } case "picolibc": root := goenv.Get("TINYGOROOT") picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc") path := c.LibraryPath("picolibc") return []string{ "-nostdlibinc", "-isystem", filepath.Join(path, "include"), "-isystem", filepath.Join(picolibcDir, "include"), "-isystem", filepath.Join(picolibcDir, "tinystdio"), "-D__PICOLIBC_ERRNO_FUNCTION=__errno_location", } case "musl": root := goenv.Get("TINYGOROOT") path := c.LibraryPath("musl") arch := MuslArchitecture(c.Triple()) return []string{ "-nostdlibinc", "-isystem", filepath.Join(path, "include"), "-isystem", filepath.Join(root, "lib", "musl", "arch", arch), "-isystem", filepath.Join(root, "lib", "musl", "arch", "generic"), "-isystem", filepath.Join(root, "lib", "musl", "include"), } case "wasi-libc": path := c.LibraryPath("wasi-libc") return []string{ "-nostdlibinc", "-isystem", filepath.Join(path, "include"), } case "wasmbuiltins": // nothing to add (library is purely for builtins) return nil case "mingw-w64": root := goenv.Get("TINYGOROOT") path := c.LibraryPath("mingw-w64") cflags := []string{ "-nostdlibinc", "-isystem", filepath.Join(path, "include"), "-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "crt"), "-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "include"), "-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"), } if c.GOARCH() == "386" { cflags = append(cflags, "-D__MSVCRT_VERSION__=0x700", // Microsoft Visual C++ .NET 2002 "-D_WIN32_WINNT=0x0501", // target Windows XP ) } else { cflags = append(cflags, "-D_UCRT", "-D_WIN32_WINNT=0x0a00", // target Windows 10 ) } return cflags case "": // No libc specified, nothing to add. return nil default: // Incorrect configuration. This could be handled in a better way, but // usually this will be found by developers (not by TinyGo users). panic("unknown libc: " + c.Target.Libc) } } // LDFlags returns the flags to pass to the linker. A few more flags are needed // (like the one for the compiler runtime), but this represents the majority of // the flags. func (c *Config) LDFlags() []string { root := goenv.Get("TINYGOROOT") // Merge and adjust LDFlags. var ldflags []string for _, flag := range c.Target.LDFlags { ldflags = append(ldflags, strings.ReplaceAll(flag, "{root}", root)) } ldflags = append(ldflags, "-L", root) if c.Target.LinkerScript != "" { ldflags = append(ldflags, "-T", c.Target.LinkerScript) } ldflags = append(ldflags, c.Options.ExtLDFlags...) return ldflags } // ExtraFiles returns the list of extra files to be built and linked with the // executable. This can include extra C and assembly files. func (c *Config) ExtraFiles() []string { return c.Target.ExtraFiles } // DumpSSA returns whether to dump Go SSA while compiling (-dumpssa flag). Only // enable this for debugging. func (c *Config) DumpSSA() bool { return c.Options.DumpSSA } // VerifyIR returns whether to run extra checks on the IR. This is normally // disabled but enabled during testing. func (c *Config) VerifyIR() bool { return c.Options.VerifyIR } // Debug returns whether debug (DWARF) information should be retained by the // linker. By default, debug information is retained, but it can be removed // with the -no-debug flag. func (c *Config) Debug() bool { return c.Options.Debug } // BinaryFormat returns an appropriate binary format, based on the file // extension and the configured binary format in the target JSON file. func (c *Config) BinaryFormat(ext string) string { switch ext { case ".bin", ".gba", ".nro": // The simplest format possible: dump everything in a raw binary file. if c.Target.BinaryFormat != "" { return c.Target.BinaryFormat } return "bin" case ".img": // Image file. Only defined for the ESP32 at the moment, where it is a // full (runnable) image that can be used in the Espressif QEMU fork. if c.Target.BinaryFormat != "" { return c.Target.BinaryFormat + "-img" } return "bin" case ".hex": // Similar to bin, but includes the start address and is thus usually a // better format. return "hex" case ".uf2": // Special purpose firmware format, mainly used on Adafruit boards. // More information: // https://github.com/Microsoft/uf2 return "uf2" case ".zip": if c.Target.BinaryFormat != "" { return c.Target.BinaryFormat } return "zip" default: // Use the ELF format for unrecognized file formats. return "elf" } } // Programmer returns the flash method and OpenOCD interface name given a // particular configuration. It may either be all configured in the target JSON // file or be modified using the -programmer command-line option. func (c *Config) Programmer() (method, openocdInterface string) { switch c.Options.Programmer { case "": // No configuration supplied. return c.Target.FlashMethod, c.Target.OpenOCDInterface case "openocd", "msd", "command": // The -programmer flag only specifies the flash method. return c.Options.Programmer, c.Target.OpenOCDInterface case "bmp": // The -programmer flag only specifies the flash method. return c.Options.Programmer, "" default: // The -programmer flag specifies something else, assume it specifies // the OpenOCD interface name. return "openocd", c.Options.Programmer } } // OpenOCDConfiguration returns a list of command line arguments to OpenOCD. // This list of command-line arguments is based on the various OpenOCD-related // flags in the target specification. func (c *Config) OpenOCDConfiguration() (args []string, err error) { _, openocdInterface := c.Programmer() if openocdInterface == "" { return nil, errors.New("OpenOCD programmer not set") } if !regexp.MustCompile(`^[\p{L}0-9_-]+$`).MatchString(openocdInterface) { return nil, fmt.Errorf("OpenOCD programmer has an invalid name: %#v", openocdInterface) } if c.Target.OpenOCDTarget == "" { return nil, errors.New("OpenOCD chip not set") } if !regexp.MustCompile(`^[\p{L}0-9_-]+$`).MatchString(c.Target.OpenOCDTarget) { return nil, fmt.Errorf("OpenOCD target has an invalid name: %#v", c.Target.OpenOCDTarget) } if c.Target.OpenOCDTransport != "" && c.Target.OpenOCDTransport != "swd" { return nil, fmt.Errorf("unknown OpenOCD transport: %#v", c.Target.OpenOCDTransport) } args = []string{"-f", "interface/" + openocdInterface + ".cfg"} if c.Target.OpenOCDTransport != "" { transport := c.Target.OpenOCDTransport if transport == "swd" { switch openocdInterface { case "stlink-dap": transport = "dapdirect_swd" } } args = append(args, "-c", "transport select "+transport) } args = append(args, "-f", "target/"+c.Target.OpenOCDTarget+".cfg") for _, cmd := range c.Target.OpenOCDCommands { args = append(args, "-c", cmd) } return args, nil } // CodeModel returns the code model used on this platform. func (c *Config) CodeModel() string { if c.Target.CodeModel != "" { return c.Target.CodeModel } return "default" } // RelocationModel returns the relocation model in use on this platform. Valid // values are "static", "pic", "dynamicnopic". func (c *Config) RelocationModel() string { if c.Target.RelocationModel != "" { return c.Target.RelocationModel } return "static" } // EmulatorName is a shorthand to get the command for this emulator, something // like qemu-system-arm or simavr. func (c *Config) EmulatorName() string { parts := strings.SplitN(c.Target.Emulator, " ", 2) if len(parts) > 1 { return parts[0] } return "" } // EmulatorFormat returns the binary format for the emulator and the associated // file extension. An empty string means to pass directly whatever the linker // produces directly without conversion (usually ELF format). func (c *Config) EmulatorFormat() (format, fileExt string) { switch { case strings.Contains(c.Target.Emulator, "{img}"): return "img", ".img" default: return "", "" } } // Emulator returns a ready-to-run command to run the given binary in an // emulator. Give it the format (returned by EmulatorFormat()) and the path to // the compiled binary. func (c *Config) Emulator(format, binary string) ([]string, error) { parts, err := shlex.Split(c.Target.Emulator) if err != nil { return nil, fmt.Errorf("could not parse emulator command: %w", err) } var emulator []string for _, s := range parts { s = strings.ReplaceAll(s, "{root}", goenv.Get("TINYGOROOT")) // Allow replacement of what's usually /tmp except notably Windows. s = strings.ReplaceAll(s, "{tmpDir}", os.TempDir()) s = strings.ReplaceAll(s, "{"+format+"}", binary) emulator = append(emulator, s) } return emulator, nil } type TestConfig struct { CompileTestBinary bool CompileOnly bool Verbose bool Short bool RunRegexp string SkipRegexp string Count *int BenchRegexp string BenchTime string BenchMem bool Shuffle string } ================================================ FILE: compileopts/options.go ================================================ package compileopts import ( "fmt" "regexp" "strings" "time" ) var ( validBuildModeOptions = []string{"default", "c-shared", "wasi-legacy"} validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise", "boehm"} validSchedulerOptions = []string{"none", "tasks", "asyncify", "threads", "cores"} validSerialOptions = []string{"none", "uart", "usb", "rtt"} validPrintSizeOptions = []string{"none", "short", "full", "html"} validPanicStrategyOptions = []string{"print", "trap"} validOptOptions = []string{"none", "0", "1", "2", "s", "z"} ) // Options contains extra options to give to the compiler. These options are // usually passed from the command line, but can also be passed in environment // variables for example. type Options struct { GOOS string // environment variable GOARCH string // environment variable GOARM string // environment variable (only used with GOARCH=arm) GOMIPS string // environment variable (only used with GOARCH=mips and GOARCH=mipsle) Directory string // working dir, leave it unset to use the current working dir Target string BuildMode string // -buildmode flag Opt string GC string PanicStrategy string Scheduler string StackSize uint64 // goroutine stack size (if none could be automatically determined) Serial string Work bool // -work flag to print temporary build directory InterpTimeout time.Duration PrintIR bool DumpSSA bool VerifyIR bool SkipDWARF bool PrintCommands func(cmd string, args ...string) `json:"-"` Semaphore chan struct{} `json:"-"` // -p flag controls cap Debug bool Nobounds bool PrintSizes string PrintAllocs *regexp.Regexp // regexp string PrintStacks bool Tags []string GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value TestConfig TestConfig Programmer string OpenOCDCommands []string LLVMFeatures string Monitor bool BaudRate int Timeout time.Duration WITPackage string // pass through to wasm-tools component embed invocation WITWorld string // pass through to wasm-tools component embed -w option ExtLDFlags []string GoCompatibility bool // enable to check for Go version compatibility } // Verify performs a validation on the given options, raising an error if options are not valid. func (o *Options) Verify() error { if o.BuildMode != "" { valid := isInArray(validBuildModeOptions, o.BuildMode) if !valid { return fmt.Errorf(`invalid buildmode option '%s': valid values are %s`, o.BuildMode, strings.Join(validBuildModeOptions, ", ")) } } if o.GC != "" { valid := isInArray(validGCOptions, o.GC) if !valid { return fmt.Errorf(`invalid gc option '%s': valid values are %s`, o.GC, strings.Join(validGCOptions, ", ")) } } if o.Scheduler != "" { valid := isInArray(validSchedulerOptions, o.Scheduler) if !valid { return fmt.Errorf(`invalid scheduler option '%s': valid values are %s`, o.Scheduler, strings.Join(validSchedulerOptions, ", ")) } } if o.Serial != "" { valid := isInArray(validSerialOptions, o.Serial) if !valid { return fmt.Errorf(`invalid serial option '%s': valid values are %s`, o.Serial, strings.Join(validSerialOptions, ", ")) } } if o.PrintSizes != "" { valid := isInArray(validPrintSizeOptions, o.PrintSizes) if !valid { return fmt.Errorf(`invalid size option '%s': valid values are %s`, o.PrintSizes, strings.Join(validPrintSizeOptions, ", ")) } } if o.PanicStrategy != "" { valid := isInArray(validPanicStrategyOptions, o.PanicStrategy) if !valid { return fmt.Errorf(`invalid panic option '%s': valid values are %s`, o.PanicStrategy, strings.Join(validPanicStrategyOptions, ", ")) } } if o.Opt != "" { if !isInArray(validOptOptions, o.Opt) { return fmt.Errorf("invalid -opt=%s: valid values are %s", o.Opt, strings.Join(validOptOptions, ", ")) } } return nil } func isInArray(arr []string, item string) bool { for _, i := range arr { if i == item { return true } } return false } ================================================ FILE: compileopts/options_test.go ================================================ package compileopts_test import ( "errors" "testing" "github.com/tinygo-org/tinygo/compileopts" ) func TestVerifyOptions(t *testing.T) { expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, conservative, custom, precise, boehm`) expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, asyncify, threads, cores`) expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full, html`) expectedPanicStrategyError := errors.New(`invalid panic option 'incorrect': valid values are print, trap`) testCases := []struct { name string opts compileopts.Options expectedError error }{ { name: "OptionsEmpty", opts: compileopts.Options{}, }, { name: "InvalidGCOption", opts: compileopts.Options{ GC: "incorrect", }, expectedError: expectedGCError, }, { name: "GCOptionNone", opts: compileopts.Options{ GC: "none", }, }, { name: "GCOptionLeaking", opts: compileopts.Options{ GC: "leaking", }, }, { name: "GCOptionConservative", opts: compileopts.Options{ GC: "conservative", }, }, { name: "GCOptionCustom", opts: compileopts.Options{ GC: "custom", }, }, { name: "InvalidSchedulerOption", opts: compileopts.Options{ Scheduler: "incorrect", }, expectedError: expectedSchedulerError, }, { name: "SchedulerOptionNone", opts: compileopts.Options{ Scheduler: "none", }, }, { name: "SchedulerOptionTasks", opts: compileopts.Options{ Scheduler: "tasks", }, }, { name: "InvalidPrintSizeOption", opts: compileopts.Options{ PrintSizes: "incorrect", }, expectedError: expectedPrintSizeError, }, { name: "PrintSizeOptionNone", opts: compileopts.Options{ PrintSizes: "none", }, }, { name: "PrintSizeOptionShort", opts: compileopts.Options{ PrintSizes: "short", }, }, { name: "PrintSizeOptionFull", opts: compileopts.Options{ PrintSizes: "full", }, }, { name: "InvalidPanicOption", opts: compileopts.Options{ PanicStrategy: "incorrect", }, expectedError: expectedPanicStrategyError, }, { name: "PanicOptionPrint", opts: compileopts.Options{ PanicStrategy: "print", }, }, { name: "PanicOptionTrap", opts: compileopts.Options{ PanicStrategy: "trap", }, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { err := tc.opts.Verify() if tc.expectedError != err { if tc.expectedError.Error() != err.Error() { t.Errorf("expected %v, got %v", tc.expectedError, err) } } }) } } ================================================ FILE: compileopts/target.go ================================================ package compileopts // This file loads a target specification from a JSON file. import ( "encoding/json" "errors" "fmt" "io" "os" "os/exec" "path/filepath" "reflect" "runtime" "strings" "github.com/tinygo-org/tinygo/goenv" ) // Target specification for a given target. Used for bare metal targets. // // The target specification is mostly inspired by Rust: // https://doc.rust-lang.org/nightly/nightly-rustc/rustc_target/spec/struct.TargetOptions.html // https://github.com/shepmaster/rust-arduino-blink-led-no-core-with-cargo/blob/master/blink/arduino.json type TargetSpec struct { Inherits []string `json:"inherits,omitempty"` Triple string `json:"llvm-target,omitempty"` CPU string `json:"cpu,omitempty"` ABI string `json:"target-abi,omitempty"` // roughly equivalent to -mabi= flag Features string `json:"features,omitempty"` GOOS string `json:"goos,omitempty"` GOARCH string `json:"goarch,omitempty"` SoftFloat bool // used for non-baremetal systems (GOMIPS=softfloat etc) BuildTags []string `json:"build-tags,omitempty"` BuildMode string `json:"buildmode,omitempty"` // default build mode (if nothing specified) GC string `json:"gc,omitempty"` Scheduler string `json:"scheduler,omitempty"` Serial string `json:"serial,omitempty"` // which serial output to use (uart, usb, none) Linker string `json:"linker,omitempty"` RTLib string `json:"rtlib,omitempty"` // compiler runtime library (libgcc, compiler-rt) Libc string `json:"libc,omitempty"` AutoStackSize *bool `json:"automatic-stack-size,omitempty"` // Determine stack size automatically at compile time. DefaultStackSize uint64 `json:"default-stack-size,omitempty"` // Default stack size if the size couldn't be determined at compile time. CFlags []string `json:"cflags,omitempty"` LDFlags []string `json:"ldflags,omitempty"` LinkerScript string `json:"linkerscript,omitempty"` ExtraFiles []string `json:"extra-files,omitempty"` RP2040BootPatch *bool `json:"rp2040-boot-patch,omitempty"` // Patch RP2040 2nd stage bootloader checksum BootPatches []string `json:"boot-patches,omitempty"` // Bootloader patches to be applied in the order they appear. Emulator string `json:"emulator,omitempty"` FlashCommand string `json:"flash-command,omitempty"` GDB []string `json:"gdb,omitempty"` PortReset string `json:"flash-1200-bps-reset,omitempty"` SerialPort []string `json:"serial-port,omitempty"` // serial port IDs in the form "vid:pid" FlashMethod string `json:"flash-method,omitempty"` FlashVolume []string `json:"msd-volume-name,omitempty"` FlashFilename string `json:"msd-firmware-name,omitempty"` UF2FamilyID string `json:"uf2-family-id,omitempty"` BinaryFormat string `json:"binary-format,omitempty"` OpenOCDInterface string `json:"openocd-interface,omitempty"` OpenOCDTarget string `json:"openocd-target,omitempty"` OpenOCDTransport string `json:"openocd-transport,omitempty"` OpenOCDCommands []string `json:"openocd-commands,omitempty"` OpenOCDVerify *bool `json:"openocd-verify,omitempty"` // enable verify when flashing with openocd JLinkDevice string `json:"jlink-device,omitempty"` CodeModel string `json:"code-model,omitempty"` RelocationModel string `json:"relocation-model,omitempty"` WITPackage string `json:"wit-package,omitempty"` WITWorld string `json:"wit-world,omitempty"` } // overrideProperties overrides all properties that are set in child into itself using reflection. func (spec *TargetSpec) overrideProperties(child *TargetSpec) error { specType := reflect.TypeOf(spec).Elem() specValue := reflect.ValueOf(spec).Elem() childValue := reflect.ValueOf(child).Elem() for i := 0; i < specType.NumField(); i++ { field := specType.Field(i) src := childValue.Field(i) dst := specValue.Field(i) switch kind := field.Type.Kind(); kind { case reflect.String: // for strings, just copy the field of child to spec if not empty if src.Len() > 0 { dst.Set(src) } case reflect.Uint, reflect.Uint32, reflect.Uint64: // for Uint, copy if not zero if src.Uint() != 0 { dst.Set(src) } case reflect.Bool: if src.Bool() { dst.Set(src) } case reflect.Ptr: // for pointers, copy if not nil if !src.IsNil() { dst.Set(src) } case reflect.Slice: // for slices, append the field and check for duplicates dst.Set(reflect.AppendSlice(dst, src)) for i := 0; i < dst.Len(); i++ { v := dst.Index(i).String() for j := i + 1; j < dst.Len(); j++ { w := dst.Index(j).String() if v == w { return fmt.Errorf("duplicate value '%s' in field %s", v, field.Name) } } } default: return fmt.Errorf("unknown field type: %s", kind) } } return nil } // load reads a target specification from the JSON in the given io.Reader. It // may load more targets specified using the "inherits" property. func (spec *TargetSpec) load(r io.Reader) error { err := json.NewDecoder(r).Decode(spec) if err != nil { return err } return nil } // loadFromGivenStr loads the TargetSpec from the given string that could be: // - targets/ directory inside the compiler sources // - a relative or absolute path to custom (project specific) target specification .json file; // the Inherits[] could contain the files from target folder (ex. stm32f4disco) // as well as path to custom files (ex. myAwesomeProject.json) func (spec *TargetSpec) loadFromGivenStr(str string) error { path := "" if strings.HasSuffix(str, ".json") { path, _ = filepath.Abs(str) } else { path = filepath.Join(goenv.Get("TINYGOROOT"), "targets", strings.ToLower(str)+".json") } fp, err := os.Open(path) if err != nil { return err } defer fp.Close() return spec.load(fp) } // resolveInherits loads inherited targets, recursively. func (spec *TargetSpec) resolveInherits() error { // First create a new spec with all the inherited properties. newSpec := &TargetSpec{} for _, name := range spec.Inherits { subtarget := &TargetSpec{} err := subtarget.loadFromGivenStr(name) if err != nil { return err } err = subtarget.resolveInherits() if err != nil { return err } err = newSpec.overrideProperties(subtarget) if err != nil { return err } } // When all properties are loaded, make sure they are properly inherited. err := newSpec.overrideProperties(spec) if err != nil { return err } *spec = *newSpec return nil } // Load a target specification. func LoadTarget(options *Options) (*TargetSpec, error) { if options.Target == "" && options.GOARCH == "wasm" { // Set a specific target if we're building from a known GOOS/GOARCH // combination that is defined in a target JSON file. switch options.GOOS { case "js": options.Target = "wasm" case "wasip1": options.Target = "wasip1" case "wasip2": options.Target = "wasip2" default: return nil, errors.New("GOARCH=wasm but GOOS is not set correctly. Please set GOOS to js, wasip1, or wasip2.") } } if options.Target == "" { return defaultTarget(options) } // See whether there is a target specification for this target (e.g. // Arduino). spec := &TargetSpec{} err := spec.loadFromGivenStr(options.Target) if err != nil { return nil, err } // Successfully loaded this target from a built-in .json file. Make sure // it includes all parents as specified in the "inherits" key. err = spec.resolveInherits() if err != nil { return nil, fmt.Errorf("%s : %w", options.Target, err) } if spec.Scheduler == "asyncify" { spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_asyncify_wasm.S") } return spec, nil } // GetTargetSpecs retrieves target specifications from the TINYGOROOT targets // directory. Only valid target JSON files are considered, and the function // returns a map of target names to their respective TargetSpec. func GetTargetSpecs() (map[string]*TargetSpec, error) { dir := filepath.Join(goenv.Get("TINYGOROOT"), "targets") entries, err := os.ReadDir(dir) if err != nil { return nil, fmt.Errorf("could not list targets: %w", err) } maps := map[string]*TargetSpec{} for _, entry := range entries { entryInfo, err := entry.Info() if err != nil { return nil, fmt.Errorf("could not get entry info: %w", err) } if !entryInfo.Mode().IsRegular() || !strings.HasSuffix(entry.Name(), ".json") { // Only inspect JSON files. continue } path := filepath.Join(dir, entry.Name()) spec, err := LoadTarget(&Options{Target: path}) if err != nil { return nil, fmt.Errorf("could not list target: %w", err) } if spec.FlashMethod == "" && spec.FlashCommand == "" && spec.Emulator == "" { // This doesn't look like a regular target file, but rather like // a parent target (such as targets/cortex-m.json). continue } name := entry.Name() name = name[:len(name)-5] maps[name] = spec } return maps, nil } // Load a target from environment variables (which default to // runtime.GOOS/runtime.GOARCH). func defaultTarget(options *Options) (*TargetSpec, error) { spec := TargetSpec{ GOOS: options.GOOS, GOARCH: options.GOARCH, BuildTags: []string{options.GOOS, options.GOARCH}, Linker: "cc", DefaultStackSize: 1024 * 64, // 64kB GDB: []string{"gdb"}, PortReset: "false", } // Configure target based on GOARCH. var llvmarch string switch options.GOARCH { case "386": llvmarch = "i386" spec.CPU = "pentium4" spec.Features = "+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" case "amd64": llvmarch = "x86_64" spec.CPU = "x86-64" spec.Features = "+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" case "arm": spec.CPU = "generic" spec.CFlags = append(spec.CFlags, "-fno-unwind-tables", "-fno-asynchronous-unwind-tables") subarch := strings.Split(options.GOARM, ",") if len(subarch) > 2 { return nil, fmt.Errorf("invalid GOARM=%s, must be of form ,[hardfloat|softfloat]", options.GOARM) } archLevel := subarch[0] var fpu string if len(subarch) >= 2 { fpu = subarch[1] } else { // Pick the default fpu value: softfloat for armv5 and hardfloat // above that. if archLevel == "5" { fpu = "softfloat" } else { fpu = "hardfloat" } } switch fpu { case "softfloat": spec.CFlags = append(spec.CFlags, "-msoft-float") spec.SoftFloat = true case "hardfloat": // Hardware floating point support is the default everywhere except // on ARMv5 where it needs to be enabled explicitly. if archLevel == "5" { spec.CFlags = append(spec.CFlags, "-mfpu=vfpv2") } default: return nil, fmt.Errorf("invalid extension GOARM=%s, must be softfloat or hardfloat", options.GOARM) } switch archLevel { case "5": llvmarch = "armv5" if spec.SoftFloat { spec.Features = "+armv5t,+soft-float,+strict-align,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } else { spec.Features = "+armv5t,+fp64,+strict-align,+vfp2,+vfp2sp,-aes,-d32,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-neon,-sha2,-thumb-mode,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } case "6": llvmarch = "armv6" if spec.SoftFloat { spec.Features = "+armv6,+dsp,+soft-float,+strict-align,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } else { spec.Features = "+armv6,+dsp,+fp64,+strict-align,+vfp2,+vfp2sp,-aes,-d32,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-neon,-sha2,-thumb-mode,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } case "7": llvmarch = "armv7" if spec.SoftFloat { spec.Features = "+armv7-a,+dsp,+soft-float,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } else { spec.Features = "+armv7-a,+d32,+dsp,+fp64,+neon,+vfp2,+vfp2sp,+vfp3,+vfp3d16,+vfp3d16sp,+vfp3sp,-aes,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-sha2,-thumb-mode,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } default: return nil, fmt.Errorf("invalid GOARM=%s, must be of form ,[hardfloat|softfloat] where num is 5, 6, or 7", options.GOARM) } case "arm64": spec.CPU = "generic" llvmarch = "aarch64" if options.GOOS == "darwin" { spec.Features = "+ete,+fp-armv8,+neon,+trbe,+v8a" // Looks like Apple prefers to call this architecture ARM64 // instead of AArch64. llvmarch = "arm64" } else if options.GOOS == "windows" { spec.Features = "+ete,+fp-armv8,+neon,+trbe,+v8a,-fmv" } else { // linux spec.Features = "+ete,+fp-armv8,+neon,+trbe,+v8a,-fmv,-outline-atomics" } case "mips", "mipsle": spec.CPU = "mips32" spec.CFlags = append(spec.CFlags, "-fno-pic") if options.GOARCH == "mips" { llvmarch = "mips" // big endian } else { llvmarch = "mipsel" // little endian } switch options.GOMIPS { case "hardfloat": spec.Features = "+fpxx,+mips32,+nooddspreg,-noabicalls" case "softfloat": spec.SoftFloat = true spec.Features = "+mips32,+soft-float,-noabicalls" spec.CFlags = append(spec.CFlags, "-msoft-float") default: return nil, fmt.Errorf("invalid GOMIPS=%s: must be hardfloat or softfloat", options.GOMIPS) } case "wasm": return nil, fmt.Errorf("GOARCH=wasm but GOOS is unset. Please set GOOS to js, wasip1, or wasip2.") default: return nil, fmt.Errorf("unknown GOARCH=%s", options.GOARCH) } // Configure target based on GOOS. llvmos := options.GOOS llvmvendor := "unknown" switch options.GOOS { case "darwin": spec.GC = "boehm" platformVersion := "10.12.0" if options.GOARCH == "arm64" { platformVersion = "11.0.0" // first macosx platform with arm64 support } llvmvendor = "apple" spec.Scheduler = "threads" spec.Linker = "ld.lld" spec.Libc = "darwin-libSystem" // Use macosx* instead of darwin, otherwise darwin/arm64 will refer to // iOS! llvmos = "macosx" + platformVersion spec.LDFlags = append(spec.LDFlags, "-flavor", "darwin", "-dead_strip", "-arch", llvmarch, "-platform_version", "macos", platformVersion, platformVersion, ) spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/futex/futex_darwin.c", "src/internal/task/task_threads.c", "src/runtime/os_darwin.c", "src/runtime/runtime_unix.c", "src/runtime/signal.c") case "linux": spec.GC = "boehm" spec.Scheduler = "threads" spec.Linker = "ld.lld" spec.RTLib = "compiler-rt" spec.Libc = "musl" spec.LDFlags = append(spec.LDFlags, "--gc-sections") if options.GOARCH == "arm64" { // Disable outline atomics. For details, see: // https://cpufun.substack.com/p/atomics-in-aarch64 // A better way would be to fully support outline atomics, which // makes atomics slightly more efficient on systems with many cores. // But the instructions are only supported on newer aarch64 CPUs, so // this feature is normally put in a system library which does // feature detection for you. // We take the lazy way out and simply disable this feature, instead // of enabling it in compiler-rt (which is a bit more complicated). // We don't really need this feature anyway as we don't even support // proper threading. spec.CFlags = append(spec.CFlags, "-mno-outline-atomics") } spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/futex/futex_linux.c", "src/internal/task/task_threads.c", "src/runtime/runtime_unix.c", "src/runtime/signal.c") case "windows": spec.GC = "boehm" spec.Scheduler = "tasks" spec.Linker = "ld.lld" spec.Libc = "mingw-w64" switch options.GOARCH { case "386": spec.LDFlags = append(spec.LDFlags, "-m", "i386pe", "--major-os-version", "4", "--major-subsystem-version", "4", ) // __udivdi3 is not present in ucrt it seems. spec.RTLib = "compiler-rt" case "amd64": // Note: using a medium code model, low image base and no ASLR // because Go doesn't really need those features. ASLR patches // around issues for unsafe languages like C/C++ that are not // normally present in Go (without explicitly opting in). // For more discussion: // https://groups.google.com/g/Golang-nuts/c/Jd9tlNc6jUE/m/Zo-7zIP_m3MJ?pli=1 spec.LDFlags = append(spec.LDFlags, "-m", "i386pep", "--image-base", "0x400000", ) case "arm64": spec.LDFlags = append(spec.LDFlags, "-m", "arm64pe", ) } spec.LDFlags = append(spec.LDFlags, "-Bdynamic", "--gc-sections", "--no-insert-timestamp", "--no-dynamicbase", ) case "wasm", "wasip1", "wasip2": return nil, fmt.Errorf("GOOS=%s but GOARCH is unset. Please set GOARCH to wasm", options.GOOS) default: return nil, fmt.Errorf("unknown GOOS=%s", options.GOOS) } if spec.GC == "boehm" { // Add this file only when needed. This fixes a build failure on // Windows. spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_boehm.c") } // Target triples (which actually have four components, but are called // triples for historical reasons) have the form: // arch-vendor-os-environment spec.Triple = llvmarch + "-" + llvmvendor + "-" + llvmos if options.GOOS == "windows" { spec.Triple += "-gnu" } else if options.GOOS == "linux" { // We use musl on Linux (not glibc) so we should use -musleabi* instead // of -gnueabi*. // The *hf suffix selects between soft/hard floating point ABI. if spec.SoftFloat { spec.Triple += "-musleabi" } else { spec.Triple += "-musleabihf" } } // Add extra assembly files (needed for the scheduler etc). if options.GOARCH != "wasm" { suffix := "" if options.GOOS == "windows" && options.GOARCH == "amd64" { // Windows uses a different calling convention on amd64 from other // operating systems so we need separate assembly files. suffix = "_windows" } asmGoarch := options.GOARCH if options.GOARCH == "mips" || options.GOARCH == "mipsle" { asmGoarch = "mipsx" } spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/asm_"+asmGoarch+suffix+".S") spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+asmGoarch+suffix+".S") } // Configure the emulator. if options.GOARCH != runtime.GOARCH { // Some educated guesses as to how to invoke helper programs. spec.GDB = []string{"gdb-multiarch"} if options.GOOS == "linux" { switch options.GOARCH { case "386": // amd64 can _usually_ run 32-bit programs, so skip the emulator in that case. if runtime.GOARCH != "amd64" { spec.Emulator = "qemu-i386 {}" } case "amd64": spec.Emulator = "qemu-x86_64 {}" case "arm": spec.Emulator = "qemu-arm {}" case "arm64": spec.Emulator = "qemu-aarch64 {}" case "mips": spec.Emulator = "qemu-mips {}" case "mipsle": spec.Emulator = "qemu-mipsel {}" } } } if options.GOOS != runtime.GOOS { if options.GOOS == "windows" { spec.Emulator = "wine {}" } } return &spec, nil } // LookupGDB looks up a gdb executable. func (spec *TargetSpec) LookupGDB() (string, error) { if len(spec.GDB) == 0 { return "", errors.New("gdb not configured in the target specification") } for _, d := range spec.GDB { _, err := exec.LookPath(d) if err == nil { return d, nil } } return "", errors.New("no gdb found configured in the target specification (" + strings.Join(spec.GDB, ", ") + ")") } ================================================ FILE: compileopts/target_test.go ================================================ package compileopts import ( "errors" "io/fs" "reflect" "testing" ) func TestLoadTarget(t *testing.T) { _, err := LoadTarget(&Options{Target: "arduino"}) if err != nil { t.Error("LoadTarget test failed:", err) } _, err = LoadTarget(&Options{Target: "notexist"}) if err == nil { t.Error("LoadTarget should have failed with non existing target") } if !errors.Is(err, fs.ErrNotExist) { t.Error("LoadTarget failed for wrong reason:", err) } } func TestOverrideProperties(t *testing.T) { baseAutoStackSize := true base := &TargetSpec{ GOOS: "baseGoos", CPU: "baseCpu", CFlags: []string{"-base-foo", "-base-bar"}, BuildTags: []string{"bt1", "bt2"}, DefaultStackSize: 42, AutoStackSize: &baseAutoStackSize, } childAutoStackSize := false child := &TargetSpec{ GOOS: "", CPU: "chlidCpu", CFlags: []string{"-child-foo", "-child-bar"}, AutoStackSize: &childAutoStackSize, DefaultStackSize: 64, } base.overrideProperties(child) if base.GOOS != "baseGoos" { t.Errorf("Overriding failed : got %v", base.GOOS) } if base.CPU != "chlidCpu" { t.Errorf("Overriding failed : got %v", base.CPU) } if !reflect.DeepEqual(base.CFlags, []string{"-base-foo", "-base-bar", "-child-foo", "-child-bar"}) { t.Errorf("Overriding failed : got %v", base.CFlags) } if !reflect.DeepEqual(base.BuildTags, []string{"bt1", "bt2"}) { t.Errorf("Overriding failed : got %v", base.BuildTags) } if *base.AutoStackSize != false { t.Errorf("Overriding failed : got %v", base.AutoStackSize) } if base.DefaultStackSize != 64 { t.Errorf("Overriding failed : got %v", base.DefaultStackSize) } baseAutoStackSize = true base = &TargetSpec{ AutoStackSize: &baseAutoStackSize, DefaultStackSize: 42, } child = &TargetSpec{ AutoStackSize: nil, DefaultStackSize: 0, } base.overrideProperties(child) if *base.AutoStackSize != true { t.Errorf("Overriding failed : got %v", base.AutoStackSize) } if base.DefaultStackSize != 42 { t.Errorf("Overriding failed : got %v", base.DefaultStackSize) } } ================================================ FILE: compiler/alias.go ================================================ package compiler // This file defines alias functions for functions that are normally defined in // Go assembly. // // The Go toolchain defines many performance critical functions in assembly // instead of plain Go. This is a problem for TinyGo as it currently (as of // august 2021) is not able to compile these assembly files and even if it // could, it would not be able to make use of them for many targets that are // supported by TinyGo (baremetal RISC-V, AVR, etc). Therefore, many of these // functions are aliased to their generic Go implementation. // This results in slower than possible implementations, but at least they are // usable. import "tinygo.org/x/go-llvm" var stdlibAliases = map[string]string{ // crypto packages "crypto/ed25519/internal/edwards25519/field.feMul": "crypto/ed25519/internal/edwards25519/field.feMulGeneric", "crypto/internal/edwards25519/field.feSquare": "crypto/ed25519/internal/edwards25519/field.feSquareGeneric", "crypto/md5.block": "crypto/md5.blockGeneric", "crypto/sha1.block": "crypto/sha1.blockGeneric", "crypto/sha1.blockAMD64": "crypto/sha1.blockGeneric", "crypto/sha256.block": "crypto/sha256.blockGeneric", "crypto/sha512.blockAMD64": "crypto/sha512.blockGeneric", "internal/chacha8rand.block": "internal/chacha8rand.block_generic", // AES "crypto/aes.decryptBlockAsm": "crypto/aes.decryptBlock", "crypto/aes.encryptBlockAsm": "crypto/aes.encryptBlock", // math package "math.archHypot": "math.hypot", "math.archMax": "math.max", "math.archMin": "math.min", "math.archModf": "math.modf", } // createAlias implements the function (in the builder) as a call to the alias // function. func (b *builder) createAlias(alias llvm.Value) { b.llvmFn.SetVisibility(llvm.HiddenVisibility) b.llvmFn.SetUnnamedAddr(true) if b.Debug { if b.fn.Syntax() != nil { // Create debug info file if present. b.difunc = b.attachDebugInfo(b.fn) } pos := b.program.Fset.Position(b.fn.Pos()) b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{}) } entryBlock := b.ctx.AddBasicBlock(b.llvmFn, "entry") b.SetInsertPointAtEnd(entryBlock) if b.llvmFn.Type() != alias.Type() { b.addError(b.fn.Pos(), "alias function should have the same type as aliasee "+alias.Name()) b.CreateUnreachable() return } result := b.CreateCall(alias.GlobalValueType(), alias, b.llvmFn.Params(), "") if result.Type().TypeKind() == llvm.VoidTypeKind { b.CreateRetVoid() } else { b.CreateRet(result) } } ================================================ FILE: compiler/asserts.go ================================================ package compiler // This file implements functions that do certain safety checks that are // required by the Go programming language. import ( "fmt" "go/token" "go/types" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) // createLookupBoundsCheck emits a bounds check before doing a lookup into a // slice. This is required by the Go language spec: an index out of bounds must // cause a panic. // The caller should make sure that index is at least as big as arrayLen. func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value) { if b.info.nobounds { // The //go:nobounds pragma was added to the function to avoid bounds // checking. return } // Extend arrayLen if it's too small. if index.Type().IntTypeWidth() > arrayLen.Type().IntTypeWidth() { // The index is bigger than the array length type, so extend it. arrayLen = b.CreateZExt(arrayLen, index.Type(), "") } // Now do the bounds check: index >= arrayLen outOfBounds := b.CreateICmp(llvm.IntUGE, index, arrayLen, "") b.createRuntimeAssert(outOfBounds, "lookup", "lookupPanic") } // createSliceBoundsCheck emits a bounds check before a slicing operation to make // sure it is within bounds. // // This function is both used for slicing a slice (low and high have their // normal meaning) and for creating a new slice, where 'capacity' means the // biggest possible slice capacity, 'low' means len and 'high' means cap. The // logic is the same in both cases. func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) { if b.info.nobounds { // The //go:nobounds pragma was added to the function to avoid bounds // checking. return } // Extend the capacity integer to be at least as wide as low and high. capacityType := capacity.Type() if low.Type().IntTypeWidth() > capacityType.IntTypeWidth() { capacityType = low.Type() } if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() { capacityType = high.Type() } if max.Type().IntTypeWidth() > capacityType.IntTypeWidth() { capacityType = max.Type() } if capacityType != capacity.Type() { capacity = b.CreateZExt(capacity, capacityType, "") } // Extend low and high to be the same size as capacity. low = b.extendInteger(low, lowType, capacityType) high = b.extendInteger(high, highType, capacityType) max = b.extendInteger(max, maxType, capacityType) // Now do the bounds check: low > high || high > capacity outOfBounds1 := b.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh") outOfBounds2 := b.CreateICmp(llvm.IntUGT, high, max, "slice.highmax") outOfBounds3 := b.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap") outOfBounds := b.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax") outOfBounds = b.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap") b.createRuntimeAssert(outOfBounds, "slice", "slicePanic") } // createSliceToArrayPointerCheck adds a check for slice-to-array pointer // conversions. This conversion was added in Go 1.17. For details, see: // https://tip.golang.org/ref/spec#Conversions_from_slice_to_array_pointer func (b *builder) createSliceToArrayPointerCheck(sliceLen llvm.Value, arrayLen int64) { // From the spec: // > If the length of the slice is less than the length of the array, a // > run-time panic occurs. arrayLenValue := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false) isLess := b.CreateICmp(llvm.IntULT, sliceLen, arrayLenValue, "") b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic") } // createUnsafeSliceStringCheck inserts a runtime check used for unsafe.Slice // and unsafe.String. This function must panic if the ptr/len parameters are // invalid. func (b *builder) createUnsafeSliceStringCheck(name string, ptr, len llvm.Value, elementType llvm.Type, lenType *types.Basic) { // From the documentation of unsafe.Slice and unsafe.String: // > At run time, if len is negative, or if ptr is nil and len is not // > zero, a run-time panic occurs. // However, in practice, it is also necessary to check that the length is // not too big that a GEP wouldn't be possible without wrapping the pointer. // These two checks (non-negative and not too big) can be merged into one // using an unsigned greater than. // Make sure the len value is at least as big as a uintptr. len = b.extendInteger(len, lenType, b.uintptrType) // Determine the maximum slice size, and therefore the maximum value of the // len parameter. maxSize := b.maxSliceSize(elementType) maxSizeValue := llvm.ConstInt(len.Type(), maxSize, false) // Do the check. By using unsigned greater than for the length check, signed // negative values are also checked (which are very large numbers when // interpreted as signed values). zero := llvm.ConstInt(len.Type(), 0, false) lenOutOfBounds := b.CreateICmp(llvm.IntUGT, len, maxSizeValue, "") ptrIsNil := b.CreateICmp(llvm.IntEQ, ptr, llvm.ConstNull(ptr.Type()), "") lenIsNotZero := b.CreateICmp(llvm.IntNE, len, zero, "") assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "") assert = b.CreateOr(assert, lenOutOfBounds, "") b.createRuntimeAssert(assert, name, "unsafeSlicePanic") } // createChanBoundsCheck creates a bounds check before creating a new channel to // check that the value is not too big for runtime.chanMake. func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) { if b.info.nobounds { // The //go:nobounds pragma was added to the function to avoid bounds // checking. return } // Make sure bufSize is at least as big as maxBufSize (an uintptr). bufSize = b.extendInteger(bufSize, bufSizeType, b.uintptrType) // Calculate (^uintptr(0)) >> 1, which is the max value that fits in an // uintptr if uintptrs were signed. maxBufSize := b.CreateLShr(llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)), llvm.ConstInt(b.uintptrType, 1, false), "") if elementSize > maxBufSize.ZExtValue() { b.addError(pos, fmt.Sprintf("channel element type is too big (%v bytes)", elementSize)) return } // Avoid divide-by-zero. if elementSize == 0 { elementSize = 1 } // Make the maxBufSize actually the maximum allowed value (in number of // elements in the channel buffer). maxBufSize = b.CreateUDiv(maxBufSize, llvm.ConstInt(b.uintptrType, elementSize, false), "") // Make sure maxBufSize has the same type as bufSize. if maxBufSize.Type() != bufSize.Type() { maxBufSize = b.CreateZExt(maxBufSize, bufSize.Type(), "") } // Do the check for a too large (or negative) buffer size. bufSizeTooBig := b.CreateICmp(llvm.IntUGE, bufSize, maxBufSize, "") b.createRuntimeAssert(bufSizeTooBig, "chan", "chanMakePanic") } // createNilCheck checks whether the given pointer is nil, and panics if it is. // It has no effect in well-behaved programs, but makes sure no uncaught nil // pointer dereferences exist in valid Go code. func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix string) { // Check whether we need to emit this check at all. if !ptr.IsAGlobalValue().IsNil() { return } switch inst := inst.(type) { case *ssa.Alloc: // An alloc is never nil. return case *ssa.FreeVar: // A free variable is allocated in a parent function and is thus never // nil. return case *ssa.IndexAddr: // This pointer is the result of an index operation into a slice or // array. Such slices/arrays are already bounds checked so the pointer // must be a valid (non-nil) pointer. No nil checking is necessary. return case *ssa.Convert: // This is a pointer that comes from a conversion from unsafe.Pointer. // Don't do nil checking because this is unsafe code and the code should // know what it is doing. // Note: all *ssa.Convert instructions that result in a pointer must // come from unsafe.Pointer. Testing here for unsafe.Pointer to be sure. if inst.X.Type() == types.Typ[types.UnsafePointer] { return } } // Compare against nil. // We previously used a hack to make sure this wouldn't break escape // analysis, but this is not necessary anymore since // https://reviews.llvm.org/D60047 has been merged. nilptr := llvm.ConstPointerNull(ptr.Type()) isnil := b.CreateICmp(llvm.IntEQ, ptr, nilptr, "") // Emit the nil check in IR. b.createRuntimeAssert(isnil, blockPrefix, "nilPanic") } // createNegativeShiftCheck creates an assertion that panics if the given shift value is negative. // This function assumes that the shift value is signed. func (b *builder) createNegativeShiftCheck(shift llvm.Value) { if b.info.nobounds { // Function disabled bounds checking - skip shift check. return } // isNegative = shift < 0 isNegative := b.CreateICmp(llvm.IntSLT, shift, llvm.ConstInt(shift.Type(), 0, false), "") b.createRuntimeAssert(isNegative, "shift", "negativeShiftPanic") } // createDivideByZeroCheck asserts that y is not zero. If it is, a runtime panic // will be emitted. This follows the Go specification which says that a divide // by zero must cause a run time panic. func (b *builder) createDivideByZeroCheck(y llvm.Value) { if b.info.nobounds { return } // isZero = y == 0 isZero := b.CreateICmp(llvm.IntEQ, y, llvm.ConstInt(y.Type(), 0, false), "") b.createRuntimeAssert(isZero, "divbyzero", "divideByZeroPanic") } // createRuntimeAssert is a common function to create a new branch on an assert // bool, calling an assert func if the assert value is true (1). func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc string) { // Check whether we can resolve this check at compile time. if !assert.IsAConstantInt().IsNil() { val := assert.ZExtValue() if val == 0 { // Everything is constant so the check does not have to be emitted // in IR. This avoids emitting some redundant IR. return } } // Put the fault block at the end of the function and the next block at the // current insert position. faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw") nextBlock := b.insertBasicBlock(blockPrefix + ".next") b.currentBlockInfo.exit = nextBlock // adjust outgoing block for phi nodes // Now branch to the out-of-bounds or the regular block. b.CreateCondBr(assert, faultBlock, nextBlock) // Fail: the assert triggered so panic. b.SetInsertPointAtEnd(faultBlock) b.createRuntimeCall(assertFunc, nil, "") b.CreateUnreachable() // Ok: assert didn't trigger so continue normally. b.SetInsertPointAtEnd(nextBlock) } // extendInteger extends the value to at least targetType using a zero or sign // extend. The resulting value is not truncated: it may still be bigger than // targetType. func (b *builder) extendInteger(value llvm.Value, valueType types.Type, targetType llvm.Type) llvm.Value { if value.Type().IntTypeWidth() < targetType.IntTypeWidth() { if valueType.Underlying().(*types.Basic).Info()&types.IsUnsigned != 0 { // Unsigned, so zero-extend to the target type. value = b.CreateZExt(value, targetType, "") } else { // Signed, so sign-extend to the target type. value = b.CreateSExt(value, targetType, "") } } return value } ================================================ FILE: compiler/atomic.go ================================================ package compiler import ( "tinygo.org/x/go-llvm" ) // createAtomicOp lowers a sync/atomic function by lowering it as an LLVM atomic // operation. It returns the result of the operation, or a zero llvm.Value if // the result is void. func (b *builder) createAtomicOp(name string) llvm.Value { switch name { case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr": ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) val := b.getValue(b.fn.Params[1], getPos(b.fn)) oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpAdd, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true) // Return the new value, not the original value returned by atomicrmw. return b.CreateAdd(oldVal, val, "") case "AndInt32", "AndInt64", "AndUint32", "AndUint64", "AndUintptr": ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) val := b.getValue(b.fn.Params[1], getPos(b.fn)) oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpAnd, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true) return oldVal case "OrInt32", "OrInt64", "OrUint32", "OrUint64", "OrUintptr": ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) val := b.getValue(b.fn.Params[1], getPos(b.fn)) oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpOr, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true) return oldVal case "SwapInt32", "SwapInt64", "SwapUint32", "SwapUint64", "SwapUintptr", "SwapPointer": ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) val := b.getValue(b.fn.Params[1], getPos(b.fn)) oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpXchg, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true) return oldVal case "CompareAndSwapInt32", "CompareAndSwapInt64", "CompareAndSwapUint32", "CompareAndSwapUint64", "CompareAndSwapUintptr", "CompareAndSwapPointer": ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) old := b.getValue(b.fn.Params[1], getPos(b.fn)) newVal := b.getValue(b.fn.Params[2], getPos(b.fn)) tuple := b.CreateAtomicCmpXchg(ptr, old, newVal, llvm.AtomicOrderingSequentiallyConsistent, llvm.AtomicOrderingSequentiallyConsistent, true) swapped := b.CreateExtractValue(tuple, 1, "") return swapped case "LoadInt32", "LoadInt64", "LoadUint32", "LoadUint64", "LoadUintptr", "LoadPointer": ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) val := b.CreateLoad(b.getLLVMType(b.fn.Signature.Results().At(0).Type()), ptr, "") val.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent) val.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required return val case "StoreInt32", "StoreInt64", "StoreUint32", "StoreUint64", "StoreUintptr", "StorePointer": ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) val := b.getValue(b.fn.Params[1], getPos(b.fn)) store := b.CreateStore(val, ptr) store.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent) store.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required return llvm.Value{} default: b.addError(b.fn.Pos(), "unknown atomic operation: "+b.fn.Name()) return llvm.Value{} } } ================================================ FILE: compiler/calls.go ================================================ package compiler import ( "go/types" "strconv" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) // For a description of the calling convention in prose, see: // https://tinygo.org/compiler-internals/calling-convention/ // The maximum number of arguments that can be expanded from a single struct. If // a struct contains more fields, it is passed as a struct without expanding. const maxFieldsPerParam = 3 // paramInfo contains some information collected about a function parameter, // useful while declaring or defining a function. type paramInfo struct { llvmType llvm.Type name string // name, possibly with suffixes for e.g. struct fields elemSize uint64 // size of pointer element type, or 0 if this isn't a pointer flags paramFlags // extra flags for this parameter } // paramFlags identifies parameter attributes for flags. Most importantly, it // determines which parameters are dereferenceable_or_null and which aren't. type paramFlags uint8 const ( // Whether this is a full or partial Go parameter (int, slice, etc). // The extra context parameter is not a Go parameter. paramIsGoParam = 1 << iota // Whether this is a readonly parameter (for example, a string pointer). paramIsReadonly ) // createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or // createRuntimeInvoke instead. func (b *builder) createRuntimeCallCommon(fnName string, args []llvm.Value, name string, isInvoke bool) llvm.Value { member := b.program.ImportedPackage("runtime").Members[fnName] if member == nil { panic("unknown runtime call: " + fnName) } fn := member.(*ssa.Function) fnType, llvmFn := b.getFunction(fn) if llvmFn.IsNil() { panic("trying to call non-existent function: " + fn.RelString(nil)) } args = append(args, llvm.Undef(b.dataPtrType)) // unused context parameter if isInvoke { return b.createInvoke(fnType, llvmFn, args, name) } return b.createCall(fnType, llvmFn, args, name) } // createRuntimeCall creates a new call to runtime. with the given // arguments. func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value { return b.createRuntimeCallCommon(fnName, args, name, false) } // createRuntimeInvoke creates a new call to runtime. with the given // arguments. If the runtime call panics, control flow is diverted to the // landing pad block. // Note that "invoke" here is meant in the LLVM sense (a call that can // panic/throw), not in the Go sense (an interface method call). func (b *builder) createRuntimeInvoke(fnName string, args []llvm.Value, name string) llvm.Value { return b.createRuntimeCallCommon(fnName, args, name, true) } // createCall creates a call to the given function with the arguments possibly // expanded. func (b *builder) createCall(fnType llvm.Type, fn llvm.Value, args []llvm.Value, name string) llvm.Value { expanded := make([]llvm.Value, 0, len(args)) for _, arg := range args { fragments := b.expandFormalParam(arg) expanded = append(expanded, fragments...) } call := b.CreateCall(fnType, fn, expanded, name) if !fn.IsAFunction().IsNil() { if cc := fn.FunctionCallConv(); cc != llvm.CCallConv { // Set a different calling convention if needed. // This is needed for GetModuleHandleExA on Windows, for example. call.SetInstructionCallConv(cc) } } return call } // createInvoke is like createCall but continues execution at the landing pad if // the call resulted in a panic. func (b *builder) createInvoke(fnType llvm.Type, fn llvm.Value, args []llvm.Value, name string) llvm.Value { if b.hasDeferFrame() { b.createInvokeCheckpoint() } return b.createCall(fnType, fn, args, name) } // Expand an argument type to a list that can be used in a function call // parameter list. func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo { switch t.TypeKind() { case llvm.StructTypeKind: fieldInfos := c.flattenAggregateType(t, name, goType) if len(fieldInfos) <= maxFieldsPerParam { // managed to expand this parameter return fieldInfos } // failed to expand this parameter: too many fields } // TODO: split small arrays return []paramInfo{c.getParamInfo(t, name, goType)} } // expandFormalParamOffsets returns a list of offsets from the start of an // object of type t after it would have been split up by expandFormalParam. This // is useful for debug information, where it is necessary to know the offset // from the start of the combined object. func (b *builder) expandFormalParamOffsets(t llvm.Type) []uint64 { switch t.TypeKind() { case llvm.StructTypeKind: fields := b.flattenAggregateTypeOffsets(t) if len(fields) <= maxFieldsPerParam { return fields } else { // failed to lower return []uint64{0} } default: // TODO: split small arrays return []uint64{0} } } // expandFormalParam splits a formal param value into pieces, so it can be // passed directly as part of a function call. For example, it splits up small // structs into individual fields. It is the equivalent of expandFormalParamType // for parameter values. func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value { switch v.Type().TypeKind() { case llvm.StructTypeKind: fieldInfos := b.flattenAggregateType(v.Type(), "", nil) if len(fieldInfos) <= maxFieldsPerParam { fields := b.flattenAggregate(v) if len(fields) != len(fieldInfos) { panic("type and value param lowering don't match") } return fields } else { // failed to lower return []llvm.Value{v} } default: // TODO: split small arrays return []llvm.Value{v} } } // Try to flatten a struct type to a list of types. Returns a 1-element slice // with the passed in type if this is not possible. func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo { switch t.TypeKind() { case llvm.StructTypeKind: var paramInfos []paramInfo for i, subfield := range t.StructElementTypes() { if c.targetData.TypeAllocSize(subfield) == 0 { continue } suffix := strconv.Itoa(i) isString := false if goType != nil { // Try to come up with a good suffix for this struct field, // depending on which Go type it's based on. switch goType := goType.Underlying().(type) { case *types.Interface: suffix = []string{"typecode", "value"}[i] case *types.Slice: suffix = []string{"data", "len", "cap"}[i] case *types.Struct: suffix = goType.Field(i).Name() case *types.Basic: switch goType.Kind() { case types.Complex64, types.Complex128: suffix = []string{"r", "i"}[i] case types.String: suffix = []string{"data", "len"}[i] isString = true } case *types.Signature: suffix = []string{"context", "funcptr"}[i] } } subInfos := c.flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i)) if isString { subInfos[0].flags |= paramIsReadonly } paramInfos = append(paramInfos, subInfos...) } return paramInfos default: return []paramInfo{c.getParamInfo(t, name, goType)} } } // getParamInfo collects information about a parameter. For example, if this // parameter is pointer-like, it will also store the element type for the // dereferenceable_or_null attribute. func (c *compilerContext) getParamInfo(t llvm.Type, name string, goType types.Type) paramInfo { info := paramInfo{ llvmType: t, name: name, flags: paramIsGoParam, } if goType != nil { switch underlying := goType.Underlying().(type) { case *types.Pointer: // Pointers in Go must either point to an object or be nil. info.elemSize = c.targetData.TypeAllocSize(c.getLLVMType(underlying.Elem())) case *types.Chan: // Channels are implemented simply as a *runtime.channel. info.elemSize = c.targetData.TypeAllocSize(c.getLLVMRuntimeType("channel")) case *types.Map: // Maps are similar to channels: they are implemented as a // *runtime.hashmap. info.elemSize = c.targetData.TypeAllocSize(c.getLLVMRuntimeType("hashmap")) } } return info } // extractSubfield extracts a field from a struct, or returns null if this is // not a struct and thus no subfield can be obtained. func extractSubfield(t types.Type, field int) types.Type { if t == nil { return nil } switch t := t.Underlying().(type) { case *types.Struct: return t.Field(field).Type() case *types.Interface, *types.Slice, *types.Basic, *types.Signature: // These Go types are (sometimes) implemented as LLVM structs but can't // really be split further up in Go (with the possible exception of // complex numbers). return nil default: // This should be unreachable. panic("cannot split subfield: " + t.String()) } } // flattenAggregateTypeOffsets returns the offsets from the start of an object of // type t if this object were flattened like in flattenAggregate. Used together // with flattenAggregate to know the start indices of each value in the // non-flattened object. // // Note: this is an implementation detail, use expandFormalParamOffsets instead. func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 { switch t.TypeKind() { case llvm.StructTypeKind: var fields []uint64 for fieldIndex, field := range t.StructElementTypes() { if c.targetData.TypeAllocSize(field) == 0 { continue } suboffsets := c.flattenAggregateTypeOffsets(field) offset := c.targetData.ElementOffset(t, fieldIndex) for i := range suboffsets { suboffsets[i] += offset } fields = append(fields, suboffsets...) } return fields default: return []uint64{0} } } // flattenAggregate breaks down a struct into its elementary values for argument // passing. It is the value equivalent of flattenAggregateType func (b *builder) flattenAggregate(v llvm.Value) []llvm.Value { switch v.Type().TypeKind() { case llvm.StructTypeKind: var fields []llvm.Value for i, field := range v.Type().StructElementTypes() { if b.targetData.TypeAllocSize(field) == 0 { continue } subfield := b.CreateExtractValue(v, i, "") subfields := b.flattenAggregate(subfield) fields = append(fields, subfields...) } return fields default: return []llvm.Value{v} } } // collapseFormalParam combines an aggregate object back into the original // value. This is used to join multiple LLVM parameters into a single Go value // in the function entry block. func (b *builder) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Value { param, remaining := b.collapseFormalParamInternal(t, fields) if len(remaining) != 0 { panic("failed to expand back all fields") } return param } // collapseFormalParamInternal is an implementation detail of // collapseFormalParam: it works by recursing until there are no fields left. func (b *builder) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) { switch t.TypeKind() { case llvm.StructTypeKind: flattened := b.flattenAggregateType(t, "", nil) if len(flattened) <= maxFieldsPerParam { value := llvm.ConstNull(t) for i, subtyp := range t.StructElementTypes() { if b.targetData.TypeAllocSize(subtyp) == 0 { continue } structField, remaining := b.collapseFormalParamInternal(subtyp, fields) fields = remaining value = b.CreateInsertValue(value, structField, i, "") } return value, fields } else { // this struct was not flattened return fields[0], fields[1:] } default: return fields[0], fields[1:] } } ================================================ FILE: compiler/channel.go ================================================ package compiler // This file lowers channel operations (make/send/recv/close) to runtime calls // or pseudo-operations that are lowered during goroutine lowering. import ( "fmt" "go/types" "math" "github.com/tinygo-org/tinygo/compiler/llvmutil" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value { elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().Underlying().(*types.Chan).Elem())) elementSizeValue := llvm.ConstInt(b.uintptrType, elementSize, false) bufSize := b.getValue(expr.Size, getPos(expr)) b.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos()) if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() { bufSize = b.CreateZExt(bufSize, b.uintptrType, "") } else if bufSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() { bufSize = b.CreateTrunc(bufSize, b.uintptrType, "") } return b.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "") } // createChanSend emits a pseudo chan send operation. It is lowered to the // actual channel send operation during goroutine lowering. func (b *builder) createChanSend(instr *ssa.Send) { ch := b.getValue(instr.Chan, getPos(instr)) chanValue := b.getValue(instr.X, getPos(instr)) // store value-to-send valueType := b.getLLVMType(instr.X.Type()) isZeroSize := b.targetData.TypeAllocSize(valueType) == 0 var valueAlloca, valueAllocaSize llvm.Value if isZeroSize { valueAlloca = llvm.ConstNull(b.dataPtrType) } else { valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value") b.CreateStore(chanValue, valueAlloca) } // Allocate buffer for the channel operation. channelOp := b.getLLVMRuntimeType("channelOp") channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op") // Do the send. b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "") // End the lifetime of the allocas. // This also works around a bug in CoroSplit, at least in LLVM 8: // https://bugs.llvm.org/show_bug.cgi?id=41742 b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize) if !isZeroSize { b.emitLifetimeEnd(valueAlloca, valueAllocaSize) } } // createChanRecv emits a pseudo chan receive operation. It is lowered to the // actual channel receive operation during goroutine lowering. func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value { valueType := b.getLLVMType(unop.X.Type().Underlying().(*types.Chan).Elem()) ch := b.getValue(unop.X, getPos(unop)) // Allocate memory to receive into. isZeroSize := b.targetData.TypeAllocSize(valueType) == 0 var valueAlloca, valueAllocaSize llvm.Value if isZeroSize { valueAlloca = llvm.ConstNull(b.dataPtrType) } else { valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value") } // Allocate buffer for the channel operation. channelOp := b.getLLVMRuntimeType("channelOp") channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op") // Do the receive. commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "") var received llvm.Value if isZeroSize { received = llvm.ConstNull(valueType) } else { received = b.CreateLoad(valueType, valueAlloca, "chan.received") b.emitLifetimeEnd(valueAlloca, valueAllocaSize) } b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize) if unop.CommaOk { tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false)) tuple = b.CreateInsertValue(tuple, received, 0, "") tuple = b.CreateInsertValue(tuple, commaOk, 1, "") return tuple } else { return received } } // createChanClose closes the given channel. func (b *builder) createChanClose(ch llvm.Value) { b.createRuntimeCall("chanClose", []llvm.Value{ch}, "") } // createSelect emits all IR necessary for a select statements. That's a // non-trivial amount of code because select is very complex to implement. func (b *builder) createSelect(expr *ssa.Select) llvm.Value { if len(expr.States) == 0 { // Shortcuts for some simple selects. llvmType := b.getLLVMType(expr.Type()) if expr.Blocking { // Blocks forever: // select {} b.createRuntimeCall("deadlock", nil, "") return llvm.Undef(llvmType) } else { // No-op: // select { // default: // } retval := llvm.Undef(llvmType) retval = b.CreateInsertValue(retval, llvm.ConstInt(b.intType, 0xffffffffffffffff, true), 0, "") return retval // {-1, false} } } const maxSelectStates = math.MaxUint32 >> 2 if len(expr.States) > maxSelectStates { // The runtime code assumes that the number of state must fit in 30 bits // (so the select index can be stored in a uint32 with two bits reserved // for other purposes). It seems unlikely that a real program would have // that many states, but we check for this case anyway to be sure. // We use a uint32 (and not a uintptr or uint64) to avoid 64-bit atomic // operations which aren't available everywhere. b.addError(expr.Pos(), fmt.Sprintf("too many select states: got %d but the maximum supported number is %d", len(expr.States), maxSelectStates)) // Continue as usual (we'll generate broken code but the error will // prevent the compilation to complete). } // This code create a (stack-allocated) slice containing all the select // cases and then calls runtime.chanSelect to perform the actual select // statement. // Simple selects (blocking and with just one case) are already transformed // into regular chan operations during SSA construction so we don't have to // optimize such small selects. // Go through all the cases. Create the selectStates slice and and // determine the receive buffer size and alignment. recvbufSize := uint64(0) recvbufAlign := 0 var selectStates []llvm.Value chanSelectStateType := b.getLLVMRuntimeType("chanSelectState") for _, state := range expr.States { ch := b.getValue(state.Chan, state.Pos) selectState := llvm.ConstNull(chanSelectStateType) selectState = b.CreateInsertValue(selectState, ch, 0, "") switch state.Dir { case types.RecvOnly: // Make sure the receive buffer is big enough and has the correct alignment. llvmType := b.getLLVMType(state.Chan.Type().Underlying().(*types.Chan).Elem()) if size := b.targetData.TypeAllocSize(llvmType); size > recvbufSize { recvbufSize = size } if align := b.targetData.ABITypeAlignment(llvmType); align > recvbufAlign { recvbufAlign = align } case types.SendOnly: // Store this value in an alloca and put a pointer to this alloca // in the send state. sendValue := b.getValue(state.Send, state.Pos) alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value") b.CreateStore(sendValue, alloca) selectState = b.CreateInsertValue(selectState, alloca, 1, "") default: panic("unreachable") } selectStates = append(selectStates, selectState) } // Create a receive buffer, where the received value will be stored. recvbuf := llvm.Undef(b.dataPtrType) if recvbufSize != 0 { allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize)) recvbufAlloca, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca") recvbufAlloca.SetAlignment(recvbufAlign) recvbuf = b.CreateGEP(allocaType, recvbufAlloca, []llvm.Value{ llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false), }, "select.recvbuf") } // Create the states slice (allocated on the stack). statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates)) statesAlloca, statesSize := b.createTemporaryAlloca(statesAllocaType, "select.states.alloca") for i, state := range selectStates { // Set each slice element to the appropriate channel. gep := b.CreateGEP(statesAllocaType, statesAlloca, []llvm.Value{ llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false), }, "") b.CreateStore(state, gep) } statesPtr := b.CreateGEP(statesAllocaType, statesAlloca, []llvm.Value{ llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false), }, "select.states") statesLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false) // Do the select in the runtime. var results llvm.Value if expr.Blocking { // Stack-allocate operation structures. // If these were simply created as a slice, they would heap-allocate. opsAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelOp"), len(selectStates)) opsAlloca, opsSize := b.createTemporaryAlloca(opsAllocaType, "select.block.alloca") opsLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false) opsPtr := b.CreateGEP(opsAllocaType, opsAlloca, []llvm.Value{ llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false), }, "select.block") results = b.createRuntimeCall("chanSelect", []llvm.Value{ recvbuf, statesPtr, statesLen, statesLen, // []chanSelectState opsPtr, opsLen, opsLen, // []channelOp }, "select.result") // Terminate the lifetime of the operation structures. b.emitLifetimeEnd(opsAlloca, opsSize) } else { opsPtr := llvm.ConstNull(b.dataPtrType) opsLen := llvm.ConstInt(b.uintptrType, 0, false) results = b.createRuntimeCall("chanSelect", []llvm.Value{ recvbuf, statesPtr, statesLen, statesLen, // []chanSelectState opsPtr, opsLen, opsLen, // []channelOp (nil slice) }, "select.result") } // Terminate the lifetime of the states alloca. b.emitLifetimeEnd(statesAlloca, statesSize) // The result value does not include all the possible received values, // because we can't load them in advance. Instead, the *ssa.Extract // instruction will treat a *ssa.Select specially and load it there inline. // Store the receive alloca in a sidetable until we hit this extract // instruction. if b.selectRecvBuf == nil { b.selectRecvBuf = make(map[*ssa.Select]llvm.Value) } b.selectRecvBuf[expr] = recvbuf return results } // getChanSelectResult returns the special values from a *ssa.Extract expression // when extracting a value from a select statement (*ssa.Select). Because // *ssa.Select cannot load all values in advance, it does this later in the // *ssa.Extract expression. func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value { if expr.Index == 0 { // index value := b.getValue(expr.Tuple, getPos(expr)) index := b.CreateExtractValue(value, expr.Index, "") if index.Type().IntTypeWidth() < b.intType.IntTypeWidth() { index = b.CreateSExt(index, b.intType, "") } return index } else if expr.Index == 1 { // comma-ok value := b.getValue(expr.Tuple, getPos(expr)) return b.CreateExtractValue(value, expr.Index, "") } else { // Select statements are (index, ok, ...) where ... is a number of // received values, depending on how many receive statements there // are. They are all combined into one alloca (because only one // receive can proceed at a time) so we'll get that alloca, bitcast // it to the correct type, and dereference it. recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)] typ := b.getLLVMType(expr.Type()) return b.CreateLoad(typ, recvbuf, "") } } ================================================ FILE: compiler/compiler.go ================================================ package compiler import ( "debug/dwarf" "errors" "fmt" "go/ast" "go/constant" "go/token" "go/types" "math/bits" "path" "path/filepath" "sort" "strconv" "strings" "github.com/tinygo-org/tinygo/compiler/llvmutil" "github.com/tinygo-org/tinygo/loader" "github.com/tinygo-org/tinygo/src/tinygo" "golang.org/x/tools/go/ssa" "golang.org/x/tools/go/types/typeutil" "tinygo.org/x/go-llvm" ) func init() { llvm.InitializeAllTargets() llvm.InitializeAllTargetMCs() llvm.InitializeAllTargetInfos() llvm.InitializeAllAsmParsers() llvm.InitializeAllAsmPrinters() } // Config is the configuration for the compiler. Most settings should be copied // directly from compileopts.Config, it recreated here to decouple the compiler // package a bit and because it makes caching easier. // // This struct can be used for caching: if one of the flags here changes the // code must be recompiled. type Config struct { // Target and output information. Triple string CPU string Features string ABI string GOOS string GOARCH string BuildMode string CodeModel string RelocationModel string SizeLevel int TinyGoVersion string // for llvm.ident // Various compiler options that determine how code is generated. Scheduler string AutomaticStackSize bool DefaultStackSize uint64 MaxStackAlloc uint64 NeedsStackObjects bool Debug bool // Whether to emit debug information in the LLVM module. Nobounds bool // Whether to skip bounds checks PanicStrategy string } // compilerContext contains function-independent data that should still be // available while compiling every function. It is not strictly read-only, but // must not contain function-dependent data such as an IR builder. type compilerContext struct { *Config DumpSSA bool mod llvm.Module ctx llvm.Context builder llvm.Builder // only used for constant operations dibuilder *llvm.DIBuilder cu llvm.Metadata difiles map[string]llvm.Metadata ditypes map[types.Type]llvm.Metadata llvmTypes typeutil.Map interfaceTypes typeutil.Map machine llvm.TargetMachine targetData llvm.TargetData intType llvm.Type dataPtrType llvm.Type // pointer in address space 0 funcPtrType llvm.Type // pointer in function address space (1 for AVR, 0 elsewhere) funcPtrAddrSpace int uintptrType llvm.Type program *ssa.Program diagnostics []error functionInfos map[*ssa.Function]functionInfo astComments map[string]*ast.CommentGroup embedGlobals map[string][]*loader.EmbedFile pkg *types.Package packageDir string // directory for this package runtimePkg *types.Package } // newCompilerContext returns a new compiler context ready for use, most // importantly with a newly created LLVM context and module. func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *Config, dumpSSA bool) *compilerContext { c := &compilerContext{ Config: config, DumpSSA: dumpSSA, difiles: make(map[string]llvm.Metadata), ditypes: make(map[types.Type]llvm.Metadata), machine: machine, targetData: machine.CreateTargetData(), functionInfos: map[*ssa.Function]functionInfo{}, astComments: map[string]*ast.CommentGroup{}, } c.ctx = llvm.NewContext() c.builder = c.ctx.NewBuilder() c.mod = c.ctx.NewModule(moduleName) c.mod.SetTarget(config.Triple) c.mod.SetDataLayout(c.targetData.String()) if c.Debug { c.dibuilder = llvm.NewDIBuilder(c.mod) } c.uintptrType = c.ctx.IntType(c.targetData.PointerSize() * 8) if c.targetData.PointerSize() <= 4 { // 8, 16, 32 bits targets c.intType = c.ctx.Int32Type() } else if c.targetData.PointerSize() == 8 { // 64 bits target c.intType = c.ctx.Int64Type() } else { panic("unknown pointer size") } c.dataPtrType = llvm.PointerType(c.ctx.Int8Type(), 0) dummyFuncType := llvm.FunctionType(c.ctx.VoidType(), nil, false) dummyFunc := llvm.AddFunction(c.mod, "tinygo.dummy", dummyFuncType) c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace() c.funcPtrType = dummyFunc.Type() dummyFunc.EraseFromParentAsFunction() return c } // Dispose everything related to the context, _except_ for the IR module (and // the associated context). func (c *compilerContext) dispose() { c.builder.Dispose() } // builder contains all information relevant to build a single function. type builder struct { *compilerContext llvm.Builder fn *ssa.Function llvmFnType llvm.Type llvmFn llvm.Value info functionInfo locals map[ssa.Value]llvm.Value // local variables blockInfo []blockInfo currentBlock *ssa.BasicBlock currentBlockInfo *blockInfo tarjanStack []uint tarjanIndex uint phis []phiNode deferPtr llvm.Value deferFrame llvm.Value stackChainAlloca llvm.Value landingpad llvm.BasicBlock difunc llvm.Metadata dilocals map[*types.Var]llvm.Metadata initInlinedAt llvm.Metadata // fake inlinedAt position initPseudoFuncs map[string]llvm.Metadata // fake "inlined" functions for proper init debug locations allDeferFuncs []interface{} deferFuncs map[*ssa.Function]int deferInvokeFuncs map[string]int deferClosureFuncs map[*ssa.Function]int deferExprFuncs map[ssa.Value]int selectRecvBuf map[*ssa.Select]llvm.Value deferBuiltinFuncs map[ssa.Value]deferBuiltin runDefersBlock []llvm.BasicBlock afterDefersBlock []llvm.BasicBlock } func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ssa.Function) *builder { fnType, fn := c.getFunction(f) return &builder{ compilerContext: c, Builder: irbuilder, fn: f, llvmFnType: fnType, llvmFn: fn, info: c.getFunctionInfo(f), locals: make(map[ssa.Value]llvm.Value), dilocals: make(map[*types.Var]llvm.Metadata), } } type blockInfo struct { // entry is the LLVM basic block corresponding to the start of this *ssa.Block. entry llvm.BasicBlock // exit is the LLVM basic block corresponding to the end of this *ssa.Block. // It will be different than entry if any of the block's instructions contain internal branches. exit llvm.BasicBlock // tarjan holds state for applying Tarjan's strongly connected components algorithm to the CFG. // This is used by defer.go to determine whether to stack- or heap-allocate defer data. tarjan tarjanNode } type deferBuiltin struct { callName string pos token.Pos argTypes []types.Type callback int } type phiNode struct { ssa *ssa.Phi llvm llvm.Value } // NewTargetMachine returns a new llvm.TargetMachine based on the passed-in // configuration. It is used by the compiler and is needed for machine code // emission. func NewTargetMachine(config *Config) (llvm.TargetMachine, error) { target, err := llvm.GetTargetFromTriple(config.Triple) if err != nil { return llvm.TargetMachine{}, err } var codeModel llvm.CodeModel var relocationModel llvm.RelocMode switch config.CodeModel { case "default": codeModel = llvm.CodeModelDefault case "tiny": codeModel = llvm.CodeModelTiny case "small": codeModel = llvm.CodeModelSmall case "kernel": codeModel = llvm.CodeModelKernel case "medium": codeModel = llvm.CodeModelMedium case "large": codeModel = llvm.CodeModelLarge } switch config.RelocationModel { case "static": relocationModel = llvm.RelocStatic case "pic": relocationModel = llvm.RelocPIC case "dynamicnopic": relocationModel = llvm.RelocDynamicNoPic } machine := target.CreateTargetMachine(config.Triple, config.CPU, config.Features, llvm.CodeGenLevelDefault, relocationModel, codeModel) return machine, nil } // Sizes returns a types.Sizes appropriate for the given target machine. It // includes the correct int size and alignment as is necessary for the Go // typechecker. func Sizes(machine llvm.TargetMachine) types.Sizes { targetData := machine.CreateTargetData() defer targetData.Dispose() var intWidth int if targetData.PointerSize() <= 4 { // 8, 16, 32 bits targets intWidth = 32 } else if targetData.PointerSize() == 8 { // 64 bits target intWidth = 64 } else { panic("unknown pointer size") } // Construct a complex128 type because that's likely the type with the // biggest alignment on most/all ABIs. ctx := llvm.NewContext() defer ctx.Dispose() complex128Type := ctx.StructType([]llvm.Type{ctx.DoubleType(), ctx.DoubleType()}, false) return &stdSizes{ IntSize: int64(intWidth / 8), PtrSize: int64(targetData.PointerSize()), MaxAlign: int64(targetData.ABITypeAlignment(complex128Type)), } } // CompilePackage compiles a single package to a LLVM module. func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package, machine llvm.TargetMachine, config *Config, dumpSSA bool) (llvm.Module, []error) { c := newCompilerContext(moduleName, machine, config, dumpSSA) defer c.dispose() c.packageDir = pkg.OriginalDir() c.embedGlobals = pkg.EmbedGlobals c.pkg = pkg.Pkg c.runtimePkg = ssaPkg.Prog.ImportedPackage("runtime").Pkg c.program = ssaPkg.Prog // Convert AST to SSA. ssaPkg.Build() // Initialize debug information. if c.Debug { c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{ Language: 0xb, // DW_LANG_C99 (0xc, off-by-one?) File: "", Dir: "", Producer: "TinyGo", Optimized: true, }) } // Load comments such as //go:extern on globals. c.loadASTComments(pkg) // Predeclare the runtime.alloc function, which is used by the wordpack // functionality. c.getFunction(c.program.ImportedPackage("runtime").Members["alloc"].(*ssa.Function)) if c.NeedsStackObjects { // Predeclare trackPointer, which is used everywhere we use runtime.alloc. c.getFunction(c.program.ImportedPackage("runtime").Members["trackPointer"].(*ssa.Function)) } // Compile all functions, methods, and global variables in this package. irbuilder := c.ctx.NewBuilder() defer irbuilder.Dispose() c.createPackage(irbuilder, ssaPkg) // see: https://reviews.llvm.org/D18355 if c.Debug { c.mod.AddNamedMetadataOperand("llvm.module.flags", c.ctx.MDNode([]llvm.Metadata{ llvm.ConstInt(c.ctx.Int32Type(), 2, false).ConstantAsMetadata(), // Warning on mismatch c.ctx.MDString("Debug Info Version"), llvm.ConstInt(c.ctx.Int32Type(), 3, false).ConstantAsMetadata(), // DWARF version }), ) c.mod.AddNamedMetadataOperand("llvm.module.flags", c.ctx.MDNode([]llvm.Metadata{ llvm.ConstInt(c.ctx.Int32Type(), 7, false).ConstantAsMetadata(), // Max on mismatch c.ctx.MDString("Dwarf Version"), llvm.ConstInt(c.ctx.Int32Type(), 4, false).ConstantAsMetadata(), }), ) if c.TinyGoVersion != "" { // It is necessary to set llvm.ident, otherwise debugging on MacOS // won't work. c.mod.AddNamedMetadataOperand("llvm.ident", c.ctx.MDNode(([]llvm.Metadata{ c.ctx.MDString("TinyGo version " + c.TinyGoVersion), }))) } c.dibuilder.Finalize() c.dibuilder.Destroy() } // Add the "target-abi" flag, which is necessary on RISC-V otherwise it will // pick one that doesn't match the -mabi Clang flag. if c.ABI != "" { c.mod.AddNamedMetadataOperand("llvm.module.flags", c.ctx.MDNode([]llvm.Metadata{ llvm.ConstInt(c.ctx.Int32Type(), 1, false).ConstantAsMetadata(), // Error on mismatch c.ctx.MDString("target-abi"), c.ctx.MDString(c.ABI), }), ) } return c.mod, c.diagnostics } func (c *compilerContext) getRuntimeType(name string) types.Type { return c.runtimePkg.Scope().Lookup(name).(*types.TypeName).Type() } // getLLVMRuntimeType obtains a named type from the runtime package and returns // it as a LLVM type, creating it if necessary. It is a shorthand for // getLLVMType(getRuntimeType(name)). func (c *compilerContext) getLLVMRuntimeType(name string) llvm.Type { return c.getLLVMType(c.getRuntimeType(name)) } // getLLVMType returns a LLVM type for a Go type. It doesn't recreate already // created types. This is somewhat important for performance, but especially // important for named struct types (which should only be created once). func (c *compilerContext) getLLVMType(goType types.Type) llvm.Type { // Try to load the LLVM type from the cache. // Note: *types.Named isn't unique when working with generics. // See https://github.com/golang/go/issues/53914 // This is the reason for using typeutil.Map to lookup LLVM types for Go types. ival := c.llvmTypes.At(goType) if ival != nil { return ival.(llvm.Type) } // Not already created, so adding this type to the cache. llvmType := c.makeLLVMType(goType) c.llvmTypes.Set(goType, llvmType) return llvmType } // makeLLVMType creates a LLVM type for a Go type. Don't call this, use // getLLVMType instead. func (c *compilerContext) makeLLVMType(goType types.Type) llvm.Type { switch typ := types.Unalias(goType).(type) { case *types.Array: elemType := c.getLLVMType(typ.Elem()) return llvm.ArrayType(elemType, int(typ.Len())) case *types.Basic: switch typ.Kind() { case types.Bool, types.UntypedBool: return c.ctx.Int1Type() case types.Int8, types.Uint8: return c.ctx.Int8Type() case types.Int16, types.Uint16: return c.ctx.Int16Type() case types.Int32, types.Uint32: return c.ctx.Int32Type() case types.Int, types.Uint: return c.intType case types.Int64, types.Uint64: return c.ctx.Int64Type() case types.Float32: return c.ctx.FloatType() case types.Float64: return c.ctx.DoubleType() case types.Complex64: return c.ctx.StructType([]llvm.Type{c.ctx.FloatType(), c.ctx.FloatType()}, false) case types.Complex128: return c.ctx.StructType([]llvm.Type{c.ctx.DoubleType(), c.ctx.DoubleType()}, false) case types.String, types.UntypedString: return c.getLLVMRuntimeType("_string") case types.Uintptr: return c.uintptrType case types.UnsafePointer: return c.dataPtrType default: panic("unknown basic type: " + typ.String()) } case *types.Chan, *types.Map, *types.Pointer: return c.dataPtrType // all pointers are the same case *types.Interface: return c.getLLVMRuntimeType("_interface") case *types.Named: if st, ok := typ.Underlying().(*types.Struct); ok { // Structs are a special case. While other named types are ignored // in LLVM IR, named structs are implemented as named structs in // LLVM. This is because it is otherwise impossible to create // self-referencing types such as linked lists. llvmName := typ.String() llvmType := c.ctx.StructCreateNamed(llvmName) c.llvmTypes.Set(goType, llvmType) // avoid infinite recursion underlying := c.getLLVMType(st) llvmType.StructSetBody(underlying.StructElementTypes(), false) return llvmType } return c.getLLVMType(typ.Underlying()) case *types.Signature: // function value return c.getFuncType(typ) case *types.Slice: members := []llvm.Type{ c.dataPtrType, c.uintptrType, // len c.uintptrType, // cap } return c.ctx.StructType(members, false) case *types.Struct: members := make([]llvm.Type, typ.NumFields()) for i := 0; i < typ.NumFields(); i++ { members[i] = c.getLLVMType(typ.Field(i).Type()) } return c.ctx.StructType(members, false) case *types.TypeParam: return c.getLLVMType(typ.Underlying()) case *types.Tuple: members := make([]llvm.Type, typ.Len()) for i := 0; i < typ.Len(); i++ { members[i] = c.getLLVMType(typ.At(i).Type()) } return c.ctx.StructType(members, false) default: panic("unknown type: " + goType.String()) } } // Is this a pointer type of some sort? Can be unsafe.Pointer or any *T pointer. func isPointer(typ types.Type) bool { if _, ok := typ.(*types.Pointer); ok { return true } else if typ, ok := typ.(*types.Basic); ok && typ.Kind() == types.UnsafePointer { return true } else { return false } } // Get the DWARF type for this Go type. func (c *compilerContext) getDIType(typ types.Type) llvm.Metadata { if md, ok := c.ditypes[typ]; ok { return md } md := c.createDIType(typ) c.ditypes[typ] = md return md } // createDIType creates a new DWARF type. Don't call this function directly, // call getDIType instead. func (c *compilerContext) createDIType(typ types.Type) llvm.Metadata { llvmType := c.getLLVMType(typ) sizeInBytes := c.targetData.TypeAllocSize(llvmType) switch typ := typ.(type) { case *types.Alias: // Implement types.Alias just like types.Named: by treating them like a // C typedef. temporaryMDNode := c.dibuilder.CreateReplaceableCompositeType(llvm.Metadata{}, llvm.DIReplaceableCompositeType{ Tag: dwarf.TagTypedef, SizeInBits: sizeInBytes * 8, AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8, }) c.ditypes[typ] = temporaryMDNode md := c.dibuilder.CreateTypedef(llvm.DITypedef{ Type: c.getDIType(types.Unalias(typ)), // TODO: use typ.Rhs in Go 1.23 Name: typ.String(), }) temporaryMDNode.ReplaceAllUsesWith(md) return md case *types.Array: return c.dibuilder.CreateArrayType(llvm.DIArrayType{ SizeInBits: sizeInBytes * 8, AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8, ElementType: c.getDIType(typ.Elem()), Subscripts: []llvm.DISubrange{ { Lo: 0, Count: typ.Len(), }, }, }) case *types.Basic: var encoding llvm.DwarfTypeEncoding if typ.Info()&types.IsBoolean != 0 { encoding = llvm.DW_ATE_boolean } else if typ.Info()&types.IsFloat != 0 { encoding = llvm.DW_ATE_float } else if typ.Info()&types.IsComplex != 0 { encoding = llvm.DW_ATE_complex_float } else if typ.Info()&types.IsUnsigned != 0 { encoding = llvm.DW_ATE_unsigned } else if typ.Info()&types.IsInteger != 0 { encoding = llvm.DW_ATE_signed } else if typ.Kind() == types.UnsafePointer { return c.dibuilder.CreatePointerType(llvm.DIPointerType{ Name: "unsafe.Pointer", SizeInBits: c.targetData.TypeAllocSize(llvmType) * 8, AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8, AddressSpace: 0, }) } else if typ.Info()&types.IsString != 0 { return c.dibuilder.CreateStructType(llvm.Metadata{}, llvm.DIStructType{ Name: "string", SizeInBits: sizeInBytes * 8, AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8, Elements: []llvm.Metadata{ c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{ Name: "ptr", SizeInBits: c.targetData.TypeAllocSize(c.dataPtrType) * 8, AlignInBits: uint32(c.targetData.ABITypeAlignment(c.dataPtrType)) * 8, OffsetInBits: 0, Type: c.getDIType(types.NewPointer(types.Typ[types.Byte])), }), c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{ Name: "len", SizeInBits: c.targetData.TypeAllocSize(c.uintptrType) * 8, AlignInBits: uint32(c.targetData.ABITypeAlignment(c.uintptrType)) * 8, OffsetInBits: c.targetData.ElementOffset(llvmType, 1) * 8, Type: c.getDIType(types.Typ[types.Uintptr]), }), }, }) } else { panic("unknown basic type") } return c.dibuilder.CreateBasicType(llvm.DIBasicType{ Name: typ.String(), SizeInBits: sizeInBytes * 8, Encoding: encoding, }) case *types.Chan: return c.getDIType(types.NewPointer(c.program.ImportedPackage("runtime").Members["channel"].(*ssa.Type).Type())) case *types.Interface: return c.getDIType(c.program.ImportedPackage("runtime").Members["_interface"].(*ssa.Type).Type()) case *types.Map: return c.getDIType(types.NewPointer(c.program.ImportedPackage("runtime").Members["hashmap"].(*ssa.Type).Type())) case *types.Named: // Placeholder metadata node, to be replaced afterwards. temporaryMDNode := c.dibuilder.CreateReplaceableCompositeType(llvm.Metadata{}, llvm.DIReplaceableCompositeType{ Tag: dwarf.TagTypedef, SizeInBits: sizeInBytes * 8, AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8, }) c.ditypes[typ] = temporaryMDNode md := c.dibuilder.CreateTypedef(llvm.DITypedef{ Type: c.getDIType(typ.Underlying()), Name: typ.String(), }) temporaryMDNode.ReplaceAllUsesWith(md) return md case *types.Pointer: return c.dibuilder.CreatePointerType(llvm.DIPointerType{ Pointee: c.getDIType(typ.Elem()), SizeInBits: c.targetData.TypeAllocSize(llvmType) * 8, AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8, AddressSpace: 0, }) case *types.Signature: // actually a closure fields := llvmType.StructElementTypes() return c.dibuilder.CreateStructType(llvm.Metadata{}, llvm.DIStructType{ SizeInBits: sizeInBytes * 8, AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8, Elements: []llvm.Metadata{ c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{ Name: "context", SizeInBits: c.targetData.TypeAllocSize(fields[1]) * 8, AlignInBits: uint32(c.targetData.ABITypeAlignment(fields[1])) * 8, OffsetInBits: 0, Type: c.getDIType(types.Typ[types.UnsafePointer]), }), c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{ Name: "fn", SizeInBits: c.targetData.TypeAllocSize(fields[0]) * 8, AlignInBits: uint32(c.targetData.ABITypeAlignment(fields[0])) * 8, OffsetInBits: c.targetData.ElementOffset(llvmType, 1) * 8, Type: c.getDIType(types.Typ[types.UnsafePointer]), }), }, }) case *types.Slice: fields := llvmType.StructElementTypes() return c.dibuilder.CreateStructType(llvm.Metadata{}, llvm.DIStructType{ Name: typ.String(), SizeInBits: sizeInBytes * 8, AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8, Elements: []llvm.Metadata{ c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{ Name: "ptr", SizeInBits: c.targetData.TypeAllocSize(fields[0]) * 8, AlignInBits: uint32(c.targetData.ABITypeAlignment(fields[0])) * 8, OffsetInBits: 0, Type: c.getDIType(types.NewPointer(typ.Elem())), }), c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{ Name: "len", SizeInBits: c.targetData.TypeAllocSize(c.uintptrType) * 8, AlignInBits: uint32(c.targetData.ABITypeAlignment(c.uintptrType)) * 8, OffsetInBits: c.targetData.ElementOffset(llvmType, 1) * 8, Type: c.getDIType(types.Typ[types.Uintptr]), }), c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{ Name: "cap", SizeInBits: c.targetData.TypeAllocSize(c.uintptrType) * 8, AlignInBits: uint32(c.targetData.ABITypeAlignment(c.uintptrType)) * 8, OffsetInBits: c.targetData.ElementOffset(llvmType, 2) * 8, Type: c.getDIType(types.Typ[types.Uintptr]), }), }, }) case *types.Struct: elements := make([]llvm.Metadata, typ.NumFields()) for i := range elements { field := typ.Field(i) fieldType := field.Type() llvmField := c.getLLVMType(fieldType) elements[i] = c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{ Name: field.Name(), SizeInBits: c.targetData.TypeAllocSize(llvmField) * 8, AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmField)) * 8, OffsetInBits: c.targetData.ElementOffset(llvmType, i) * 8, Type: c.getDIType(fieldType), }) } md := c.dibuilder.CreateStructType(llvm.Metadata{}, llvm.DIStructType{ SizeInBits: sizeInBytes * 8, AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8, Elements: elements, }) return md case *types.TypeParam: return c.getDIType(typ.Underlying()) default: panic("unknown type while generating DWARF debug type: " + typ.String()) } } // setDebugLocation sets the current debug location for the builder. func (b *builder) setDebugLocation(pos token.Pos) { if pos == token.NoPos { // No debug information available for this instruction. b.SetCurrentDebugLocation(0, 0, b.difunc, llvm.Metadata{}) return } position := b.program.Fset.Position(pos) if b.fn.Synthetic == "package initializer" { // Package initializers are treated specially, because while individual // Go SSA instructions have file/line/col information, the parent // function does not. LLVM doesn't store filename information per // instruction, only per function. We work around this difference by // creating a fake DIFunction for each Go file and say that the // instruction really came from that (fake) function but was inlined in // the package initializer function. position := b.program.Fset.Position(pos) name := filepath.Base(position.Filename) difunc, ok := b.initPseudoFuncs[name] if !ok { diFuncType := b.dibuilder.CreateSubroutineType(llvm.DISubroutineType{ File: b.getDIFile(position.Filename), }) difunc = b.dibuilder.CreateFunction(b.getDIFile(position.Filename), llvm.DIFunction{ Name: b.fn.RelString(nil) + "#" + name, File: b.getDIFile(position.Filename), Line: 0, Type: diFuncType, LocalToUnit: true, IsDefinition: true, ScopeLine: 0, Flags: llvm.FlagPrototyped, Optimized: true, }) b.initPseudoFuncs[name] = difunc } b.SetCurrentDebugLocation(uint(position.Line), uint(position.Column), difunc, b.initInlinedAt) return } // Regular debug information. b.SetCurrentDebugLocation(uint(position.Line), uint(position.Column), b.difunc, llvm.Metadata{}) } // getLocalVariable returns a debug info entry for a local variable, which may // either be a parameter or a regular variable. It will create a new metadata // entry if there isn't one for the variable yet. func (b *builder) getLocalVariable(variable *types.Var) llvm.Metadata { if dilocal, ok := b.dilocals[variable]; ok { // DILocalVariable was already created, return it directly. return dilocal } pos := b.program.Fset.Position(variable.Pos()) // Check whether this is a function parameter. for i, param := range b.fn.Params { if param.Object().(*types.Var) == variable { // Yes it is, create it as a function parameter. dilocal := b.dibuilder.CreateParameterVariable(b.difunc, llvm.DIParameterVariable{ Name: param.Name(), File: b.getDIFile(pos.Filename), Line: pos.Line, Type: b.getDIType(param.Type()), AlwaysPreserve: true, ArgNo: i + 1, }) b.dilocals[variable] = dilocal return dilocal } } // No, it's not a parameter. Create a regular (auto) variable. dilocal := b.dibuilder.CreateAutoVariable(b.difunc, llvm.DIAutoVariable{ Name: variable.Name(), File: b.getDIFile(pos.Filename), Line: pos.Line, Type: b.getDIType(variable.Type()), AlwaysPreserve: true, }) b.dilocals[variable] = dilocal return dilocal } // attachDebugInfo adds debug info to a function declaration. It returns the // DISubprogram metadata node. func (c *compilerContext) attachDebugInfo(f *ssa.Function) llvm.Metadata { pos := c.program.Fset.Position(f.Syntax().Pos()) _, fn := c.getFunction(f) return c.attachDebugInfoRaw(f, fn, "", pos.Filename, pos.Line) } // attachDebugInfo adds debug info to a function declaration. It returns the // DISubprogram metadata node. This method allows some more control over how // debug info is added to the function. func (c *compilerContext) attachDebugInfoRaw(f *ssa.Function, llvmFn llvm.Value, suffix, filename string, line int) llvm.Metadata { // Debug info for this function. params := getParams(f.Signature) diparams := make([]llvm.Metadata, 0, len(params)) for _, param := range params { diparams = append(diparams, c.getDIType(param.Type())) } diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{ File: c.getDIFile(filename), Parameters: diparams, Flags: 0, // ? }) difunc := c.dibuilder.CreateFunction(c.getDIFile(filename), llvm.DIFunction{ Name: f.RelString(nil) + suffix, LinkageName: c.getFunctionInfo(f).linkName + suffix, File: c.getDIFile(filename), Line: line, Type: diFuncType, LocalToUnit: true, IsDefinition: true, ScopeLine: 0, Flags: llvm.FlagPrototyped, Optimized: true, }) llvmFn.SetSubprogram(difunc) return difunc } // getDIFile returns a DIFile metadata node for the given filename. It tries to // use one that was already created, otherwise it falls back to creating a new // one. func (c *compilerContext) getDIFile(filename string) llvm.Metadata { if _, ok := c.difiles[filename]; !ok { dir, file := filepath.Split(filename) if dir != "" { dir = dir[:len(dir)-1] } c.difiles[filename] = c.dibuilder.CreateFile(file, dir) } return c.difiles[filename] } // createPackage builds the LLVM IR for all types, methods, and global variables // in the given package. func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package) { // Sort by position, so that the order of the functions in the IR matches // the order of functions in the source file. This is useful for testing, // for example. var members []string for name := range pkg.Members { members = append(members, name) } sort.Slice(members, func(i, j int) bool { iPos := pkg.Members[members[i]].Pos() jPos := pkg.Members[members[j]].Pos() if i == j { // Cannot sort by pos, so do it by name. return members[i] < members[j] } return iPos < jPos }) // Define all functions. for _, name := range members { member := pkg.Members[name] switch member := member.(type) { case *ssa.Function: if member.TypeParams() != nil { // Do not try to build generic (non-instantiated) functions. continue } // Create the function definition. b := newBuilder(c, irbuilder, member) if _, ok := mathToLLVMMapping[member.RelString(nil)]; ok { // The body of this function (if there is one) is ignored and // replaced with a LLVM intrinsic call. b.defineMathOp() continue } if ok := b.defineMathBitsIntrinsic(); ok { // Like a math intrinsic, the body of this function was replaced // with a LLVM intrinsic. continue } if member.Blocks == nil { // Try to define this as an intrinsic function. b.defineIntrinsicFunction() // It might not be an intrinsic function but simply an external // function (defined via //go:linkname). Leave it undefined in // that case. continue } b.createFunction() case *ssa.Type: if types.IsInterface(member.Type()) { // Interfaces don't have concrete methods. continue } if _, isalias := member.Type().(*types.Alias); isalias { // Aliases don't need to be redefined, since they just refer to // an already existing type whose methods will be defined. continue } // Named type. We should make sure all methods are created. // This includes both functions with pointer receivers and those // without. methods := getAllMethods(pkg.Prog, member.Type()) methods = append(methods, getAllMethods(pkg.Prog, types.NewPointer(member.Type()))...) for _, method := range methods { // Parse this method. fn := pkg.Prog.MethodValue(method) if fn == nil { continue // probably a generic method } if member.Type().String() != member.String() { // This is a member on a type alias. Do not build such a // function. continue } if fn.Blocks == nil { continue // external function } if fn.Synthetic != "" && fn.Synthetic != "package initializer" { // This function is a kind of wrapper function (created by // the ssa package, not appearing in the source code) that // is created by the getFunction method as needed. // Therefore, don't build it here to avoid "function // redeclared" errors. continue } // Create the function definition. b := newBuilder(c, irbuilder, fn) b.createFunction() } case *ssa.Global: // Global variable. info := c.getGlobalInfo(member) global := c.getGlobal(member) if files, ok := c.embedGlobals[member.Name()]; ok { c.createEmbedGlobal(member, global, files) } else if !info.extern { global.SetInitializer(llvm.ConstNull(global.GlobalValueType())) global.SetVisibility(llvm.HiddenVisibility) if info.section != "" { global.SetSection(info.section) } } } } // Add forwarding functions for functions that would otherwise be // implemented in assembly. for _, name := range members { member := pkg.Members[name] switch member := member.(type) { case *ssa.Function: if member.Blocks != nil { continue // external function } info := c.getFunctionInfo(member) if aliasName, ok := stdlibAliases[info.linkName]; ok { alias := c.mod.NamedFunction(aliasName) if alias.IsNil() { // Shouldn't happen, but perhaps best to just ignore. // The error will be a link error, if there is an error. continue } b := newBuilder(c, irbuilder, member) b.createAlias(alias) } } } } // createEmbedGlobal creates an initializer for a //go:embed global variable. func (c *compilerContext) createEmbedGlobal(member *ssa.Global, global llvm.Value, files []*loader.EmbedFile) { switch typ := member.Type().(*types.Pointer).Elem().Underlying().(type) { case *types.Basic: // String type. if typ.Kind() != types.String { // This is checked at the AST level, so should be unreachable. panic("expected a string type") } if len(files) != 1 { c.addError(member.Pos(), fmt.Sprintf("//go:embed for a string should be given exactly one file, got %d", len(files))) return } strObj := c.getEmbedFileString(files[0]) global.SetInitializer(strObj) global.SetVisibility(llvm.HiddenVisibility) case *types.Slice: if typ.Elem().Underlying().(*types.Basic).Kind() != types.Byte { // This is checked at the AST level, so should be unreachable. panic("expected a byte slice") } if len(files) != 1 { c.addError(member.Pos(), fmt.Sprintf("//go:embed for a string should be given exactly one file, got %d", len(files))) return } file := files[0] bufferValue := c.ctx.ConstString(string(file.Data), false) bufferGlobal := llvm.AddGlobal(c.mod, bufferValue.Type(), c.pkg.Path()+"$embedslice") bufferGlobal.SetInitializer(bufferValue) bufferGlobal.SetLinkage(llvm.InternalLinkage) bufferGlobal.SetAlignment(1) slicePtr := llvm.ConstInBoundsGEP(bufferValue.Type(), bufferGlobal, []llvm.Value{ llvm.ConstInt(c.uintptrType, 0, false), llvm.ConstInt(c.uintptrType, 0, false), }) sliceLen := llvm.ConstInt(c.uintptrType, file.Size, false) sliceObj := c.ctx.ConstStruct([]llvm.Value{slicePtr, sliceLen, sliceLen}, false) global.SetInitializer(sliceObj) global.SetVisibility(llvm.HiddenVisibility) if c.Debug { // Add debug info to the slice backing array. position := c.program.Fset.Position(member.Pos()) diglobal := c.dibuilder.CreateGlobalVariableExpression(llvm.Metadata{}, llvm.DIGlobalVariableExpression{ File: c.getDIFile(position.Filename), Line: position.Line, Type: c.getDIType(types.NewArray(types.Typ[types.Byte], int64(len(file.Data)))), LocalToUnit: true, Expr: c.dibuilder.CreateExpression(nil), }) bufferGlobal.AddMetadata(0, diglobal) } case *types.Struct: // Assume this is an embed.FS struct: // https://cs.opensource.google/go/go/+/refs/tags/go1.18.2:src/embed/embed.go;l=148 // It looks like this: // type FS struct { // files *file // } // Make a slice of the files, as they will appear in the binary. They // are sorted in a special way to allow for binary searches, see // src/embed/embed.go for details. dirset := map[string]struct{}{} var allFiles []*loader.EmbedFile for _, file := range files { allFiles = append(allFiles, file) dirname := file.Name for { dirname, _ = path.Split(path.Clean(dirname)) if dirname == "" { break } if _, ok := dirset[dirname]; ok { break } dirset[dirname] = struct{}{} allFiles = append(allFiles, &loader.EmbedFile{ Name: dirname, }) } } sort.Slice(allFiles, func(i, j int) bool { dir1, name1 := path.Split(path.Clean(allFiles[i].Name)) dir2, name2 := path.Split(path.Clean(allFiles[j].Name)) if dir1 != dir2 { return dir1 < dir2 } return name1 < name2 }) // Make the backing array for the []files slice. This is a LLVM global. embedFileStructType := typ.Field(0).Type().(*types.Pointer).Elem().(*types.Slice).Elem() llvmEmbedFileStructType := c.getLLVMType(embedFileStructType) var fileStructs []llvm.Value for _, file := range allFiles { fileStruct := llvm.ConstNull(llvmEmbedFileStructType) name := c.createConst(ssa.NewConst(constant.MakeString(file.Name), types.Typ[types.String]), getPos(member)) fileStruct = c.builder.CreateInsertValue(fileStruct, name, 0, "") // "name" field if file.Hash != "" { data := c.getEmbedFileString(file) fileStruct = c.builder.CreateInsertValue(fileStruct, data, 1, "") // "data" field } fileStructs = append(fileStructs, fileStruct) } sliceDataInitializer := llvm.ConstArray(llvmEmbedFileStructType, fileStructs) sliceDataGlobal := llvm.AddGlobal(c.mod, sliceDataInitializer.Type(), c.pkg.Path()+"$embedfsfiles") sliceDataGlobal.SetInitializer(sliceDataInitializer) sliceDataGlobal.SetLinkage(llvm.InternalLinkage) sliceDataGlobal.SetGlobalConstant(true) sliceDataGlobal.SetUnnamedAddr(true) sliceDataGlobal.SetAlignment(c.targetData.ABITypeAlignment(sliceDataInitializer.Type())) if c.Debug { // Add debug information for code size attribution (among others). position := c.program.Fset.Position(member.Pos()) diglobal := c.dibuilder.CreateGlobalVariableExpression(llvm.Metadata{}, llvm.DIGlobalVariableExpression{ File: c.getDIFile(position.Filename), Line: position.Line, Type: c.getDIType(types.NewArray(embedFileStructType, int64(len(allFiles)))), LocalToUnit: true, Expr: c.dibuilder.CreateExpression(nil), }) sliceDataGlobal.AddMetadata(0, diglobal) } // Create the slice object itself. // Because embed.FS refers to it as *[]embed.file instead of a plain // []embed.file, we have to store this as a global. slicePtr := llvm.ConstInBoundsGEP(sliceDataInitializer.Type(), sliceDataGlobal, []llvm.Value{ llvm.ConstInt(c.uintptrType, 0, false), llvm.ConstInt(c.uintptrType, 0, false), }) sliceLen := llvm.ConstInt(c.uintptrType, uint64(len(fileStructs)), false) sliceInitializer := c.ctx.ConstStruct([]llvm.Value{slicePtr, sliceLen, sliceLen}, false) sliceGlobal := llvm.AddGlobal(c.mod, sliceInitializer.Type(), c.pkg.Path()+"$embedfsslice") sliceGlobal.SetInitializer(sliceInitializer) sliceGlobal.SetLinkage(llvm.InternalLinkage) sliceGlobal.SetGlobalConstant(true) sliceGlobal.SetUnnamedAddr(true) sliceGlobal.SetAlignment(c.targetData.ABITypeAlignment(sliceInitializer.Type())) if c.Debug { position := c.program.Fset.Position(member.Pos()) diglobal := c.dibuilder.CreateGlobalVariableExpression(llvm.Metadata{}, llvm.DIGlobalVariableExpression{ File: c.getDIFile(position.Filename), Line: position.Line, Type: c.getDIType(types.NewSlice(embedFileStructType)), LocalToUnit: true, Expr: c.dibuilder.CreateExpression(nil), }) sliceGlobal.AddMetadata(0, diglobal) } // Define the embed.FS struct. It has only one field: the files (as a // *[]embed.file). globalInitializer := llvm.ConstNull(c.getLLVMType(member.Type().(*types.Pointer).Elem())) globalInitializer = c.builder.CreateInsertValue(globalInitializer, sliceGlobal, 0, "") global.SetInitializer(globalInitializer) global.SetVisibility(llvm.HiddenVisibility) global.SetAlignment(c.targetData.ABITypeAlignment(globalInitializer.Type())) } } // getEmbedFileString returns the (constant) string object with the contents of // the given file. This is a llvm.Value of a regular Go string. func (c *compilerContext) getEmbedFileString(file *loader.EmbedFile) llvm.Value { dataGlobalName := "embed/file_" + file.Hash dataGlobal := c.mod.NamedGlobal(dataGlobalName) dataGlobalType := llvm.ArrayType(c.ctx.Int8Type(), int(file.Size)) if dataGlobal.IsNil() { dataGlobal = llvm.AddGlobal(c.mod, dataGlobalType, dataGlobalName) } strPtr := llvm.ConstInBoundsGEP(dataGlobalType, dataGlobal, []llvm.Value{ llvm.ConstInt(c.uintptrType, 0, false), llvm.ConstInt(c.uintptrType, 0, false), }) strLen := llvm.ConstInt(c.uintptrType, file.Size, false) return llvm.ConstNamedStruct(c.getLLVMRuntimeType("_string"), []llvm.Value{strPtr, strLen}) } // Start defining a function so that it can be filled with instructions: load // parameters, create basic blocks, and set up debug information. // This is separated out from createFunction() so that it is also usable to // define compiler intrinsics like the atomic operations in sync/atomic. func (b *builder) createFunctionStart(intrinsic bool) { if b.DumpSSA { fmt.Printf("\nfunc %s:\n", b.fn) } if !b.llvmFn.IsDeclaration() { errValue := b.llvmFn.Name() + " redeclared in this program" fnPos := getPosition(b.llvmFn) if fnPos.IsValid() { errValue += "\n\tprevious declaration at " + fnPos.String() } b.addError(b.fn.Pos(), errValue) return } b.addStandardDefinedAttributes(b.llvmFn) if !b.info.exported { // Do not set visibility for local linkage (internal or private). // Otherwise a "local linkage requires default visibility" // assertion error in llvm-project/llvm/include/llvm/IR/GlobalValue.h:236 // is thrown. if b.llvmFn.Linkage() != llvm.InternalLinkage && b.llvmFn.Linkage() != llvm.PrivateLinkage { b.llvmFn.SetVisibility(llvm.HiddenVisibility) } b.llvmFn.SetUnnamedAddr(true) } if b.info.section != "" { b.llvmFn.SetSection(b.info.section) } if b.info.exported && strings.HasPrefix(b.Triple, "wasm") { // Set the exported name. This is necessary for WebAssembly because // otherwise the function is not exported. functionAttr := b.ctx.CreateStringAttribute("wasm-export-name", b.info.linkName) b.llvmFn.AddFunctionAttr(functionAttr) // Unlike most targets, exported functions are actually visible in // WebAssembly (even if it's not called from within the WebAssembly // module). But LTO generally optimizes such functions away. Therefore, // exported functions must be explicitly marked as used. llvmutil.AppendToGlobal(b.mod, "llvm.used", b.llvmFn) } // Some functions have a pragma controlling the inlining level. switch b.info.inline { case inlineHint: // Add LLVM inline hint to functions with //go:inline pragma. inline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("inlinehint"), 0) b.llvmFn.AddFunctionAttr(inline) case inlineNone: // Add LLVM attribute to always avoid inlining this function. noinline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("noinline"), 0) b.llvmFn.AddFunctionAttr(noinline) } if b.info.interrupt { // Mark this function as an interrupt. // This is necessary on MCUs that don't push caller saved registers when // entering an interrupt, such as on AVR. if strings.HasPrefix(b.Triple, "avr") { b.llvmFn.AddFunctionAttr(b.ctx.CreateStringAttribute("signal", "")) } else { b.addError(b.fn.Pos(), "//go:interrupt not supported on this architecture") } } // Add debug info, if needed. if b.Debug { if b.fn.Synthetic == "package initializer" { // Package initializer functions have no debug info. Create some // fake debug info to at least have *something*. b.difunc = b.attachDebugInfoRaw(b.fn, b.llvmFn, "", b.packageDir, 0) } else if b.fn.Syntax() != nil { // Create debug info file if needed. b.difunc = b.attachDebugInfo(b.fn) } b.setDebugLocation(b.fn.Pos()) } // Pre-create all basic blocks in the function. var entryBlock llvm.BasicBlock if intrinsic { // This function isn't defined in Go SSA. It is probably a compiler // intrinsic (like an atomic operation). Create the entry block // manually. entryBlock = b.ctx.AddBasicBlock(b.llvmFn, "entry") // Intrinsics may create internal branches (e.g. nil checks). // They will attempt to access b.currentBlockInfo to update the exit block. // Create some fake block info for them to access. blockInfo := []blockInfo{ { entry: entryBlock, exit: entryBlock, }, } b.blockInfo = blockInfo b.currentBlockInfo = &blockInfo[0] } else { blocks := b.fn.Blocks blockInfo := make([]blockInfo, len(blocks)) for _, block := range b.fn.DomPreorder() { info := &blockInfo[block.Index] llvmBlock := b.ctx.AddBasicBlock(b.llvmFn, block.Comment) info.entry = llvmBlock info.exit = llvmBlock } b.blockInfo = blockInfo // Normal functions have an entry block. entryBlock = blockInfo[0].entry } b.SetInsertPointAtEnd(entryBlock) if b.fn.Synthetic == "package initializer" { b.initPseudoFuncs = make(map[string]llvm.Metadata) // Create a fake 'inlined at' metadata node. // See setDebugLocation for details. alloca := b.CreateAlloca(b.uintptrType, "") b.initInlinedAt = alloca.InstructionDebugLoc() alloca.EraseFromParentAsInstruction() } // Load function parameters llvmParamIndex := 0 for _, param := range b.fn.Params { llvmType := b.getLLVMType(param.Type()) fields := make([]llvm.Value, 0, 1) for _, info := range b.expandFormalParamType(llvmType, param.Name(), param.Type()) { param := b.llvmFn.Param(llvmParamIndex) param.SetName(info.name) fields = append(fields, param) llvmParamIndex++ } b.locals[param] = b.collapseFormalParam(llvmType, fields) // Add debug information to this parameter (if available) if b.Debug && b.fn.Syntax() != nil { dbgParam := b.getLocalVariable(param.Object().(*types.Var)) loc := b.GetCurrentDebugLocation() if len(fields) == 1 { expr := b.dibuilder.CreateExpression(nil) b.dibuilder.InsertValueAtEnd(fields[0], dbgParam, expr, loc, entryBlock) } else { fieldOffsets := b.expandFormalParamOffsets(llvmType) for i, field := range fields { expr := b.dibuilder.CreateExpression([]uint64{ 0x1000, // DW_OP_LLVM_fragment fieldOffsets[i] * 8, // offset in bits b.targetData.TypeAllocSize(field.Type()) * 8, // size in bits }) b.dibuilder.InsertValueAtEnd(field, dbgParam, expr, loc, entryBlock) } } } } // Load free variables from the context. This is a closure (or bound // method). var context llvm.Value if !b.info.exported { context = b.llvmFn.LastParam() context.SetName("context") } if len(b.fn.FreeVars) != 0 { // Get a list of all variable types in the context. freeVarTypes := make([]llvm.Type, len(b.fn.FreeVars)) for i, freeVar := range b.fn.FreeVars { freeVarTypes[i] = b.getLLVMType(freeVar.Type()) } // Load each free variable from the context pointer. // A free variable is always a pointer when this is a closure, but it // can be another type when it is a wrapper for a bound method (these // wrappers are generated by the ssa package). for i, val := range b.emitPointerUnpack(context, freeVarTypes) { b.locals[b.fn.FreeVars[i]] = val } } if b.fn.Recover != nil { // This function has deferred function calls. Set some things up for // them. b.deferInitFunc() } if b.NeedsStackObjects { // Create a dummy alloca that will be used in runtime.trackPointer. // It is necessary to pass a dummy alloca to runtime.trackPointer // because runtime.trackPointer is replaced by an alloca store. b.stackChainAlloca = b.CreateAlloca(b.ctx.Int8Type(), "stackalloc") } } // createFunction builds the LLVM IR implementation for this function. The // function must not yet be defined, otherwise this function will create a // diagnostic. func (b *builder) createFunction() { b.createFunctionStart(false) // Fill blocks with instructions. for _, block := range b.fn.DomPreorder() { if b.DumpSSA { fmt.Printf("%d: %s:\n", block.Index, block.Comment) } b.currentBlock = block b.currentBlockInfo = &b.blockInfo[block.Index] b.SetInsertPointAtEnd(b.currentBlockInfo.entry) for _, instr := range block.Instrs { if instr, ok := instr.(*ssa.DebugRef); ok { if !b.Debug { continue } object := instr.Object() variable, ok := object.(*types.Var) if !ok { // Not a local variable. continue } if instr.IsAddr { // TODO, this may happen for *ssa.Alloc and *ssa.FieldAddr // for example. continue } dbgVar := b.getLocalVariable(variable) pos := b.program.Fset.Position(instr.Pos()) b.dibuilder.InsertValueAtEnd(b.getValue(instr.X, getPos(instr)), dbgVar, b.dibuilder.CreateExpression(nil), llvm.DebugLoc{ Line: uint(pos.Line), Col: uint(pos.Column), Scope: b.difunc, }, b.GetInsertBlock()) continue } if b.DumpSSA { if val, ok := instr.(ssa.Value); ok && val.Name() != "" { fmt.Printf("\t%s = %s\n", val.Name(), val.String()) } else { fmt.Printf("\t%s\n", instr.String()) } } b.createInstruction(instr) } if b.fn.Name() == "init" && len(block.Instrs) == 0 { b.CreateRetVoid() } } // The rundefers instruction needs to be created after all defer // instructions have been created. Otherwise it won't handle all defer // cases. for i, bb := range b.runDefersBlock { b.SetInsertPointAtEnd(bb) b.createRunDefers() b.CreateBr(b.afterDefersBlock[i]) } if b.hasDeferFrame() { // Create the landing pad block, where execution continues after a // panic. b.createLandingPad() } // Resolve phi nodes for _, phi := range b.phis { block := phi.ssa.Block() for i, edge := range phi.ssa.Edges { llvmVal := b.getValue(edge, getPos(phi.ssa)) llvmBlock := b.blockInfo[block.Preds[i].Index].exit phi.llvm.AddIncoming([]llvm.Value{llvmVal}, []llvm.BasicBlock{llvmBlock}) } } if b.NeedsStackObjects { // Track phi nodes. for _, phi := range b.phis { insertPoint := llvm.NextInstruction(phi.llvm) for !insertPoint.IsAPHINode().IsNil() { insertPoint = llvm.NextInstruction(insertPoint) } b.SetInsertPointBefore(insertPoint) b.trackValue(phi.llvm) } } // Create anonymous functions (closures etc.). for _, sub := range b.fn.AnonFuncs { b := newBuilder(b.compilerContext, b.Builder, sub) b.llvmFn.SetLinkage(llvm.InternalLinkage) b.createFunction() } // Create wrapper function that can be called externally. if b.info.wasmExport != "" { b.createWasmExport() } } // posser is an interface that's implemented by both ssa.Value and // ssa.Instruction. It is implemented by everything that has a Pos() method, // which is all that getPos() needs. type posser interface { Pos() token.Pos } // getPos returns position information for a ssa.Value or ssa.Instruction. // // Not all instructions have position information, especially when they're // implicit (such as implicit casts or implicit returns at the end of a // function). In these cases, it makes sense to try a bit harder to guess what // the position really should be. func getPos(val posser) token.Pos { pos := val.Pos() if pos != token.NoPos { // Easy: position is known. return pos } // No position information is known. switch val := val.(type) { case *ssa.MakeInterface: return getPos(val.X) case *ssa.MakeClosure: return val.Fn.(*ssa.Function).Pos() case *ssa.Return: syntax := val.Parent().Syntax() if syntax != nil { // non-synthetic return syntax.End() } return token.NoPos case *ssa.FieldAddr: return getPos(val.X) case *ssa.IndexAddr: return getPos(val.X) case *ssa.Slice: return getPos(val.X) case *ssa.Store: return getPos(val.Addr) case *ssa.Extract: return getPos(val.Tuple) default: // This is reachable, for example with *ssa.Const, *ssa.If, and // *ssa.Jump. They might be implemented in some way in the future. return token.NoPos } } // createInstruction builds the LLVM IR equivalent instructions for the // particular Go SSA instruction. func (b *builder) createInstruction(instr ssa.Instruction) { if b.Debug { b.setDebugLocation(getPos(instr)) } switch instr := instr.(type) { case ssa.Value: if value, err := b.createExpr(instr); err != nil { // This expression could not be parsed. Add the error to the list // of diagnostics and continue with an undef value. // The resulting IR will be incorrect (but valid). However, // compilation can proceed which is useful because there may be // more compilation errors which can then all be shown together to // the user. b.diagnostics = append(b.diagnostics, err) b.locals[instr] = llvm.Undef(b.getLLVMType(instr.Type())) } else { b.locals[instr] = value if len(*instr.Referrers()) != 0 && b.NeedsStackObjects { b.trackExpr(instr, value) } } case *ssa.DebugRef: // ignore case *ssa.Defer: b.createDefer(instr) case *ssa.Go: // Start a new goroutine. b.createGo(instr) case *ssa.If: cond := b.getValue(instr.Cond, getPos(instr)) block := instr.Block() blockThen := b.blockInfo[block.Succs[0].Index].entry blockElse := b.blockInfo[block.Succs[1].Index].entry b.CreateCondBr(cond, blockThen, blockElse) case *ssa.Jump: blockJump := b.blockInfo[instr.Block().Succs[0].Index].entry b.CreateBr(blockJump) case *ssa.MapUpdate: m := b.getValue(instr.Map, getPos(instr)) key := b.getValue(instr.Key, getPos(instr)) value := b.getValue(instr.Value, getPos(instr)) mapType := instr.Map.Type().Underlying().(*types.Map) b.createMapUpdate(mapType.Key(), m, key, value, instr.Pos()) case *ssa.Panic: value := b.getValue(instr.X, getPos(instr)) b.createRuntimeInvoke("_panic", []llvm.Value{value}, "") b.CreateUnreachable() case *ssa.Return: if b.hasDeferFrame() { b.createRuntimeCall("destroyDeferFrame", []llvm.Value{b.deferFrame}, "") } if len(instr.Results) == 0 { b.CreateRetVoid() } else if len(instr.Results) == 1 { b.CreateRet(b.getValue(instr.Results[0], getPos(instr))) } else { // Multiple return values. Put them all in a struct. retVal := llvm.ConstNull(b.llvmFn.GlobalValueType().ReturnType()) for i, result := range instr.Results { val := b.getValue(result, getPos(instr)) retVal = b.CreateInsertValue(retVal, val, i, "") } b.CreateRet(retVal) } case *ssa.RunDefers: // Note where we're going to put the rundefers block run := b.insertBasicBlock("rundefers.block") b.CreateBr(run) b.runDefersBlock = append(b.runDefersBlock, run) after := b.insertBasicBlock("rundefers.after") b.SetInsertPointAtEnd(after) b.afterDefersBlock = append(b.afterDefersBlock, after) case *ssa.Send: b.createChanSend(instr) case *ssa.Store: llvmAddr := b.getValue(instr.Addr, getPos(instr)) llvmVal := b.getValue(instr.Val, getPos(instr)) b.createNilCheck(instr.Addr, llvmAddr, "store") if b.targetData.TypeAllocSize(llvmVal.Type()) == 0 { // nothing to store return } b.CreateStore(llvmVal, llvmAddr) default: b.addError(instr.Pos(), "unknown instruction: "+instr.String()) } } // createBuiltin lowers a builtin Go function (append, close, delete, etc.) to // LLVM IR. It uses runtime calls for some builtins. func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, callName string, pos token.Pos) (llvm.Value, error) { switch callName { case "append": src := argValues[0] elems := argValues[1] srcBuf := b.CreateExtractValue(src, 0, "append.srcBuf") srcLen := b.CreateExtractValue(src, 1, "append.srcLen") srcCap := b.CreateExtractValue(src, 2, "append.srcCap") elemsBuf := b.CreateExtractValue(elems, 0, "append.elemsBuf") elemsLen := b.CreateExtractValue(elems, 1, "append.elemsLen") elemType := b.getLLVMType(argTypes[0].Underlying().(*types.Slice).Elem()) elemSize := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(elemType), false) result := b.createRuntimeCall("sliceAppend", []llvm.Value{srcBuf, elemsBuf, srcLen, srcCap, elemsLen, elemSize}, "append.new") newPtr := b.CreateExtractValue(result, 0, "append.newPtr") newLen := b.CreateExtractValue(result, 1, "append.newLen") newCap := b.CreateExtractValue(result, 2, "append.newCap") newSlice := llvm.Undef(src.Type()) newSlice = b.CreateInsertValue(newSlice, newPtr, 0, "") newSlice = b.CreateInsertValue(newSlice, newLen, 1, "") newSlice = b.CreateInsertValue(newSlice, newCap, 2, "") return newSlice, nil case "cap": value := argValues[0] var llvmCap llvm.Value switch argTypes[0].Underlying().(type) { case *types.Chan: llvmCap = b.createRuntimeCall("chanCap", []llvm.Value{value}, "cap") case *types.Slice: llvmCap = b.CreateExtractValue(value, 2, "cap") default: return llvm.Value{}, b.makeError(pos, "todo: cap: unknown type") } if b.targetData.TypeAllocSize(llvmCap.Type()) < b.targetData.TypeAllocSize(b.intType) { llvmCap = b.CreateZExt(llvmCap, b.intType, "len.int") } return llvmCap, nil case "close": b.createChanClose(argValues[0]) return llvm.Value{}, nil case "complex": r := argValues[0] i := argValues[1] t := argTypes[0].Underlying().(*types.Basic) var cplx llvm.Value switch t.Kind() { case types.Float32: cplx = llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.FloatType(), b.ctx.FloatType()}, false)) case types.Float64: cplx = llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.DoubleType(), b.ctx.DoubleType()}, false)) default: return llvm.Value{}, b.makeError(pos, "unsupported type in complex builtin: "+t.String()) } cplx = b.CreateInsertValue(cplx, r, 0, "") cplx = b.CreateInsertValue(cplx, i, 1, "") return cplx, nil case "clear": value := argValues[0] switch typ := argTypes[0].Underlying().(type) { case *types.Slice: elementType := b.getLLVMType(typ.Elem()) elementSize := b.targetData.TypeAllocSize(elementType) elementAlign := b.targetData.ABITypeAlignment(elementType) // The pointer to the data to be cleared. llvmBuf := b.CreateExtractValue(value, 0, "buf") // The length (in bytes) to be cleared. llvmLen := b.CreateExtractValue(value, 1, "len") llvmLen = b.CreateMul(llvmLen, llvm.ConstInt(llvmLen.Type(), elementSize, false), "") // Do the clear operation using the LLVM memset builtin. // This is also correct for nil slices: in those cases, len will be // 0 which means the memset call is a no-op (according to the LLVM // LangRef). memset := b.getMemsetFunc() call := b.createCall(memset.GlobalValueType(), memset, []llvm.Value{ llvmBuf, // dest llvm.ConstInt(b.ctx.Int8Type(), 0, false), // val llvmLen, // len llvm.ConstInt(b.ctx.Int1Type(), 0, false), // isVolatile }, "") call.AddCallSiteAttribute(1, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(elementAlign))) return llvm.Value{}, nil case *types.Map: m := argValues[0] b.createMapClear(m) return llvm.Value{}, nil default: return llvm.Value{}, b.makeError(pos, "unsupported type in clear builtin: "+typ.String()) } case "copy": dst := argValues[0] src := argValues[1] dstLen := b.CreateExtractValue(dst, 1, "copy.dstLen") srcLen := b.CreateExtractValue(src, 1, "copy.srcLen") dstBuf := b.CreateExtractValue(dst, 0, "copy.dstArray") srcBuf := b.CreateExtractValue(src, 0, "copy.srcArray") elemType := b.getLLVMType(argTypes[0].Underlying().(*types.Slice).Elem()) elemSize := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(elemType), false) return b.createRuntimeCall("sliceCopy", []llvm.Value{dstBuf, srcBuf, dstLen, srcLen, elemSize}, "copy.n"), nil case "delete": m := argValues[0] key := argValues[1] return llvm.Value{}, b.createMapDelete(argTypes[1], m, key, pos) case "imag": cplx := argValues[0] return b.CreateExtractValue(cplx, 1, "imag"), nil case "len": value := argValues[0] var llvmLen llvm.Value switch argTypes[0].Underlying().(type) { case *types.Basic, *types.Slice: // string or slice llvmLen = b.CreateExtractValue(value, 1, "len") case *types.Chan: llvmLen = b.createRuntimeCall("chanLen", []llvm.Value{value}, "len") case *types.Map: llvmLen = b.createRuntimeCall("hashmapLen", []llvm.Value{value}, "len") default: return llvm.Value{}, b.makeError(pos, "todo: len: unknown type") } if b.targetData.TypeAllocSize(llvmLen.Type()) < b.targetData.TypeAllocSize(b.intType) { llvmLen = b.CreateZExt(llvmLen, b.intType, "len.int") } return llvmLen, nil case "min", "max": // min and max builtins, added in Go 1.21. // We can simply reuse the existing binop comparison code, which has all // the edge cases figured out already. tok := token.LSS if callName == "max" { tok = token.GTR } result := argValues[0] typ := argTypes[0] for _, arg := range argValues[1:] { cmp, err := b.createBinOp(tok, typ, typ, result, arg, pos) if err != nil { return result, err } result = b.CreateSelect(cmp, result, arg, "") } return result, nil case "panic": // This is rare, but happens in "defer panic()". b.createRuntimeInvoke("_panic", argValues, "") return llvm.Value{}, nil case "print", "println": b.createRuntimeCall("printlock", nil, "") for i, value := range argValues { if i >= 1 && callName == "println" { b.createRuntimeCall("printspace", nil, "") } typ := argTypes[i].Underlying() switch typ := typ.(type) { case *types.Basic: switch typ.Kind() { case types.String, types.UntypedString: b.createRuntimeCall("printstring", []llvm.Value{value}, "") case types.Uintptr: b.createRuntimeCall("printptr", []llvm.Value{value}, "") case types.UnsafePointer: ptrValue := b.CreatePtrToInt(value, b.uintptrType, "") b.createRuntimeCall("printptr", []llvm.Value{ptrValue}, "") default: // runtime.print{int,uint}{8,16,32,64} if typ.Info()&types.IsInteger != 0 { name := "print" if typ.Info()&types.IsUnsigned != 0 { name += "uint" } else { name += "int" } name += strconv.FormatUint(b.targetData.TypeAllocSize(value.Type())*8, 10) b.createRuntimeCall(name, []llvm.Value{value}, "") } else if typ.Kind() == types.Bool { b.createRuntimeCall("printbool", []llvm.Value{value}, "") } else if typ.Kind() == types.Float32 { b.createRuntimeCall("printfloat32", []llvm.Value{value}, "") } else if typ.Kind() == types.Float64 { b.createRuntimeCall("printfloat64", []llvm.Value{value}, "") } else if typ.Kind() == types.Complex64 { b.createRuntimeCall("printcomplex64", []llvm.Value{value}, "") } else if typ.Kind() == types.Complex128 { b.createRuntimeCall("printcomplex128", []llvm.Value{value}, "") } else { return llvm.Value{}, b.makeError(pos, "unknown basic arg type: "+typ.String()) } } case *types.Interface: b.createRuntimeCall("printitf", []llvm.Value{value}, "") case *types.Map: b.createRuntimeCall("printmap", []llvm.Value{value}, "") case *types.Pointer: ptrValue := b.CreatePtrToInt(value, b.uintptrType, "") b.createRuntimeCall("printptr", []llvm.Value{ptrValue}, "") case *types.Slice: bufptr := b.CreateExtractValue(value, 0, "") buflen := b.CreateExtractValue(value, 1, "") bufcap := b.CreateExtractValue(value, 2, "") ptrValue := b.CreatePtrToInt(bufptr, b.uintptrType, "") b.createRuntimeCall("printslice", []llvm.Value{ptrValue, buflen, bufcap}, "") default: return llvm.Value{}, b.makeError(pos, "unknown arg type: "+typ.String()) } } if callName == "println" { b.createRuntimeCall("printnl", nil, "") } b.createRuntimeCall("printunlock", nil, "") return llvm.Value{}, nil // print() or println() returns void case "real": cplx := argValues[0] return b.CreateExtractValue(cplx, 0, "real"), nil case "recover": useParentFrame := uint64(0) if b.hasDeferFrame() { // recover() should return the panic value of the parent function, // not of the current function. useParentFrame = 1 } return b.createRuntimeCall("_recover", []llvm.Value{llvm.ConstInt(b.ctx.Int1Type(), useParentFrame, false)}, ""), nil case "ssa:wrapnilchk": // TODO: do an actual nil check? return argValues[0], nil // Builtins from the unsafe package. case "Add": // unsafe.Add // This is basically just a GEP operation. // Note: the pointer is always of type *i8. ptr := argValues[0] len := argValues[1] return b.CreateGEP(b.ctx.Int8Type(), ptr, []llvm.Value{len}, ""), nil case "Alignof": // unsafe.Alignof align := b.targetData.ABITypeAlignment(argValues[0].Type()) return llvm.ConstInt(b.uintptrType, uint64(align), false), nil case "Offsetof": // unsafe.Offsetof // This builtin is a bit harder to implement and may need a bit of // refactoring to work (it may be easier to implement if we have access // to the underlying Go SSA instruction). It is also rarely used: it // only applies in generic code and unsafe.Offsetof isn't very commonly // used anyway. // In other words, postpone it to some other day. return llvm.Value{}, b.makeError(pos, "todo: unsafe.Offsetof") case "Sizeof": // unsafe.Sizeof size := b.targetData.TypeAllocSize(argValues[0].Type()) return llvm.ConstInt(b.uintptrType, size, false), nil case "Slice", "String": // unsafe.Slice, unsafe.String // This creates a slice or string from a pointer and a length. // Note that the exception mentioned in the documentation (if the // pointer and length are nil, the slice is also nil) is trivially // already the case. ptr := argValues[0] len := argValues[1] var elementType llvm.Type if callName == "Slice" { elementType = b.getLLVMType(argTypes[0].Underlying().(*types.Pointer).Elem()) } else { elementType = b.ctx.Int8Type() } b.createUnsafeSliceStringCheck("unsafe."+callName, ptr, len, elementType, argTypes[1].Underlying().(*types.Basic)) if len.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() { // Too small, zero-extend len. len = b.CreateZExt(len, b.uintptrType, "") } else if len.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() { // Too big, truncate len. len = b.CreateTrunc(len, b.uintptrType, "") } if callName == "Slice" { slice := llvm.Undef(b.ctx.StructType([]llvm.Type{ ptr.Type(), b.uintptrType, b.uintptrType, }, false)) slice = b.CreateInsertValue(slice, ptr, 0, "") slice = b.CreateInsertValue(slice, len, 1, "") slice = b.CreateInsertValue(slice, len, 2, "") return slice, nil } else { str := llvm.Undef(b.getLLVMRuntimeType("_string")) str = b.CreateInsertValue(str, argValues[0], 0, "") str = b.CreateInsertValue(str, len, 1, "") return str, nil } case "SliceData", "StringData": // unsafe.SliceData, unsafe.StringData return b.CreateExtractValue(argValues[0], 0, "slice.data"), nil default: return llvm.Value{}, b.makeError(pos, "todo: builtin: "+callName) } } // createFunctionCall lowers a Go SSA call instruction (to a simple function, // closure, function pointer, builtin, method, etc.) to LLVM IR, usually a call // instruction. // // This is also where compiler intrinsics are implemented. func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) { // See if this is an intrinsic function that is handled specially. if fn := instr.StaticCallee(); fn != nil { // Direct function call, either to a named or anonymous (directly // applied) function call. If it is anonymous, it may be a closure. name := fn.RelString(nil) switch { case name == "device.Asm" || name == "device/arm.Asm" || name == "device/arm64.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm": return b.createInlineAsm(instr.Args) case name == "device.AsmFull" || name == "device/arm.AsmFull" || name == "device/arm64.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull": return b.createInlineAsmFull(instr) case strings.HasPrefix(name, "device/arm.SVCall"): return b.emitSVCall(instr.Args, getPos(instr)) case strings.HasPrefix(name, "device/arm64.SVCall"): return b.emitSV64Call(instr.Args, getPos(instr)) case strings.HasPrefix(name, "(device/riscv.CSR)."): return b.emitCSROperation(instr) case strings.HasPrefix(name, "syscall.Syscall") || strings.HasPrefix(name, "syscall.RawSyscall") || strings.HasPrefix(name, "golang.org/x/sys/unix.Syscall") || strings.HasPrefix(name, "golang.org/x/sys/unix.RawSyscall"): if b.GOOS != "darwin" { return b.createSyscall(instr) } case strings.HasPrefix(name, "syscall.rawSyscallNoError") || strings.HasPrefix(name, "golang.org/x/sys/unix.RawSyscallNoError"): return b.createRawSyscallNoError(instr) case name == "runtime.supportsRecover": supportsRecover := uint64(0) if b.supportsRecover() { supportsRecover = 1 } return llvm.ConstInt(b.ctx.Int1Type(), supportsRecover, false), nil case name == "runtime.panicStrategy": panicStrategy := map[string]uint64{ "print": tinygo.PanicStrategyPrint, "trap": tinygo.PanicStrategyTrap, }[b.Config.PanicStrategy] return llvm.ConstInt(b.ctx.Int8Type(), panicStrategy, false), nil case name == "runtime/interrupt.New": return b.createInterruptGlobal(instr) case name == "runtime.exportedFuncPtr": _, ptr := b.getFunction(instr.Args[0].(*ssa.Function)) return b.CreatePtrToInt(ptr, b.uintptrType, ""), nil case name == "(*runtime/interrupt.Checkpoint).Save": return b.createInterruptCheckpoint(instr.Args[0]), nil case name == "internal/abi.FuncPCABI0": retval := b.createDarwinFuncPCABI0Call(instr) if !retval.IsNil() { return retval, nil } } } var params []llvm.Value for _, param := range instr.Args { params = append(params, b.getValue(param, getPos(instr))) } // Try to call the function directly for trivially static calls. var callee, context llvm.Value var calleeType llvm.Type exported := false if fn := instr.StaticCallee(); fn != nil { calleeType, callee = b.getFunction(fn) info := b.getFunctionInfo(fn) if callee.IsNil() { return llvm.Value{}, b.makeError(instr.Pos(), "undefined function: "+info.linkName) } switch value := instr.Value.(type) { case *ssa.Function: // Regular function call. No context is necessary. context = llvm.Undef(b.dataPtrType) if info.variadic && len(fn.Params) == 0 { // This matches Clang, see: https://godbolt.org/z/Gqv49xKMq // Eventually we might be able to eliminate this special case // entirely. For details, see: // https://discourse.llvm.org/t/rfc-enabling-wstrict-prototypes-by-default-in-c/60521 calleeType = llvm.FunctionType(callee.GlobalValueType().ReturnType(), nil, false) } case *ssa.MakeClosure: // A call on a func value, but the callee is trivial to find. For // example: immediately applied functions. funcValue := b.getValue(value, getPos(value)) context = b.extractFuncContext(funcValue) default: panic("StaticCallee returned an unexpected value") } exported = info.exported } else if call, ok := instr.Value.(*ssa.Builtin); ok { // Builtin function (append, close, delete, etc.).) var argTypes []types.Type for _, arg := range instr.Args { argTypes = append(argTypes, arg.Type()) } return b.createBuiltin(argTypes, params, call.Name(), instr.Pos()) } else if instr.IsInvoke() { // Interface method call (aka invoke call). itf := b.getValue(instr.Value, getPos(instr)) // interface value (runtime._interface) typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode") value := b.CreateExtractValue(itf, 1, "invoke.func.value") // receiver // Prefix the params with receiver value and suffix with typecode. params = append([]llvm.Value{value}, params...) params = append(params, typecode) callee = b.getInvokeFunction(instr) calleeType = callee.GlobalValueType() context = llvm.Undef(b.dataPtrType) } else { // Function pointer. value := b.getValue(instr.Value, getPos(instr)) // This is a func value, which cannot be called directly. We have to // extract the function pointer and context first from the func value. callee, context = b.decodeFuncValue(value) calleeType = b.getLLVMFunctionType(instr.Value.Type().Underlying().(*types.Signature)) b.createNilCheck(instr.Value, callee, "fpcall") } if !exported { // This function takes a context parameter. // Add it to the end of the parameter list. params = append(params, context) } return b.createInvoke(calleeType, callee, params, ""), nil } // getValue returns the LLVM value of a constant, function value, global, or // already processed SSA expression. func (b *builder) getValue(expr ssa.Value, pos token.Pos) llvm.Value { switch expr := expr.(type) { case *ssa.Const: if pos == token.NoPos { // If the position isn't known, at least try to find in which file // it is defined. file := b.program.Fset.File(b.fn.Pos()) if file != nil { pos = file.Pos(0) } } return b.createConst(expr, pos) case *ssa.Function: if b.getFunctionInfo(expr).exported { b.addError(expr.Pos(), "cannot use an exported function as value: "+expr.String()) return llvm.Undef(b.getLLVMType(expr.Type())) } _, fn := b.getFunction(expr) return b.createFuncValue(fn, llvm.Undef(b.dataPtrType), expr.Signature) case *ssa.Global: value := b.getGlobal(expr) if value.IsNil() { b.addError(expr.Pos(), "global not found: "+expr.RelString(nil)) return llvm.Undef(b.getLLVMType(expr.Type())) } return value default: // other (local) SSA value if value, ok := b.locals[expr]; ok { return value } else { // indicates a compiler bug panic("SSA value not previously found in function: " + expr.String()) } } } // maxSliceSize determines the maximum size a slice of the given element type // can be. func (c *compilerContext) maxSliceSize(elementType llvm.Type) uint64 { // Calculate ^uintptr(0), which is the max value that fits in uintptr. maxPointerValue := llvm.ConstNot(llvm.ConstInt(c.uintptrType, 0, false)).ZExtValue() // Calculate (^uint(0))/2, which is the max value that fits in an int. maxIntegerValue := llvm.ConstNot(llvm.ConstInt(c.intType, 0, false)).ZExtValue() / 2 // Determine the maximum allowed size for a slice. The biggest possible // pointer (starting from 0) would be maxPointerValue*sizeof(elementType) so // divide by the element type to get the real maximum size. elementSize := c.targetData.TypeAllocSize(elementType) if elementSize == 0 { elementSize = 1 } maxSize := maxPointerValue / elementSize // len(slice) is an int. Make sure the length remains small enough to fit in // an int. if maxSize > maxIntegerValue { maxSize = maxIntegerValue } return maxSize } // createExpr translates a Go SSA expression to LLVM IR. This can be zero, one, // or multiple LLVM IR instructions and/or runtime calls. func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { if _, ok := b.locals[expr]; ok { // sanity check panic("instruction has already been created: " + expr.String()) } switch expr := expr.(type) { case *ssa.Alloc: typ := b.getLLVMType(expr.Type().Underlying().(*types.Pointer).Elem()) size := b.targetData.TypeAllocSize(typ) // Move all "large" allocations to the heap. if expr.Heap || size > b.MaxStackAlloc { // Calculate ^uintptr(0) maxSize := llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)).ZExtValue() if size > maxSize { // Size would be truncated if truncated to uintptr. return llvm.Value{}, b.makeError(expr.Pos(), fmt.Sprintf("value is too big (%v bytes)", size)) } sizeValue := llvm.ConstInt(b.uintptrType, size, false) layoutValue := b.createObjectLayout(typ, expr.Pos()) buf := b.createRuntimeCall("alloc", []llvm.Value{sizeValue, layoutValue}, expr.Comment) align := b.targetData.ABITypeAlignment(typ) buf.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align))) return buf, nil } else { buf := llvmutil.CreateEntryBlockAlloca(b.Builder, typ, expr.Comment) if b.targetData.TypeAllocSize(typ) != 0 { b.CreateStore(llvm.ConstNull(typ), buf) // zero-initialize var } return buf, nil } case *ssa.BinOp: x := b.getValue(expr.X, getPos(expr)) y := b.getValue(expr.Y, getPos(expr)) return b.createBinOp(expr.Op, expr.X.Type(), expr.Y.Type(), x, y, expr.Pos()) case *ssa.Call: return b.createFunctionCall(expr.Common()) case *ssa.ChangeInterface: // Do not change between interface types: always use the underlying // (concrete) type in the type number of the interface. Every method // call on an interface will do a lookup which method to call. // This is different from how the official Go compiler works, because of // heap allocation and because it's easier to implement, see: // https://research.swtch.com/interfaces return b.getValue(expr.X, getPos(expr)), nil case *ssa.ChangeType: // This instruction changes the type, but the underlying value remains // the same. This is often a no-op, but sometimes we have to change the // LLVM type as well. x := b.getValue(expr.X, getPos(expr)) llvmType := b.getLLVMType(expr.Type()) if x.Type() == llvmType { // Different Go type but same LLVM type (for example, named int). // This is the common case. return x, nil } // Figure out what kind of type we need to cast. switch llvmType.TypeKind() { case llvm.StructTypeKind: // Unfortunately, we can't just bitcast structs. We have to // actually create a new struct of the correct type and insert the // values from the previous struct in there. value := llvm.Undef(llvmType) for i := 0; i < llvmType.StructElementTypesCount(); i++ { field := b.CreateExtractValue(x, i, "changetype.field") value = b.CreateInsertValue(value, field, i, "changetype.struct") } return value, nil default: return llvm.Value{}, errors.New("todo: unknown ChangeType type: " + expr.X.Type().String()) } case *ssa.Const: panic("const is not an expression") case *ssa.Convert: x := b.getValue(expr.X, getPos(expr)) return b.createConvert(expr.X.Type(), expr.Type(), x, expr.Pos()) case *ssa.Extract: if _, ok := expr.Tuple.(*ssa.Select); ok { return b.getChanSelectResult(expr), nil } value := b.getValue(expr.Tuple, getPos(expr)) return b.CreateExtractValue(value, expr.Index, ""), nil case *ssa.Field: value := b.getValue(expr.X, getPos(expr)) result := b.CreateExtractValue(value, expr.Field, "") return result, nil case *ssa.FieldAddr: val := b.getValue(expr.X, getPos(expr)) // Check for nil pointer before calculating the address, from the spec: // > For an operand x of type T, the address operation &x generates a // > pointer of type *T to x. [...] If the evaluation of x would cause a // > run-time panic, then the evaluation of &x does too. b.createNilCheck(expr.X, val, "gep") // Do a GEP on the pointer to get the field address. indices := []llvm.Value{ llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), uint64(expr.Field), false), } elementType := b.getLLVMType(expr.X.Type().Underlying().(*types.Pointer).Elem()) return b.CreateInBoundsGEP(elementType, val, indices, ""), nil case *ssa.Function: panic("function is not an expression") case *ssa.Global: panic("global is not an expression") case *ssa.Index: collection := b.getValue(expr.X, getPos(expr)) index := b.getValue(expr.Index, getPos(expr)) switch xType := expr.X.Type().Underlying().(type) { case *types.Basic: // extract byte from string // Value type must be a string, which is a basic type. if xType.Info()&types.IsString == 0 { panic("lookup on non-string?") } // Sometimes, the index can be e.g. an uint8 or int8, and we have to // correctly extend that type for two reasons: // 1. The lookup bounds check expects an index of at least uintptr // size. // 2. getelementptr has signed operands, and therefore s[uint8(x)] // can be lowered as s[int8(x)]. That would be a bug. index = b.extendInteger(index, expr.Index.Type(), b.uintptrType) // Bounds check. length := b.CreateExtractValue(collection, 1, "len") b.createLookupBoundsCheck(length, index) // Lookup byte buf := b.CreateExtractValue(collection, 0, "") bufElemType := b.ctx.Int8Type() bufPtr := b.CreateInBoundsGEP(bufElemType, buf, []llvm.Value{index}, "") return b.CreateLoad(bufElemType, bufPtr, ""), nil case *types.Array: // extract element from array // Extend index to at least uintptr size, because getelementptr // assumes index is a signed integer. index = b.extendInteger(index, expr.Index.Type(), b.uintptrType) // Check bounds. arrayLen := llvm.ConstInt(b.uintptrType, uint64(xType.Len()), false) b.createLookupBoundsCheck(arrayLen, index) // Can't load directly from array (as index is non-constant), so // have to do it using an alloca+gep+load. arrayType := collection.Type() alloca, allocaSize := b.createTemporaryAlloca(arrayType, "index.alloca") b.CreateStore(collection, alloca) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) ptr := b.CreateInBoundsGEP(arrayType, alloca, []llvm.Value{zero, index}, "index.gep") result := b.CreateLoad(arrayType.ElementType(), ptr, "index.load") b.emitLifetimeEnd(alloca, allocaSize) return result, nil default: panic("unknown *ssa.Index type") } case *ssa.IndexAddr: val := b.getValue(expr.X, getPos(expr)) index := b.getValue(expr.Index, getPos(expr)) // Get buffer pointer and length var bufptr, buflen llvm.Value var bufType llvm.Type switch ptrTyp := expr.X.Type().Underlying().(type) { case *types.Pointer: typ := ptrTyp.Elem().Underlying() switch typ := typ.(type) { case *types.Array: bufptr = val buflen = llvm.ConstInt(b.uintptrType, uint64(typ.Len()), false) bufType = b.getLLVMType(typ) // Check for nil pointer before calculating the address, from // the spec: // > For an operand x of type T, the address operation &x // > generates a pointer of type *T to x. [...] If the // > evaluation of x would cause a run-time panic, then the // > evaluation of &x does too. b.createNilCheck(expr.X, bufptr, "gep") default: return llvm.Value{}, b.makeError(expr.Pos(), "todo: indexaddr: "+typ.String()) } case *types.Slice: bufptr = b.CreateExtractValue(val, 0, "indexaddr.ptr") buflen = b.CreateExtractValue(val, 1, "indexaddr.len") bufType = b.getLLVMType(ptrTyp.Elem()) default: return llvm.Value{}, b.makeError(expr.Pos(), "todo: indexaddr: "+ptrTyp.String()) } // Make sure index is at least the size of uintptr because getelementptr // assumes index is a signed integer. index = b.extendInteger(index, expr.Index.Type(), b.uintptrType) // Bounds check. b.createLookupBoundsCheck(buflen, index) switch expr.X.Type().Underlying().(type) { case *types.Pointer: indices := []llvm.Value{ llvm.ConstInt(b.ctx.Int32Type(), 0, false), index, } return b.CreateInBoundsGEP(bufType, bufptr, indices, ""), nil case *types.Slice: return b.CreateInBoundsGEP(bufType, bufptr, []llvm.Value{index}, ""), nil default: panic("unreachable") } case *ssa.Lookup: // map lookup value := b.getValue(expr.X, getPos(expr)) index := b.getValue(expr.Index, getPos(expr)) valueType := expr.Type() if expr.CommaOk { valueType = valueType.(*types.Tuple).At(0).Type() } return b.createMapLookup(expr.X.Type().Underlying().(*types.Map).Key(), valueType, value, index, expr.CommaOk, expr.Pos()) case *ssa.MakeChan: return b.createMakeChan(expr), nil case *ssa.MakeClosure: return b.parseMakeClosure(expr) case *ssa.MakeInterface: val := b.getValue(expr.X, getPos(expr)) return b.createMakeInterface(val, expr.X.Type(), expr.Pos()), nil case *ssa.MakeMap: return b.createMakeMap(expr) case *ssa.MakeSlice: sliceLen := b.getValue(expr.Len, getPos(expr)) sliceCap := b.getValue(expr.Cap, getPos(expr)) sliceType := expr.Type().Underlying().(*types.Slice) llvmElemType := b.getLLVMType(sliceType.Elem()) elemSize := b.targetData.TypeAllocSize(llvmElemType) elemAlign := b.targetData.ABITypeAlignment(llvmElemType) elemSizeValue := llvm.ConstInt(b.uintptrType, elemSize, false) maxSize := b.maxSliceSize(llvmElemType) if elemSize > maxSize { // This seems to be checked by the typechecker already, but let's // check it again just to be sure. return llvm.Value{}, b.makeError(expr.Pos(), fmt.Sprintf("slice element type is too big (%v bytes)", elemSize)) } // Bounds checking. lenType := expr.Len.Type().Underlying().(*types.Basic) capType := expr.Cap.Type().Underlying().(*types.Basic) maxSizeValue := llvm.ConstInt(b.uintptrType, maxSize, false) b.createSliceBoundsCheck(maxSizeValue, sliceLen, sliceCap, sliceCap, lenType, capType, capType) // Allocate the backing array. sliceCapCast, err := b.createConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos()) if err != nil { return llvm.Value{}, err } sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap") layoutValue := b.createObjectLayout(llvmElemType, expr.Pos()) slicePtr := b.createRuntimeCall("alloc", []llvm.Value{sliceSize, layoutValue}, "makeslice.buf") slicePtr.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(elemAlign))) // Extend or truncate if necessary. This is safe as we've already done // the bounds check. sliceLen, err = b.createConvert(expr.Len.Type(), types.Typ[types.Uintptr], sliceLen, expr.Pos()) if err != nil { return llvm.Value{}, err } sliceCap, err = b.createConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos()) if err != nil { return llvm.Value{}, err } // Create the slice. slice := b.ctx.ConstStruct([]llvm.Value{ llvm.Undef(slicePtr.Type()), llvm.Undef(b.uintptrType), llvm.Undef(b.uintptrType), }, false) slice = b.CreateInsertValue(slice, slicePtr, 0, "") slice = b.CreateInsertValue(slice, sliceLen, 1, "") slice = b.CreateInsertValue(slice, sliceCap, 2, "") return slice, nil case *ssa.Next: rangeVal := expr.Iter.(*ssa.Range).X llvmRangeVal := b.getValue(rangeVal, getPos(expr)) it := b.getValue(expr.Iter, getPos(expr)) if expr.IsString { return b.createRuntimeCall("stringNext", []llvm.Value{llvmRangeVal, it}, "range.next"), nil } else { // map return b.createMapIteratorNext(rangeVal, llvmRangeVal, it), nil } case *ssa.Phi: phi := b.CreatePHI(b.getLLVMType(expr.Type()), "") b.phis = append(b.phis, phiNode{expr, phi}) return phi, nil case *ssa.Range: var iteratorType llvm.Type switch typ := expr.X.Type().Underlying().(type) { case *types.Basic: // string iteratorType = b.getLLVMRuntimeType("stringIterator") case *types.Map: iteratorType = b.getLLVMRuntimeType("hashmapIterator") default: panic("unknown type in range: " + typ.String()) } it, _ := b.createTemporaryAlloca(iteratorType, "range.it") b.CreateStore(llvm.ConstNull(iteratorType), it) return it, nil case *ssa.Select: return b.createSelect(expr), nil case *ssa.Slice: value := b.getValue(expr.X, getPos(expr)) var lowType, highType, maxType *types.Basic var low, high, max llvm.Value if expr.Low != nil { lowType = expr.Low.Type().Underlying().(*types.Basic) low = b.getValue(expr.Low, getPos(expr)) low = b.extendInteger(low, lowType, b.uintptrType) } else { lowType = types.Typ[types.Uintptr] low = llvm.ConstInt(b.uintptrType, 0, false) } if expr.High != nil { highType = expr.High.Type().Underlying().(*types.Basic) high = b.getValue(expr.High, getPos(expr)) high = b.extendInteger(high, highType, b.uintptrType) } else { highType = types.Typ[types.Uintptr] } if expr.Max != nil { maxType = expr.Max.Type().Underlying().(*types.Basic) max = b.getValue(expr.Max, getPos(expr)) max = b.extendInteger(max, maxType, b.uintptrType) } else { maxType = types.Typ[types.Uintptr] } switch typ := expr.X.Type().Underlying().(type) { case *types.Pointer: // pointer to array // slice an array arrayType := typ.Elem().Underlying().(*types.Array) length := arrayType.Len() llvmLen := llvm.ConstInt(b.uintptrType, uint64(length), false) if high.IsNil() { high = llvmLen } if max.IsNil() { max = llvmLen } indices := []llvm.Value{ llvm.ConstInt(b.ctx.Int32Type(), 0, false), low, } b.createNilCheck(expr.X, value, "slice") b.createSliceBoundsCheck(llvmLen, low, high, max, lowType, highType, maxType) // Truncate ints bigger than uintptr. This is after the bounds // check so it's safe. if b.targetData.TypeAllocSize(low.Type()) > b.targetData.TypeAllocSize(b.uintptrType) { low = b.CreateTrunc(low, b.uintptrType, "") } if b.targetData.TypeAllocSize(high.Type()) > b.targetData.TypeAllocSize(b.uintptrType) { high = b.CreateTrunc(high, b.uintptrType, "") } if b.targetData.TypeAllocSize(max.Type()) > b.targetData.TypeAllocSize(b.uintptrType) { max = b.CreateTrunc(max, b.uintptrType, "") } sliceLen := b.CreateSub(high, low, "slice.len") slicePtr := b.CreateInBoundsGEP(b.getLLVMType(arrayType), value, indices, "slice.ptr") sliceCap := b.CreateSub(max, low, "slice.cap") slice := b.ctx.ConstStruct([]llvm.Value{ llvm.Undef(slicePtr.Type()), llvm.Undef(b.uintptrType), llvm.Undef(b.uintptrType), }, false) slice = b.CreateInsertValue(slice, slicePtr, 0, "") slice = b.CreateInsertValue(slice, sliceLen, 1, "") slice = b.CreateInsertValue(slice, sliceCap, 2, "") return slice, nil case *types.Slice: // slice a slice oldPtr := b.CreateExtractValue(value, 0, "") oldLen := b.CreateExtractValue(value, 1, "") oldCap := b.CreateExtractValue(value, 2, "") if high.IsNil() { high = oldLen } if max.IsNil() { max = oldCap } b.createSliceBoundsCheck(oldCap, low, high, max, lowType, highType, maxType) // Truncate ints bigger than uintptr. This is after the bounds // check so it's safe. if b.targetData.TypeAllocSize(low.Type()) > b.targetData.TypeAllocSize(b.uintptrType) { low = b.CreateTrunc(low, b.uintptrType, "") } if b.targetData.TypeAllocSize(high.Type()) > b.targetData.TypeAllocSize(b.uintptrType) { high = b.CreateTrunc(high, b.uintptrType, "") } if b.targetData.TypeAllocSize(max.Type()) > b.targetData.TypeAllocSize(b.uintptrType) { max = b.CreateTrunc(max, b.uintptrType, "") } ptrElemType := b.getLLVMType(typ.Elem()) newPtr := b.CreateInBoundsGEP(ptrElemType, oldPtr, []llvm.Value{low}, "") newLen := b.CreateSub(high, low, "") newCap := b.CreateSub(max, low, "") slice := b.ctx.ConstStruct([]llvm.Value{ llvm.Undef(newPtr.Type()), llvm.Undef(b.uintptrType), llvm.Undef(b.uintptrType), }, false) slice = b.CreateInsertValue(slice, newPtr, 0, "") slice = b.CreateInsertValue(slice, newLen, 1, "") slice = b.CreateInsertValue(slice, newCap, 2, "") return slice, nil case *types.Basic: if typ.Info()&types.IsString == 0 { return llvm.Value{}, b.makeError(expr.Pos(), "unknown slice type: "+typ.String()) } // slice a string if expr.Max != nil { // This might as well be a panic, as the frontend should have // handled this already. return llvm.Value{}, b.makeError(expr.Pos(), "slicing a string with a max parameter is not allowed by the spec") } oldPtr := b.CreateExtractValue(value, 0, "") oldLen := b.CreateExtractValue(value, 1, "") if high.IsNil() { high = oldLen } b.createSliceBoundsCheck(oldLen, low, high, high, lowType, highType, maxType) // Truncate ints bigger than uintptr. This is after the bounds // check so it's safe. if b.targetData.TypeAllocSize(low.Type()) > b.targetData.TypeAllocSize(b.uintptrType) { low = b.CreateTrunc(low, b.uintptrType, "") } if b.targetData.TypeAllocSize(high.Type()) > b.targetData.TypeAllocSize(b.uintptrType) { high = b.CreateTrunc(high, b.uintptrType, "") } newPtr := b.CreateInBoundsGEP(b.ctx.Int8Type(), oldPtr, []llvm.Value{low}, "") newLen := b.CreateSub(high, low, "") str := llvm.Undef(b.getLLVMRuntimeType("_string")) str = b.CreateInsertValue(str, newPtr, 0, "") str = b.CreateInsertValue(str, newLen, 1, "") return str, nil default: return llvm.Value{}, b.makeError(expr.Pos(), "unknown slice type: "+typ.String()) } case *ssa.SliceToArrayPointer: // Conversion from a slice to an array pointer, as the name clearly // says. This requires a runtime check to make sure the slice is at // least as big as the array. slice := b.getValue(expr.X, getPos(expr)) sliceLen := b.CreateExtractValue(slice, 1, "") arrayLen := expr.Type().Underlying().(*types.Pointer).Elem().Underlying().(*types.Array).Len() b.createSliceToArrayPointerCheck(sliceLen, arrayLen) ptr := b.CreateExtractValue(slice, 0, "") return ptr, nil case *ssa.TypeAssert: return b.createTypeAssert(expr), nil case *ssa.UnOp: return b.createUnOp(expr) default: return llvm.Value{}, b.makeError(expr.Pos(), "todo: unknown expression: "+expr.String()) } } // createBinOp creates a LLVM binary operation (add, sub, mul, etc) for a Go // binary operation. This is almost a direct mapping, but there are some subtle // differences such as the requirement in LLVM IR that both sides must have the // same type, even for bitshifts. Also, signedness in Go is encoded in the type // and is encoded in the operation in LLVM IR: this is important for some // operations such as divide. func (b *builder) createBinOp(op token.Token, typ, ytyp types.Type, x, y llvm.Value, pos token.Pos) (llvm.Value, error) { switch typ := typ.Underlying().(type) { case *types.Basic: if typ.Info()&types.IsInteger != 0 { // Operations on integers signed := typ.Info()&types.IsUnsigned == 0 switch op { case token.ADD: // + return b.CreateAdd(x, y, ""), nil case token.SUB: // - return b.CreateSub(x, y, ""), nil case token.MUL: // * return b.CreateMul(x, y, ""), nil case token.QUO, token.REM: // /, % // Check for a divide by zero. If y is zero, the Go // specification says that a runtime error must be triggered. b.createDivideByZeroCheck(y) if signed { // Deal with signed division overflow. // The LLVM LangRef says: // // Overflow also leads to undefined behavior; this is a // rare case, but can occur, for example, by doing a // 32-bit division of -2147483648 by -1. // // The Go specification however says this about division: // // The one exception to this rule is that if the dividend // x is the most negative value for the int type of x, the // quotient q = x / -1 is equal to x (and r = 0) due to // two's-complement integer overflow. // // In other words, in the special case that the lowest // possible signed integer is divided by -1, the result of // the division is the same as x (the dividend). // This is implemented by checking for this condition and // changing y to 1 if it occurs, for example for 32-bit // ints: // // if x == -2147483648 && y == -1 { // y = 1 // } // // Dividing x by 1 obviously returns x, therefore satisfying // the Go specification without a branch. llvmType := x.Type() minusOne := llvm.ConstSub(llvm.ConstInt(llvmType, 0, false), llvm.ConstInt(llvmType, 1, false)) lowestInteger := llvm.ConstInt(x.Type(), 1<<(llvmType.IntTypeWidth()-1), false) yIsMinusOne := b.CreateICmp(llvm.IntEQ, y, minusOne, "") xIsLowestInteger := b.CreateICmp(llvm.IntEQ, x, lowestInteger, "") hasOverflow := b.CreateAnd(yIsMinusOne, xIsLowestInteger, "") y = b.CreateSelect(hasOverflow, llvm.ConstInt(llvmType, 1, true), y, "") if op == token.QUO { return b.CreateSDiv(x, y, ""), nil } else { return b.CreateSRem(x, y, ""), nil } } else { if op == token.QUO { return b.CreateUDiv(x, y, ""), nil } else { return b.CreateURem(x, y, ""), nil } } case token.AND: // & return b.CreateAnd(x, y, ""), nil case token.OR: // | return b.CreateOr(x, y, ""), nil case token.XOR: // ^ return b.CreateXor(x, y, ""), nil case token.SHL, token.SHR: if ytyp.Underlying().(*types.Basic).Info()&types.IsUnsigned == 0 { // Ensure that y is not negative. b.createNegativeShiftCheck(y) } sizeX := b.targetData.TypeAllocSize(x.Type()) sizeY := b.targetData.TypeAllocSize(y.Type()) // Check if the shift is bigger than the bit-width of the shifted value. // This is UB in LLVM, so it needs to be handled separately. // The Go spec indirectly defines the result as 0. // Negative shifts are handled earlier, so we can treat y as unsigned. overshifted := b.CreateICmp(llvm.IntUGE, y, llvm.ConstInt(y.Type(), 8*sizeX, false), "shift.overflow") // Adjust the size of y to match x. switch { case sizeX > sizeY: y = b.CreateZExt(y, x.Type(), "") case sizeX < sizeY: // If it gets truncated, overshifted will be true and it will not matter. y = b.CreateTrunc(y, x.Type(), "") } // Create a shift operation. var val llvm.Value switch op { case token.SHL: // << val = b.CreateShl(x, y, "") case token.SHR: // >> if signed { // Arithmetic right shifts work differently, since shifting a negative number right yields -1. // Cap the shift input rather than selecting the output. y = b.CreateSelect(overshifted, llvm.ConstInt(y.Type(), 8*sizeX-1, false), y, "shift.offset") return b.CreateAShr(x, y, ""), nil } else { val = b.CreateLShr(x, y, "") } default: panic("unreachable") } // Select between the shift result and zero depending on whether there was an overshift. return b.CreateSelect(overshifted, llvm.ConstInt(val.Type(), 0, false), val, "shift.result"), nil case token.EQL: // == return b.CreateICmp(llvm.IntEQ, x, y, ""), nil case token.NEQ: // != return b.CreateICmp(llvm.IntNE, x, y, ""), nil case token.AND_NOT: // &^ // Go specific. Calculate "and not" with x & (~y) inv := b.CreateNot(y, "") // ~y return b.CreateAnd(x, inv, ""), nil case token.LSS: // < if signed { return b.CreateICmp(llvm.IntSLT, x, y, ""), nil } else { return b.CreateICmp(llvm.IntULT, x, y, ""), nil } case token.LEQ: // <= if signed { return b.CreateICmp(llvm.IntSLE, x, y, ""), nil } else { return b.CreateICmp(llvm.IntULE, x, y, ""), nil } case token.GTR: // > if signed { return b.CreateICmp(llvm.IntSGT, x, y, ""), nil } else { return b.CreateICmp(llvm.IntUGT, x, y, ""), nil } case token.GEQ: // >= if signed { return b.CreateICmp(llvm.IntSGE, x, y, ""), nil } else { return b.CreateICmp(llvm.IntUGE, x, y, ""), nil } default: panic("binop on integer: " + op.String()) } } else if typ.Info()&types.IsFloat != 0 { // Operations on floats switch op { case token.ADD: // + return b.CreateFAdd(x, y, ""), nil case token.SUB: // - return b.CreateFSub(x, y, ""), nil case token.MUL: // * return b.CreateFMul(x, y, ""), nil case token.QUO: // / return b.CreateFDiv(x, y, ""), nil case token.EQL: // == return b.CreateFCmp(llvm.FloatOEQ, x, y, ""), nil case token.NEQ: // != return b.CreateFCmp(llvm.FloatUNE, x, y, ""), nil case token.LSS: // < return b.CreateFCmp(llvm.FloatOLT, x, y, ""), nil case token.LEQ: // <= return b.CreateFCmp(llvm.FloatOLE, x, y, ""), nil case token.GTR: // > return b.CreateFCmp(llvm.FloatOGT, x, y, ""), nil case token.GEQ: // >= return b.CreateFCmp(llvm.FloatOGE, x, y, ""), nil default: panic("binop on float: " + op.String()) } } else if typ.Info()&types.IsComplex != 0 { r1 := b.CreateExtractValue(x, 0, "r1") r2 := b.CreateExtractValue(y, 0, "r2") i1 := b.CreateExtractValue(x, 1, "i1") i2 := b.CreateExtractValue(y, 1, "i2") switch op { case token.EQL: // == req := b.CreateFCmp(llvm.FloatOEQ, r1, r2, "") ieq := b.CreateFCmp(llvm.FloatOEQ, i1, i2, "") return b.CreateAnd(req, ieq, ""), nil case token.NEQ: // != req := b.CreateFCmp(llvm.FloatOEQ, r1, r2, "") ieq := b.CreateFCmp(llvm.FloatOEQ, i1, i2, "") neq := b.CreateAnd(req, ieq, "") return b.CreateNot(neq, ""), nil case token.ADD, token.SUB: var r, i llvm.Value switch op { case token.ADD: r = b.CreateFAdd(r1, r2, "") i = b.CreateFAdd(i1, i2, "") case token.SUB: r = b.CreateFSub(r1, r2, "") i = b.CreateFSub(i1, i2, "") default: panic("unreachable") } cplx := llvm.Undef(b.ctx.StructType([]llvm.Type{r.Type(), i.Type()}, false)) cplx = b.CreateInsertValue(cplx, r, 0, "") cplx = b.CreateInsertValue(cplx, i, 1, "") return cplx, nil case token.MUL: // Complex multiplication follows the current implementation in // the Go compiler, with the difference that complex64 // components are not first scaled up to float64 for increased // precision. // https://github.com/golang/go/blob/170b8b4b12be50eeccbcdadb8523fb4fc670ca72/src/cmd/compile/internal/gc/ssa.go#L2089-L2127 // The implementation is as follows: // r := real(a) * real(b) - imag(a) * imag(b) // i := real(a) * imag(b) + imag(a) * real(b) // Note: this does NOT follow the C11 specification (annex G): // http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1548.pdf#page=549 // See https://github.com/golang/go/issues/29846 for a related // discussion. r := b.CreateFSub(b.CreateFMul(r1, r2, ""), b.CreateFMul(i1, i2, ""), "") i := b.CreateFAdd(b.CreateFMul(r1, i2, ""), b.CreateFMul(i1, r2, ""), "") cplx := llvm.Undef(b.ctx.StructType([]llvm.Type{r.Type(), i.Type()}, false)) cplx = b.CreateInsertValue(cplx, r, 0, "") cplx = b.CreateInsertValue(cplx, i, 1, "") return cplx, nil case token.QUO: // Complex division. // Do this in a library call because it's too difficult to do // inline. switch r1.Type().TypeKind() { case llvm.FloatTypeKind: return b.createRuntimeCall("complex64div", []llvm.Value{x, y}, ""), nil case llvm.DoubleTypeKind: return b.createRuntimeCall("complex128div", []llvm.Value{x, y}, ""), nil default: panic("unexpected complex type") } default: panic("binop on complex: " + op.String()) } } else if typ.Info()&types.IsBoolean != 0 { // Operations on booleans switch op { case token.EQL: // == return b.CreateICmp(llvm.IntEQ, x, y, ""), nil case token.NEQ: // != return b.CreateICmp(llvm.IntNE, x, y, ""), nil default: panic("binop on bool: " + op.String()) } } else if typ.Kind() == types.UnsafePointer { // Operations on pointers switch op { case token.EQL: // == return b.CreateICmp(llvm.IntEQ, x, y, ""), nil case token.NEQ: // != return b.CreateICmp(llvm.IntNE, x, y, ""), nil default: panic("binop on pointer: " + op.String()) } } else if typ.Info()&types.IsString != 0 { // Operations on strings switch op { case token.ADD: // + return b.createRuntimeCall("stringConcat", []llvm.Value{x, y}, ""), nil case token.EQL: // == return b.createRuntimeCall("stringEqual", []llvm.Value{x, y}, ""), nil case token.NEQ: // != result := b.createRuntimeCall("stringEqual", []llvm.Value{x, y}, "") return b.CreateNot(result, ""), nil case token.LSS: // x < y return b.createRuntimeCall("stringLess", []llvm.Value{x, y}, ""), nil case token.LEQ: // x <= y becomes NOT (y < x) result := b.createRuntimeCall("stringLess", []llvm.Value{y, x}, "") return b.CreateNot(result, ""), nil case token.GTR: // x > y becomes y < x return b.createRuntimeCall("stringLess", []llvm.Value{y, x}, ""), nil case token.GEQ: // x >= y becomes NOT (x < y) result := b.createRuntimeCall("stringLess", []llvm.Value{x, y}, "") return b.CreateNot(result, ""), nil default: panic("binop on string: " + op.String()) } } else { return llvm.Value{}, b.makeError(pos, "todo: unknown basic type in binop: "+typ.String()) } case *types.Signature: // Get raw scalars from the function value and compare those. // Function values may be implemented in multiple ways, but they all // have some way of getting a scalar value identifying the function. // This is safe: function pointers are generally not comparable // against each other, only against nil. So one of these has to be nil. x = b.extractFuncScalar(x) y = b.extractFuncScalar(y) switch op { case token.EQL: // == return b.CreateICmp(llvm.IntEQ, x, y, ""), nil case token.NEQ: // != return b.CreateICmp(llvm.IntNE, x, y, ""), nil default: return llvm.Value{}, b.makeError(pos, "binop on signature: "+op.String()) } case *types.Interface: switch op { case token.EQL, token.NEQ: // ==, != nilInterface := llvm.ConstNull(x.Type()) var result llvm.Value if x == nilInterface || y == nilInterface { // An interface value is compared against nil. // This is a very common case and is easy to optimize: simply // compare the typecodes (of which one is nil). typecodeX := b.CreateExtractValue(x, 0, "") typecodeY := b.CreateExtractValue(y, 0, "") result = b.CreateICmp(llvm.IntEQ, typecodeX, typecodeY, "") } else { // Fall back to a full interface comparison. result = b.createRuntimeCall("interfaceEqual", []llvm.Value{x, y}, "") } if op == token.NEQ { result = b.CreateNot(result, "") } return result, nil default: return llvm.Value{}, b.makeError(pos, "binop on interface: "+op.String()) } case *types.Chan, *types.Map, *types.Pointer: // Maps are in general not comparable, but can be compared against nil // (which is a nil pointer). This means they can be trivially compared // by treating them as a pointer. // Channels behave as pointers in that they are equal as long as they // are created with the same call to make or if both are nil. switch op { case token.EQL: // == return b.CreateICmp(llvm.IntEQ, x, y, ""), nil case token.NEQ: // != return b.CreateICmp(llvm.IntNE, x, y, ""), nil default: return llvm.Value{}, b.makeError(pos, "todo: binop on pointer: "+op.String()) } case *types.Slice: // Slices are in general not comparable, but can be compared against // nil. Assume at least one of them is nil to make the code easier. xPtr := b.CreateExtractValue(x, 0, "") yPtr := b.CreateExtractValue(y, 0, "") switch op { case token.EQL: // == return b.CreateICmp(llvm.IntEQ, xPtr, yPtr, ""), nil case token.NEQ: // != return b.CreateICmp(llvm.IntNE, xPtr, yPtr, ""), nil default: return llvm.Value{}, b.makeError(pos, "todo: binop on slice: "+op.String()) } case *types.Array: // Compare each array element and combine the result. From the spec: // Array values are comparable if values of the array element type // are comparable. Two array values are equal if their corresponding // elements are equal. result := llvm.ConstInt(b.ctx.Int1Type(), 1, true) for i := 0; i < int(typ.Len()); i++ { xField := b.CreateExtractValue(x, i, "") yField := b.CreateExtractValue(y, i, "") fieldEqual, err := b.createBinOp(token.EQL, typ.Elem(), typ.Elem(), xField, yField, pos) if err != nil { return llvm.Value{}, err } result = b.CreateAnd(result, fieldEqual, "") } switch op { case token.EQL: // == return result, nil case token.NEQ: // != return b.CreateNot(result, ""), nil default: return llvm.Value{}, b.makeError(pos, "unknown: binop on struct: "+op.String()) } case *types.Struct: // Compare each struct field and combine the result. From the spec: // Struct values are comparable if all their fields are comparable. // Two struct values are equal if their corresponding non-blank // fields are equal. result := llvm.ConstInt(b.ctx.Int1Type(), 1, true) for i := 0; i < typ.NumFields(); i++ { if typ.Field(i).Name() == "_" { // skip blank fields continue } fieldType := typ.Field(i).Type() xField := b.CreateExtractValue(x, i, "") yField := b.CreateExtractValue(y, i, "") fieldEqual, err := b.createBinOp(token.EQL, fieldType, fieldType, xField, yField, pos) if err != nil { return llvm.Value{}, err } result = b.CreateAnd(result, fieldEqual, "") } switch op { case token.EQL: // == return result, nil case token.NEQ: // != return b.CreateNot(result, ""), nil default: return llvm.Value{}, b.makeError(pos, "unknown: binop on struct: "+op.String()) } default: return llvm.Value{}, b.makeError(pos, "todo: binop type: "+typ.String()) } } // createConst creates a LLVM constant value from a Go constant. func (c *compilerContext) createConst(expr *ssa.Const, pos token.Pos) llvm.Value { switch typ := expr.Type().Underlying().(type) { case *types.Basic: llvmType := c.getLLVMType(typ) if typ.Info()&types.IsBoolean != 0 { n := uint64(0) if constant.BoolVal(expr.Value) { n = 1 } return llvm.ConstInt(llvmType, n, false) } else if typ.Info()&types.IsString != 0 { str := constant.StringVal(expr.Value) strLen := llvm.ConstInt(c.uintptrType, uint64(len(str)), false) var strPtr llvm.Value if str != "" { objname := c.pkg.Path() + "$string" globalType := llvm.ArrayType(c.ctx.Int8Type(), len(str)) global := llvm.AddGlobal(c.mod, globalType, objname) global.SetInitializer(c.ctx.ConstString(str, false)) global.SetLinkage(llvm.InternalLinkage) global.SetGlobalConstant(true) global.SetUnnamedAddr(true) global.SetAlignment(1) if c.Debug { // Unfortunately, expr.Pos() is always token.NoPos. position := c.program.Fset.Position(pos) diglobal := c.dibuilder.CreateGlobalVariableExpression(llvm.Metadata{}, llvm.DIGlobalVariableExpression{ File: c.getDIFile(position.Filename), Line: position.Line, Type: c.getDIType(types.NewArray(types.Typ[types.Byte], int64(len(str)))), LocalToUnit: true, Expr: c.dibuilder.CreateExpression(nil), }) global.AddMetadata(0, diglobal) } zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false) strPtr = llvm.ConstInBoundsGEP(globalType, global, []llvm.Value{zero, zero}) } else { strPtr = llvm.ConstNull(c.dataPtrType) } strObj := llvm.ConstNamedStruct(c.getLLVMRuntimeType("_string"), []llvm.Value{strPtr, strLen}) return strObj } else if typ.Kind() == types.UnsafePointer { if !expr.IsNil() { value, _ := constant.Uint64Val(constant.ToInt(expr.Value)) return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, value, false), c.dataPtrType) } return llvm.ConstNull(c.dataPtrType) } else if typ.Info()&types.IsUnsigned != 0 { n, _ := constant.Uint64Val(constant.ToInt(expr.Value)) return llvm.ConstInt(llvmType, n, false) } else if typ.Info()&types.IsInteger != 0 { // signed n, _ := constant.Int64Val(constant.ToInt(expr.Value)) return llvm.ConstInt(llvmType, uint64(n), true) } else if typ.Info()&types.IsFloat != 0 { n, _ := constant.Float64Val(expr.Value) return llvm.ConstFloat(llvmType, n) } else if typ.Kind() == types.Complex64 { r := c.createConst(ssa.NewConst(constant.Real(expr.Value), types.Typ[types.Float32]), pos) i := c.createConst(ssa.NewConst(constant.Imag(expr.Value), types.Typ[types.Float32]), pos) cplx := llvm.Undef(c.ctx.StructType([]llvm.Type{c.ctx.FloatType(), c.ctx.FloatType()}, false)) cplx = c.builder.CreateInsertValue(cplx, r, 0, "") cplx = c.builder.CreateInsertValue(cplx, i, 1, "") return cplx } else if typ.Kind() == types.Complex128 { r := c.createConst(ssa.NewConst(constant.Real(expr.Value), types.Typ[types.Float64]), pos) i := c.createConst(ssa.NewConst(constant.Imag(expr.Value), types.Typ[types.Float64]), pos) cplx := llvm.Undef(c.ctx.StructType([]llvm.Type{c.ctx.DoubleType(), c.ctx.DoubleType()}, false)) cplx = c.builder.CreateInsertValue(cplx, r, 0, "") cplx = c.builder.CreateInsertValue(cplx, i, 1, "") return cplx } else { panic("unknown constant of basic type: " + expr.String()) } case *types.Chan: if expr.Value != nil { panic("expected nil chan constant") } return llvm.ConstNull(c.getLLVMType(expr.Type())) case *types.Signature: if expr.Value != nil { panic("expected nil signature constant") } return llvm.ConstNull(c.getLLVMType(expr.Type())) case *types.Interface: if expr.Value != nil { panic("expected nil interface constant") } // Create a generic nil interface with no dynamic type (typecode=0). fields := []llvm.Value{ llvm.ConstInt(c.uintptrType, 0, false), llvm.ConstPointerNull(c.dataPtrType), } return llvm.ConstNamedStruct(c.getLLVMRuntimeType("_interface"), fields) case *types.Pointer: if expr.Value != nil { panic("expected nil pointer constant") } return llvm.ConstPointerNull(c.getLLVMType(typ)) case *types.Array: if expr.Value != nil { panic("expected nil array constant") } return llvm.ConstNull(c.getLLVMType(expr.Type())) case *types.Slice: if expr.Value != nil { panic("expected nil slice constant") } llvmPtr := llvm.ConstPointerNull(c.dataPtrType) llvmLen := llvm.ConstInt(c.uintptrType, 0, false) slice := c.ctx.ConstStruct([]llvm.Value{ llvmPtr, // backing array llvmLen, // len llvmLen, // cap }, false) return slice case *types.Struct: if expr.Value != nil { panic("expected nil struct constant") } return llvm.ConstNull(c.getLLVMType(expr.Type())) case *types.Map: if !expr.IsNil() { // I believe this is not allowed by the Go spec. panic("non-nil map constant") } llvmType := c.getLLVMType(typ) return llvm.ConstNull(llvmType) default: panic("unknown constant: " + expr.String()) } } // createConvert creates a Go type conversion instruction. func (b *builder) createConvert(typeFrom, typeTo types.Type, value llvm.Value, pos token.Pos) (llvm.Value, error) { llvmTypeFrom := value.Type() llvmTypeTo := b.getLLVMType(typeTo) // Conversion between unsafe.Pointer and uintptr. isPtrFrom := isPointer(typeFrom.Underlying()) isPtrTo := isPointer(typeTo.Underlying()) if isPtrFrom && !isPtrTo { return b.CreatePtrToInt(value, llvmTypeTo, ""), nil } else if !isPtrFrom && isPtrTo { return b.CreateIntToPtr(value, llvmTypeTo, ""), nil } // Conversion between pointers and unsafe.Pointer. if isPtrFrom && isPtrTo { return value, nil } switch typeTo := typeTo.Underlying().(type) { case *types.Basic: sizeFrom := b.targetData.TypeAllocSize(llvmTypeFrom) if typeTo.Info()&types.IsString != 0 { switch typeFrom := typeFrom.Underlying().(type) { case *types.Basic: // Assume a Unicode code point, as that is the only possible // value here. // Cast to an i32 value as expected by // runtime.stringFromUnicode. if sizeFrom > 4 { value = b.CreateTrunc(value, b.ctx.Int32Type(), "") } else if sizeFrom < 4 && typeTo.Info()&types.IsUnsigned != 0 { value = b.CreateZExt(value, b.ctx.Int32Type(), "") } else if sizeFrom < 4 { value = b.CreateSExt(value, b.ctx.Int32Type(), "") } return b.createRuntimeCall("stringFromUnicode", []llvm.Value{value}, ""), nil case *types.Slice: switch typeFrom.Elem().(*types.Basic).Kind() { case types.Byte: return b.createRuntimeCall("stringFromBytes", []llvm.Value{value}, ""), nil case types.Rune: return b.createRuntimeCall("stringFromRunes", []llvm.Value{value}, ""), nil default: return llvm.Value{}, b.makeError(pos, "todo: convert to string: "+typeFrom.String()) } default: return llvm.Value{}, b.makeError(pos, "todo: convert to string: "+typeFrom.String()) } } typeFrom := typeFrom.Underlying().(*types.Basic) sizeTo := b.targetData.TypeAllocSize(llvmTypeTo) if typeFrom.Info()&types.IsInteger != 0 && typeTo.Info()&types.IsInteger != 0 { // Conversion between two integers. if sizeFrom > sizeTo { return b.CreateTrunc(value, llvmTypeTo, ""), nil } else if typeFrom.Info()&types.IsUnsigned != 0 { // if unsigned return b.CreateZExt(value, llvmTypeTo, ""), nil } else { // if signed return b.CreateSExt(value, llvmTypeTo, ""), nil } } if typeFrom.Info()&types.IsFloat != 0 && typeTo.Info()&types.IsFloat != 0 { // Conversion between two floats. if sizeFrom > sizeTo { return b.CreateFPTrunc(value, llvmTypeTo, ""), nil } else if sizeFrom < sizeTo { return b.CreateFPExt(value, llvmTypeTo, ""), nil } else { return value, nil } } if typeFrom.Info()&types.IsFloat != 0 && typeTo.Info()&types.IsInteger != 0 { // Conversion from float to int. // Passing an out-of-bounds float to LLVM would cause UB, so that UB is trapped by select instructions. // The Go specification says that this should be implementation-defined behavior. // This implements saturating behavior, except that NaN is mapped to the minimum value. var significandBits int switch typeFrom.Kind() { case types.Float32: significandBits = 23 case types.Float64: significandBits = 52 } if typeTo.Info()&types.IsUnsigned != 0 { // if unsigned // Select the maximum value for this unsigned integer type. max := ^(^uint64(0) << uint(llvmTypeTo.IntTypeWidth())) maxFloat := float64(max) if bits.Len64(max) > significandBits { // Round the max down to fit within the significand. maxFloat = float64(max & (^uint64(0) << uint(bits.Len64(max)-significandBits))) } // Check if the value is in-bounds (0 <= value <= max). positive := b.CreateFCmp(llvm.FloatOLE, llvm.ConstNull(llvmTypeFrom), value, "positive") withinMax := b.CreateFCmp(llvm.FloatOLE, value, llvm.ConstFloat(llvmTypeFrom, maxFloat), "withinmax") inBounds := b.CreateAnd(positive, withinMax, "inbounds") // Assuming that the value is out-of-bounds, select a saturated value. saturated := b.CreateSelect(positive, llvm.ConstInt(llvmTypeTo, max, false), // value > max llvm.ConstNull(llvmTypeTo), // value < 0 (or NaN) "saturated", ) // Do a normal conversion. normal := b.CreateFPToUI(value, llvmTypeTo, "normal") return b.CreateSelect(inBounds, normal, saturated, ""), nil } else { // if signed // Select the minimum value for this signed integer type. min := uint64(1) << uint(llvmTypeTo.IntTypeWidth()-1) minFloat := -float64(min) // Select the maximum value for this signed integer type. max := ^(^uint64(0) << uint(llvmTypeTo.IntTypeWidth()-1)) maxFloat := float64(max) if bits.Len64(max) > significandBits { // Round the max down to fit within the significand. maxFloat = float64(max & (^uint64(0) << uint(bits.Len64(max)-significandBits))) } // Check if the value is in-bounds (min <= value <= max). aboveMin := b.CreateFCmp(llvm.FloatOLE, llvm.ConstFloat(llvmTypeFrom, minFloat), value, "abovemin") belowMax := b.CreateFCmp(llvm.FloatOLE, value, llvm.ConstFloat(llvmTypeFrom, maxFloat), "belowmax") inBounds := b.CreateAnd(aboveMin, belowMax, "inbounds") // Assuming that the value is out-of-bounds, select a saturated value. saturated := b.CreateSelect(aboveMin, llvm.ConstInt(llvmTypeTo, max, false), // value > max llvm.ConstInt(llvmTypeTo, min, false), // value < min "saturated", ) // Map NaN to 0. saturated = b.CreateSelect(b.CreateFCmp(llvm.FloatUNO, value, value, "isnan"), llvm.ConstNull(llvmTypeTo), saturated, "remapped", ) // Do a normal conversion. normal := b.CreateFPToSI(value, llvmTypeTo, "normal") return b.CreateSelect(inBounds, normal, saturated, ""), nil } } if typeFrom.Info()&types.IsInteger != 0 && typeTo.Info()&types.IsFloat != 0 { // Conversion from int to float. if typeFrom.Info()&types.IsUnsigned != 0 { // if unsigned return b.CreateUIToFP(value, llvmTypeTo, ""), nil } else { // if signed return b.CreateSIToFP(value, llvmTypeTo, ""), nil } } if typeFrom.Kind() == types.Complex128 && typeTo.Kind() == types.Complex64 { // Conversion from complex128 to complex64. r := b.CreateExtractValue(value, 0, "real.f64") i := b.CreateExtractValue(value, 1, "imag.f64") r = b.CreateFPTrunc(r, b.ctx.FloatType(), "real.f32") i = b.CreateFPTrunc(i, b.ctx.FloatType(), "imag.f32") cplx := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.FloatType(), b.ctx.FloatType()}, false)) cplx = b.CreateInsertValue(cplx, r, 0, "") cplx = b.CreateInsertValue(cplx, i, 1, "") return cplx, nil } if typeFrom.Kind() == types.Complex64 && typeTo.Kind() == types.Complex128 { // Conversion from complex64 to complex128. r := b.CreateExtractValue(value, 0, "real.f32") i := b.CreateExtractValue(value, 1, "imag.f32") r = b.CreateFPExt(r, b.ctx.DoubleType(), "real.f64") i = b.CreateFPExt(i, b.ctx.DoubleType(), "imag.f64") cplx := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.DoubleType(), b.ctx.DoubleType()}, false)) cplx = b.CreateInsertValue(cplx, r, 0, "") cplx = b.CreateInsertValue(cplx, i, 1, "") return cplx, nil } return llvm.Value{}, b.makeError(pos, "todo: convert: basic non-integer type: "+typeFrom.String()+" -> "+typeTo.String()) case *types.Slice: if basic, ok := typeFrom.Underlying().(*types.Basic); !ok || basic.Info()&types.IsString == 0 { panic("can only convert from a string to a slice") } elemType := typeTo.Elem().Underlying().(*types.Basic) // must be byte or rune switch elemType.Kind() { case types.Byte: return b.createRuntimeCall("stringToBytes", []llvm.Value{value}, ""), nil case types.Rune: return b.createRuntimeCall("stringToRunes", []llvm.Value{value}, ""), nil default: panic("unexpected type in string to slice conversion") } default: return llvm.Value{}, b.makeError(pos, "todo: convert "+typeTo.String()+" <- "+typeFrom.String()) } } // createUnOp creates LLVM IR for a given Go unary operation. // Most unary operators are pretty simple, such as the not and minus operator // which can all be directly lowered to IR. However, there is also the channel // receive operator which is handled in the runtime directly. func (b *builder) createUnOp(unop *ssa.UnOp) (llvm.Value, error) { x := b.getValue(unop.X, getPos(unop)) switch unop.Op { case token.NOT: // !x return b.CreateNot(x, ""), nil case token.SUB: // -x if typ, ok := unop.X.Type().Underlying().(*types.Basic); ok { if typ.Info()&types.IsInteger != 0 { return b.CreateSub(llvm.ConstInt(x.Type(), 0, false), x, ""), nil } else if typ.Info()&types.IsFloat != 0 { return b.CreateFNeg(x, ""), nil } else if typ.Info()&types.IsComplex != 0 { // Negate both components of the complex number. r := b.CreateExtractValue(x, 0, "r") i := b.CreateExtractValue(x, 1, "i") r = b.CreateFNeg(r, "") i = b.CreateFNeg(i, "") cplx := llvm.Undef(x.Type()) cplx = b.CreateInsertValue(cplx, r, 0, "") cplx = b.CreateInsertValue(cplx, i, 1, "") return cplx, nil } else { return llvm.Value{}, b.makeError(unop.Pos(), "todo: unknown basic type for negate: "+typ.String()) } } else { return llvm.Value{}, b.makeError(unop.Pos(), "todo: unknown type for negate: "+unop.X.Type().Underlying().String()) } case token.MUL: // *x, dereference pointer valueType := b.getLLVMType(unop.X.Type().Underlying().(*types.Pointer).Elem()) if b.targetData.TypeAllocSize(valueType) == 0 { // zero-length data return llvm.ConstNull(valueType), nil } else if strings.HasSuffix(unop.X.String(), "$funcaddr") { // CGo function pointer. The cgo part has rewritten CGo function // pointers as stub global variables of the form: // var C.add unsafe.Pointer // Instead of a load from the global, create a bitcast of the // function pointer itself. name := strings.TrimSuffix(unop.X.(*ssa.Global).Name(), "$funcaddr") pkg := b.fn.Pkg if pkg == nil { pkg = b.fn.Origin().Pkg } _, fn := b.getFunction(pkg.Members[name].(*ssa.Function)) if fn.IsNil() { return llvm.Value{}, b.makeError(unop.Pos(), "cgo function not found: "+name) } return fn, nil } else { b.createNilCheck(unop.X, x, "deref") load := b.CreateLoad(valueType, x, "") return load, nil } case token.XOR: // ^x, toggle all bits in integer return b.CreateXor(x, llvm.ConstInt(x.Type(), ^uint64(0), false), ""), nil case token.ARROW: // <-x, receive from channel return b.createChanRecv(unop), nil default: return llvm.Value{}, b.makeError(unop.Pos(), "todo: unknown unop") } } ================================================ FILE: compiler/compiler_test.go ================================================ package compiler import ( "flag" "go/types" "os" "strconv" "strings" "testing" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/loader" "tinygo.org/x/go-llvm" ) // Pass -update to go test to update the output of the test files. var flagUpdate = flag.Bool("update", false, "update tests based on test output") type testCase struct { file string target string scheduler string } // Basic tests for the compiler. Build some Go files and compare the output with // the expected LLVM IR for regression testing. func TestCompiler(t *testing.T) { t.Parallel() // Determine Go minor version (e.g. 16 in go1.16.3). _, goMinor, err := goenv.GetGorootVersion() if err != nil { t.Fatal("could not read Go version:", err) } // Determine which tests to run, depending on the Go and LLVM versions. tests := []testCase{ {"basic.go", "", ""}, {"pointer.go", "", ""}, {"slice.go", "", ""}, {"string.go", "", ""}, {"float.go", "", ""}, {"interface.go", "", ""}, {"func.go", "", ""}, {"defer.go", "cortex-m-qemu", ""}, {"pragma.go", "", ""}, {"goroutine.go", "wasm", "asyncify"}, {"goroutine.go", "cortex-m-qemu", "tasks"}, {"channel.go", "", ""}, {"gc.go", "", ""}, {"zeromap.go", "", ""}, } if goMinor >= 20 { tests = append(tests, testCase{"go1.20.go", "", ""}) } if goMinor >= 21 { tests = append(tests, testCase{"go1.21.go", "", ""}) } for _, tc := range tests { name := tc.file targetString := "wasm" if tc.target != "" { targetString = tc.target name += "-" + tc.target } if tc.scheduler != "" { name += "-" + tc.scheduler } t.Run(name, func(t *testing.T) { options := &compileopts.Options{ Target: targetString, } if tc.scheduler != "" { options.Scheduler = tc.scheduler } mod, errs := testCompilePackage(t, options, tc.file) if errs != nil { for _, err := range errs { t.Error(err) } return } err := llvm.VerifyModule(mod, llvm.PrintMessageAction) if err != nil { t.Error(err) } // Optimize IR a little. passOptions := llvm.NewPassBuilderOptions() defer passOptions.Dispose() err = mod.RunPasses("instcombine", llvm.TargetMachine{}, passOptions) if err != nil { t.Error(err) } outFilePrefix := tc.file[:len(tc.file)-3] if tc.target != "" { outFilePrefix += "-" + tc.target } if tc.scheduler != "" { outFilePrefix += "-" + tc.scheduler } outPath := "./testdata/" + outFilePrefix + ".ll" // Update test if needed. Do not check the result. if *flagUpdate { err := os.WriteFile(outPath, []byte(mod.String()), 0666) if err != nil { t.Error("failed to write updated output file:", err) } return } expected, err := os.ReadFile(outPath) if err != nil { t.Fatal("failed to read golden file:", err) } if !fuzzyEqualIR(mod.String(), string(expected)) { t.Errorf("output does not match expected output:\n%s", mod.String()) } }) } } // fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly // equal. That means, only relevant lines are compared (excluding comments // etc.). func fuzzyEqualIR(s1, s2 string) bool { lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n")) lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n")) if len(lines1) != len(lines2) { return false } for i, line1 := range lines1 { line2 := lines2[i] if line1 != line2 { return false } } return true } // filterIrrelevantIRLines removes lines from the input slice of strings that // are not relevant in comparing IR. For example, empty lines and comments are // stripped out. func filterIrrelevantIRLines(lines []string) []string { var out []string llvmVersion, err := strconv.Atoi(strings.Split(llvm.Version, ".")[0]) if err != nil { // Note: this should never happen and if it does, it will always happen // for a particular build because llvm.Version is a constant. panic(err) } for _, line := range lines { line = strings.Split(line, ";")[0] // strip out comments/info line = strings.TrimRight(line, "\r ") // drop '\r' on Windows and remove trailing spaces from comments if line == "" { continue } if strings.HasPrefix(line, "source_filename = ") { continue } if llvmVersion < 15 && strings.HasPrefix(line, "target datalayout = ") { // The datalayout string may vary betewen LLVM versions. // Right now test outputs are for LLVM 15 and higher. continue } out = append(out, line) } return out } func TestCompilerErrors(t *testing.T) { t.Parallel() // Read expected errors from the test file. var expectedErrors []string errorsFile, err := os.ReadFile("testdata/errors.go") if err != nil { t.Error(err) } errorsFileString := strings.ReplaceAll(string(errorsFile), "\r\n", "\n") for _, line := range strings.Split(errorsFileString, "\n") { if strings.HasPrefix(line, "// ERROR: ") { expectedErrors = append(expectedErrors, strings.TrimPrefix(line, "// ERROR: ")) } } // Compile the Go file with errors. options := &compileopts.Options{ Target: "wasm", } _, errs := testCompilePackage(t, options, "errors.go") // Check whether the actual errors match the expected errors. expectedErrorsIdx := 0 for _, err := range errs { err := err.(types.Error) position := err.Fset.Position(err.Pos) position.Filename = "errors.go" // don't use a full path if expectedErrorsIdx >= len(expectedErrors) || expectedErrors[expectedErrorsIdx] != err.Msg { t.Errorf("unexpected compiler error: %s: %s", position.String(), err.Msg) continue } expectedErrorsIdx++ } } // Build a package given a number of compiler options and a file. func testCompilePackage(t *testing.T, options *compileopts.Options, file string) (llvm.Module, []error) { target, err := compileopts.LoadTarget(options) if err != nil { t.Fatal("failed to load target:", err) } config := &compileopts.Config{ Options: options, Target: target, } compilerConfig := &Config{ Triple: config.Triple(), Features: config.Features(), ABI: config.ABI(), GOOS: config.GOOS(), GOARCH: config.GOARCH(), CodeModel: config.CodeModel(), RelocationModel: config.RelocationModel(), Scheduler: config.Scheduler(), AutomaticStackSize: config.AutomaticStackSize(), DefaultStackSize: config.StackSize(), NeedsStackObjects: config.NeedsStackObjects(), } machine, err := NewTargetMachine(compilerConfig) if err != nil { t.Fatal("failed to create target machine:", err) } defer machine.Dispose() // Load entire program AST into memory. lprogram, err := loader.Load(config, "./testdata/"+file, types.Config{ Sizes: Sizes(machine), }) if err != nil { t.Fatal("failed to create target machine:", err) } err = lprogram.Parse() if err != nil { t.Fatalf("could not parse test case %s: %s", file, err) } // Compile AST to IR. program := lprogram.LoadSSA() pkg := lprogram.MainPkg() return CompilePackage(file, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false) } ================================================ FILE: compiler/defer.go ================================================ package compiler // This file implements the 'defer' keyword in Go. // Defer statements are implemented by transforming the function in the // following way: // * Creating an alloca in the entry block that contains a pointer (initially // null) to the linked list of defer frames. // * Every time a defer statement is executed, a new defer frame is created // using alloca with a pointer to the previous defer frame, and the head // pointer in the entry block is replaced with a pointer to this defer // frame. // * On return, runtime.rundefers is called which calls all deferred functions // from the head of the linked list until it has gone through all defer // frames. import ( "go/types" "strconv" "strings" "github.com/tinygo-org/tinygo/compiler/llvmutil" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) // supportsRecover returns whether the compiler supports the recover() builtin // for the current architecture. func (b *builder) supportsRecover() bool { switch b.archFamily() { case "wasm32": // Probably needs to be implemented using the exception handling // proposal of WebAssembly: // https://github.com/WebAssembly/exception-handling return false case "riscv64", "xtensa": // TODO: add support for these architectures return false default: return true } } // hasDeferFrame returns whether the current function needs to catch panics and // run defers. func (b *builder) hasDeferFrame() bool { if b.fn.Recover == nil { return false } return b.supportsRecover() } // deferInitFunc sets up this function for future deferred calls. It must be // called from within the entry block when this function contains deferred // calls. func (b *builder) deferInitFunc() { // Some setup. b.deferFuncs = make(map[*ssa.Function]int) b.deferInvokeFuncs = make(map[string]int) b.deferClosureFuncs = make(map[*ssa.Function]int) b.deferExprFuncs = make(map[ssa.Value]int) b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin) // Create defer list pointer. b.deferPtr = b.CreateAlloca(b.dataPtrType, "deferPtr") b.CreateStore(llvm.ConstPointerNull(b.dataPtrType), b.deferPtr) if b.hasDeferFrame() { // Set up the defer frame with the current stack pointer. // This assumes that the stack pointer doesn't move outside of the // function prologue/epilogue (an invariant maintained by TinyGo but // possibly broken by the C alloca function). // The frame pointer is _not_ saved, because it is marked as clobbered // in the setjmp-like inline assembly. deferFrameType := b.getLLVMRuntimeType("deferFrame") b.deferFrame = b.CreateAlloca(deferFrameType, "deferframe.buf") stackPointer := b.readStackPointer() b.createRuntimeCall("setupDeferFrame", []llvm.Value{b.deferFrame, stackPointer}, "") // Create the landing pad block, which is where control transfers after // a panic. b.landingpad = b.ctx.AddBasicBlock(b.llvmFn, "lpad") } } // createLandingPad fills in the landing pad block. This block runs the deferred // functions and returns (by jumping to the recover block). If the function is // still panicking after the defers are run, the panic will be re-raised in // destroyDeferFrame. func (b *builder) createLandingPad() { b.SetInsertPointAtEnd(b.landingpad) // Add debug info, if needed. // The location used is the closing bracket of the function. if b.Debug { pos := b.program.Fset.Position(b.fn.Syntax().End()) b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{}) } b.createRunDefers() // Continue at the 'recover' block, which returns to the parent in an // appropriate way. b.CreateBr(b.blockInfo[b.fn.Recover.Index].entry) } // Create a checkpoint (similar to setjmp). This emits inline assembly that // stores the current program counter inside the ptr address (actually // ptr+sizeof(ptr)) and then returns a boolean indicating whether this is the // normal flow (false) or we jumped here from somewhere else (true). func (b *builder) createCheckpoint(ptr llvm.Value) llvm.Value { // Construct inline assembly equivalents of setjmp. // The assembly works as follows: // * All registers (both callee-saved and caller saved) are clobbered // after the inline assembly returns. // * The assembly stores the address just past the end of the assembly // into the jump buffer. // * The return value (eax, rax, r0, etc) is set to zero in the inline // assembly but set to an unspecified non-zero value when jumping using // a longjmp. var asmString, constraints string resultType := b.uintptrType switch b.archFamily() { case "i386": asmString = ` xorl %eax, %eax movl $$1f, 4(%ebx) 1:` constraints = "={eax},{ebx},~{ebx},~{ecx},~{edx},~{esi},~{edi},~{ebp},~{xmm0},~{xmm1},~{xmm2},~{xmm3},~{xmm4},~{xmm5},~{xmm6},~{xmm7},~{fpsr},~{fpcr},~{flags},~{dirflag},~{memory}" // This doesn't include the floating point stack because TinyGo uses // newer floating point instructions. case "x86_64": asmString = ` leaq 1f(%rip), %rax movq %rax, 8(%rbx) xorq %rax, %rax 1:` constraints = "={rax},{rbx},~{rbx},~{rcx},~{rdx},~{rsi},~{rdi},~{rbp},~{r8},~{r9},~{r10},~{r11},~{r12},~{r13},~{r14},~{r15},~{xmm0},~{xmm1},~{xmm2},~{xmm3},~{xmm4},~{xmm5},~{xmm6},~{xmm7},~{xmm8},~{xmm9},~{xmm10},~{xmm11},~{xmm12},~{xmm13},~{xmm14},~{xmm15},~{xmm16},~{xmm17},~{xmm18},~{xmm19},~{xmm20},~{xmm21},~{xmm22},~{xmm23},~{xmm24},~{xmm25},~{xmm26},~{xmm27},~{xmm28},~{xmm29},~{xmm30},~{xmm31},~{fpsr},~{fpcr},~{flags},~{dirflag},~{memory}" // This list doesn't include AVX/AVX512 registers because TinyGo // doesn't currently enable support for AVX instructions. case "arm": // Note: the following assembly takes into account that the PC is // always 4 bytes ahead on ARM. The PC that is stored always points // to the instruction just after the assembly fragment so that // tinygo_longjmp lands at the correct instruction. if b.isThumb() { // Instructions are 2 bytes in size. asmString = ` movs r0, #0 mov r2, pc str r2, [r1, #4]` } else { // Instructions are 4 bytes in size. asmString = ` str pc, [r1, #4] movs r0, #0` } constraints = "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}" case "aarch64": asmString = ` adr x2, 1f str x2, [x1, #8] mov x0, #0 1: ` constraints = "={x0},{x1},~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7},~{x8},~{x9},~{x10},~{x11},~{x12},~{x13},~{x14},~{x15},~{x16},~{x17},~{x19},~{x20},~{x21},~{x22},~{x23},~{x24},~{x25},~{x26},~{x27},~{x28},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{q16},~{q17},~{q18},~{q19},~{q20},~{q21},~{q22},~{q23},~{q24},~{q25},~{q26},~{q27},~{q28},~{q29},~{q30},~{nzcv},~{ffr},~{memory}" if b.GOOS != "darwin" && b.GOOS != "windows" { // These registers cause the following warning when compiling for // MacOS and Windows: // warning: inline asm clobber list contains reserved registers: // X18, FP // Reserved registers on the clobber list may not be preserved // across the asm statement, and clobbering them may lead to // undefined behaviour. constraints += ",~{x18},~{fp}" } // TODO: SVE registers, which we don't use in TinyGo at the moment. case "avr": // Note: the Y register (R28:R29) is a fixed register and therefore // needs to be saved manually. TODO: do this only once per function with // a defer frame, not for every call. resultType = b.ctx.Int8Type() asmString = ` ldi r24, pm_lo8(1f) ldi r25, pm_hi8(1f) std z+2, r24 std z+3, r25 std z+4, r28 std z+5, r29 ldi r24, 0 1:` constraints = "={r24},z,~{r0},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{r13},~{r14},~{r15},~{r16},~{r17},~{r18},~{r19},~{r20},~{r21},~{r22},~{r23},~{r25},~{r26},~{r27}" case "mips": // $4 flag (zero or non-zero) // $5 defer frame asmString = ` .set noat move $$4, $$zero jal 1f 1: addiu $$ra, 8 sw $$ra, 4($$5) .set at` constraints = "={$4},{$5},~{$1},~{$2},~{$3},~{$5},~{$6},~{$7},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$16},~{$17},~{$18},~{$19},~{$20},~{$21},~{$22},~{$23},~{$24},~{$25},~{$26},~{$27},~{$28},~{$29},~{$30},~{$31},~{memory}" if !strings.Contains(b.Features, "+soft-float") { // Using floating point registers together with GOMIPS=softfloat // results in a crash: "This value type is not natively supported!" // So only add them when using hardfloat. constraints += ",~{$f0},~{$f1},~{$f2},~{$f3},~{$f4},~{$f5},~{$f6},~{$f7},~{$f8},~{$f9},~{$f10},~{$f11},~{$f12},~{$f13},~{$f14},~{$f15},~{$f16},~{$f17},~{$f18},~{$f19},~{$f20},~{$f21},~{$f22},~{$f23},~{$f24},~{$f25},~{$f26},~{$f27},~{$f28},~{$f29},~{$f30},~{$f31}" } case "riscv32": asmString = ` la a2, 1f sw a2, 4(a1) li a0, 0 1:` constraints = "={a0},{a1},~{a1},~{a2},~{a3},~{a4},~{a5},~{a6},~{a7},~{s0},~{s1},~{s2},~{s3},~{s4},~{s5},~{s6},~{s7},~{s8},~{s9},~{s10},~{s11},~{t0},~{t1},~{t2},~{t3},~{t4},~{t5},~{t6},~{ra},~{f0},~{f1},~{f2},~{f3},~{f4},~{f5},~{f6},~{f7},~{f8},~{f9},~{f10},~{f11},~{f12},~{f13},~{f14},~{f15},~{f16},~{f17},~{f18},~{f19},~{f20},~{f21},~{f22},~{f23},~{f24},~{f25},~{f26},~{f27},~{f28},~{f29},~{f30},~{f31},~{memory}" default: // This case should have been handled by b.supportsRecover(). b.addError(b.fn.Pos(), "unknown architecture for defer: "+b.archFamily()) } asmType := llvm.FunctionType(resultType, []llvm.Type{b.dataPtrType}, false) asm := llvm.InlineAsm(asmType, asmString, constraints, false, false, 0, false) result := b.CreateCall(asmType, asm, []llvm.Value{ptr}, "setjmp") result.AddCallSiteAttribute(-1, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("returns_twice"), 0)) isZero := b.CreateICmp(llvm.IntEQ, result, llvm.ConstInt(resultType, 0, false), "setjmp.result") return isZero } // createInvokeCheckpoint saves the function state at the given point, to // continue at the landing pad if a panic happened. This is implemented using a // setjmp-like construct. func (b *builder) createInvokeCheckpoint() { isZero := b.createCheckpoint(b.deferFrame) continueBB := b.insertBasicBlock("") b.CreateCondBr(isZero, continueBB, b.landingpad) b.SetInsertPointAtEnd(continueBB) b.currentBlockInfo.exit = continueBB } // isInLoop checks if there is a path from the current block to itself. // Use Tarjan's strongly connected components algorithm to search for cycles. // A one-node SCC is a cycle iff there is an edge from the node to itself. // A multi-node SCC is always a cycle. // https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm func (b *builder) isInLoop() bool { if b.currentBlockInfo.tarjan.lowLink == 0 { b.strongConnect(b.currentBlock) } return b.currentBlockInfo.tarjan.cyclic } func (b *builder) strongConnect(block *ssa.BasicBlock) { // Assign a new index. // Indices start from 1 so that 0 can be used as a sentinel. assignedIndex := b.tarjanIndex + 1 b.tarjanIndex = assignedIndex // Apply the new index. blockIndex := block.Index node := &b.blockInfo[blockIndex].tarjan node.lowLink = assignedIndex // Push the node onto the stack. node.onStack = true b.tarjanStack = append(b.tarjanStack, uint(blockIndex)) // Process the successors. for _, successor := range block.Succs { // Look up the successor's state. successorIndex := successor.Index if successorIndex == blockIndex { // Handle a self-cycle specially. node.cyclic = true continue } successorNode := &b.blockInfo[successorIndex].tarjan switch { case successorNode.lowLink == 0: // This node has not yet been visisted. b.strongConnect(successor) case !successorNode.onStack: // This node has been visited, but is in a different SCC. // Ignore it, and do not update lowLink. continue } // Update the lowLink index. // This always uses the min-of-lowlink instead of using index in the on-stack case. // This is done for two reasons: // 1. The lowLink update can be shared between the new-node and on-stack cases. // 2. The assigned index does not need to be saved - it is only needed for root node detection. if successorNode.lowLink < node.lowLink { node.lowLink = successorNode.lowLink } } if node.lowLink == assignedIndex { // This is a root node. // Pop the SCC off the stack. stack := b.tarjanStack top := stack[len(stack)-1] stack = stack[:len(stack)-1] blocks := b.blockInfo topNode := &blocks[top].tarjan topNode.onStack = false if top != uint(blockIndex) { // The root node is not the only node in the SCC. // Mark all nodes in this SCC as cyclic. topNode.cyclic = true for top != uint(blockIndex) { top = stack[len(stack)-1] stack = stack[:len(stack)-1] topNode = &blocks[top].tarjan topNode.onStack = false topNode.cyclic = true } } b.tarjanStack = stack } } // tarjanNode holds per-block state for isInLoop and strongConnect. type tarjanNode struct { // lowLink is the index of the first visited node that is reachable from this block. // The lowLink indices are assigned by the SCC search, and do not correspond to b.Index. // A lowLink of 0 is used as a sentinel to mark a node which has not yet been visited. lowLink uint // onStack tracks whether this node is currently on the SCC search stack. onStack bool // cyclic indicates whether this block is in a loop. // If lowLink is 0, strongConnect must be called before reading this field. cyclic bool } // createDefer emits a single defer instruction, to be run when this function // returns. func (b *builder) createDefer(instr *ssa.Defer) { // The pointer to the previous defer struct, which we will replace to // make a linked list. next := b.CreateLoad(b.dataPtrType, b.deferPtr, "defer.next") var values []llvm.Value valueTypes := []llvm.Type{b.uintptrType, next.Type()} if instr.Call.IsInvoke() { // Method call on an interface. // Get callback type number. methodName := instr.Call.Method.FullName() if _, ok := b.deferInvokeFuncs[methodName]; !ok { b.deferInvokeFuncs[methodName] = len(b.allDeferFuncs) b.allDeferFuncs = append(b.allDeferFuncs, &instr.Call) } callback := llvm.ConstInt(b.uintptrType, uint64(b.deferInvokeFuncs[methodName]), false) // Collect all values to be put in the struct (starting with // runtime._defer fields, followed by the call parameters). itf := b.getValue(instr.Call.Value, getPos(instr)) // interface typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode") receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver") values = []llvm.Value{callback, next, typecode, receiverValue} valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType) for _, arg := range instr.Call.Args { val := b.getValue(arg, getPos(instr)) values = append(values, val) valueTypes = append(valueTypes, val.Type()) } } else if callee, ok := instr.Call.Value.(*ssa.Function); ok { // Regular function call. if _, ok := b.deferFuncs[callee]; !ok { b.deferFuncs[callee] = len(b.allDeferFuncs) b.allDeferFuncs = append(b.allDeferFuncs, callee) } callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[callee]), false) // Collect all values to be put in the struct (starting with // runtime._defer fields). values = []llvm.Value{callback, next} for _, param := range instr.Call.Args { llvmParam := b.getValue(param, getPos(instr)) values = append(values, llvmParam) valueTypes = append(valueTypes, llvmParam.Type()) } } else if makeClosure, ok := instr.Call.Value.(*ssa.MakeClosure); ok { // Immediately applied function literal with free variables. // Extract the context from the closure. We won't need the function // pointer. // TODO: ignore this closure entirely and put pointers to the free // variables directly in the defer struct, avoiding a memory allocation. closure := b.getValue(instr.Call.Value, getPos(instr)) context := b.CreateExtractValue(closure, 0, "") // Get the callback number. fn := makeClosure.Fn.(*ssa.Function) if _, ok := b.deferClosureFuncs[fn]; !ok { b.deferClosureFuncs[fn] = len(b.allDeferFuncs) b.allDeferFuncs = append(b.allDeferFuncs, makeClosure) } callback := llvm.ConstInt(b.uintptrType, uint64(b.deferClosureFuncs[fn]), false) // Collect all values to be put in the struct (starting with // runtime._defer fields, followed by all parameters including the // context pointer). values = []llvm.Value{callback, next} for _, param := range instr.Call.Args { llvmParam := b.getValue(param, getPos(instr)) values = append(values, llvmParam) valueTypes = append(valueTypes, llvmParam.Type()) } values = append(values, context) valueTypes = append(valueTypes, context.Type()) } else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok { var argTypes []types.Type var argValues []llvm.Value for _, arg := range instr.Call.Args { argTypes = append(argTypes, arg.Type()) argValues = append(argValues, b.getValue(arg, getPos(instr))) } if _, ok := b.deferBuiltinFuncs[instr.Call.Value]; !ok { b.deferBuiltinFuncs[instr.Call.Value] = deferBuiltin{ callName: builtin.Name(), pos: builtin.Pos(), argTypes: argTypes, callback: len(b.allDeferFuncs), } b.allDeferFuncs = append(b.allDeferFuncs, instr.Call.Value) } callback := llvm.ConstInt(b.uintptrType, uint64(b.deferBuiltinFuncs[instr.Call.Value].callback), false) // Collect all values to be put in the struct (starting with // runtime._defer fields). values = []llvm.Value{callback, next} for _, param := range argValues { values = append(values, param) valueTypes = append(valueTypes, param.Type()) } } else { funcValue := b.getValue(instr.Call.Value, getPos(instr)) if _, ok := b.deferExprFuncs[instr.Call.Value]; !ok { b.deferExprFuncs[instr.Call.Value] = len(b.allDeferFuncs) b.allDeferFuncs = append(b.allDeferFuncs, &instr.Call) } callback := llvm.ConstInt(b.uintptrType, uint64(b.deferExprFuncs[instr.Call.Value]), false) // Collect all values to be put in the struct (starting with // runtime._defer fields, followed by all parameters including the // context pointer). values = []llvm.Value{callback, next, funcValue} valueTypes = append(valueTypes, funcValue.Type()) for _, param := range instr.Call.Args { llvmParam := b.getValue(param, getPos(instr)) values = append(values, llvmParam) valueTypes = append(valueTypes, llvmParam.Type()) } } // Make a struct out of the collected values to put in the deferred call // struct. deferredCallType := b.ctx.StructType(valueTypes, false) deferredCall := llvm.ConstNull(deferredCallType) for i, value := range values { deferredCall = b.CreateInsertValue(deferredCall, value, i, "") } // Put this struct in an allocation. var alloca llvm.Value if instr.Block() != b.currentBlock { panic("block mismatch") } if !b.isInLoop() { // This can safely use a stack allocation. alloca = llvmutil.CreateEntryBlockAlloca(b.Builder, deferredCallType, "defer.alloca") } else { // This may be hit a variable number of times, so use a heap allocation. size := b.targetData.TypeAllocSize(deferredCallType) sizeValue := llvm.ConstInt(b.uintptrType, size, false) nilPtr := llvm.ConstNull(b.dataPtrType) alloca = b.createRuntimeCall("alloc", []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call") } if b.NeedsStackObjects { b.trackPointer(alloca) } b.CreateStore(deferredCall, alloca) // Push it on top of the linked list by replacing deferPtr. b.CreateStore(alloca, b.deferPtr) } // createRunDefers emits code to run all deferred functions. func (b *builder) createRunDefers() { deferType := b.getLLVMRuntimeType("_defer") // Add a loop like the following: // for stack != nil { // _stack := stack // stack = stack.next // switch _stack.callback { // case 0: // // run first deferred call // case 1: // // run second deferred call // // etc. // default: // unreachable // } // } // Create loop, in the order: loophead, loop, callback0, callback1, ..., unreachable, end. end := b.insertBasicBlock("rundefers.end") unreachable := b.ctx.InsertBasicBlock(end, "rundefers.default") loop := b.ctx.InsertBasicBlock(unreachable, "rundefers.loop") loophead := b.ctx.InsertBasicBlock(loop, "rundefers.loophead") b.CreateBr(loophead) // Create loop head: // for stack != nil { b.SetInsertPointAtEnd(loophead) deferData := b.CreateLoad(b.dataPtrType, b.deferPtr, "") stackIsNil := b.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil") b.CreateCondBr(stackIsNil, end, loop) // Create loop body: // _stack := stack // stack = stack.next // switch stack.callback { b.SetInsertPointAtEnd(loop) nextStackGEP := b.CreateInBoundsGEP(deferType, deferData, []llvm.Value{ llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 1, false), // .next field }, "stack.next.gep") nextStack := b.CreateLoad(b.dataPtrType, nextStackGEP, "stack.next") b.CreateStore(nextStack, b.deferPtr) gep := b.CreateInBoundsGEP(deferType, deferData, []llvm.Value{ llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false), // .callback field }, "callback.gep") callback := b.CreateLoad(b.uintptrType, gep, "callback") sw := b.CreateSwitch(callback, unreachable, len(b.allDeferFuncs)) for i, callback := range b.allDeferFuncs { // Create switch case, for example: // case 0: // // run first deferred call block := b.insertBasicBlock("rundefers.callback" + strconv.Itoa(i)) sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block) b.SetInsertPointAtEnd(block) switch callback := callback.(type) { case *ssa.CallCommon: // Call on an value or interface value. // Get the real defer struct type and cast to it. valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType} if !callback.IsInvoke() { //Expect funcValue to be passed through the deferred call. valueTypes = append(valueTypes, b.getFuncType(callback.Signature())) } else { //Expect typecode valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType) } for _, arg := range callback.Args { valueTypes = append(valueTypes, b.getLLVMType(arg.Type())) } // Extract the params from the struct (including receiver). forwardParams := []llvm.Value{} zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) deferredCallType := b.ctx.StructType(valueTypes, false) for i := 2; i < len(valueTypes); i++ { gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep") forwardParam := b.CreateLoad(valueTypes[i], gep, "param") forwardParams = append(forwardParams, forwardParam) } var fnPtr llvm.Value var fnType llvm.Type if !callback.IsInvoke() { // Isolate the func value. funcValue := forwardParams[0] forwardParams = forwardParams[1:] //Get function pointer and context var context llvm.Value fnPtr, context = b.decodeFuncValue(funcValue) fnType = b.getLLVMFunctionType(callback.Signature()) //Pass context forwardParams = append(forwardParams, context) } else { // Move typecode from the start to the end of the list of // parameters. forwardParams = append(forwardParams[1:], forwardParams[0]) fnPtr = b.getInvokeFunction(callback) fnType = fnPtr.GlobalValueType() // Add the context parameter. An interface call cannot also be a // closure but we have to supply the parameter anyway for platforms // with a strict calling convention. forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType)) } b.createCall(fnType, fnPtr, forwardParams, "") case *ssa.Function: // Direct call. // Get the real defer struct type and cast to it. valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType} for _, param := range getParams(callback.Signature) { valueTypes = append(valueTypes, b.getLLVMType(param.Type())) } deferredCallType := b.ctx.StructType(valueTypes, false) // Extract the params from the struct. forwardParams := []llvm.Value{} zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) for i := range getParams(callback.Signature) { gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep") forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param") forwardParams = append(forwardParams, forwardParam) } // Plain TinyGo functions add some extra parameters to implement async functionality and function receivers. // These parameters should not be supplied when calling into an external C/ASM function. if !b.getFunctionInfo(callback).exported { // Add the context parameter. We know it is ignored by the receiving // function, but we have to pass one anyway. forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType)) } // Call real function. fnType, fn := b.getFunction(callback) b.createInvoke(fnType, fn, forwardParams, "") case *ssa.MakeClosure: // Get the real defer struct type and cast to it. fn := callback.Fn.(*ssa.Function) valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType} params := fn.Signature.Params() for i := 0; i < params.Len(); i++ { valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type())) } valueTypes = append(valueTypes, b.dataPtrType) // closure deferredCallType := b.ctx.StructType(valueTypes, false) // Extract the params from the struct. forwardParams := []llvm.Value{} zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) for i := 2; i < len(valueTypes); i++ { gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "") forwardParam := b.CreateLoad(valueTypes[i], gep, "param") forwardParams = append(forwardParams, forwardParam) } // Call deferred function. fnType, llvmFn := b.getFunction(fn) b.createCall(fnType, llvmFn, forwardParams, "") case *ssa.Builtin: db := b.deferBuiltinFuncs[callback] //Get parameter types valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType} //Get signature from call results params := callback.Type().Underlying().(*types.Signature).Params() for i := 0; i < params.Len(); i++ { valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type())) } deferredCallType := b.ctx.StructType(valueTypes, false) // Extract the params from the struct. var argValues []llvm.Value zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) for i := 0; i < params.Len(); i++ { gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep") forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param") argValues = append(argValues, forwardParam) } _, err := b.createBuiltin(db.argTypes, argValues, db.callName, db.pos) if err != nil { b.diagnostics = append(b.diagnostics, err) } default: panic("unknown deferred function type") } // Branch back to the start of the loop. b.CreateBr(loophead) } // Create default unreachable block: // default: // unreachable // } b.SetInsertPointAtEnd(unreachable) b.CreateUnreachable() // End of loop. b.SetInsertPointAtEnd(end) } ================================================ FILE: compiler/errors.go ================================================ package compiler // This file contains some utility functions related to error handling. import ( "go/token" "go/types" "path/filepath" "tinygo.org/x/go-llvm" ) // makeError makes it easy to create an error from a token.Pos with a message. func (c *compilerContext) makeError(pos token.Pos, msg string) types.Error { return types.Error{ Fset: c.program.Fset, Pos: pos, Msg: msg, } } // addError adds a new compiler diagnostic with the given location and message. func (c *compilerContext) addError(pos token.Pos, msg string) { c.diagnostics = append(c.diagnostics, c.makeError(pos, msg)) } // getPosition returns the position information for the given value, as far as // it is available. func getPosition(val llvm.Value) token.Position { if !val.IsAInstruction().IsNil() { loc := val.InstructionDebugLoc() if loc.IsNil() { return token.Position{} } file := loc.LocationScope().ScopeFile() return token.Position{ Filename: filepath.Join(file.FileDirectory(), file.FileFilename()), Line: int(loc.LocationLine()), Column: int(loc.LocationColumn()), } } else if !val.IsAFunction().IsNil() { loc := val.Subprogram() if loc.IsNil() { return token.Position{} } file := loc.ScopeFile() return token.Position{ Filename: filepath.Join(file.FileDirectory(), file.FileFilename()), Line: int(loc.SubprogramLine()), } } else { return token.Position{} } } ================================================ FILE: compiler/func.go ================================================ package compiler // This file implements function values and closures. It may need some lowering // in a later step, see func-lowering.go. import ( "go/types" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) // createFuncValue creates a function value from a raw function pointer with no // context. func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value { // Closure is: {context, function pointer} funcValueType := b.getFuncType(sig) funcValue := llvm.Undef(funcValueType) funcValue = b.CreateInsertValue(funcValue, context, 0, "") funcValue = b.CreateInsertValue(funcValue, funcPtr, 1, "") return funcValue } // extractFuncScalar returns some scalar that can be used in comparisons. It is // a cheap operation. func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value { return b.CreateExtractValue(funcValue, 1, "") } // extractFuncContext extracts the context pointer from this function value. It // is a cheap operation. func (b *builder) extractFuncContext(funcValue llvm.Value) llvm.Value { return b.CreateExtractValue(funcValue, 0, "") } // decodeFuncValue extracts the context and the function pointer from this func // value. func (b *builder) decodeFuncValue(funcValue llvm.Value) (funcPtr, context llvm.Value) { context = b.CreateExtractValue(funcValue, 0, "") funcPtr = b.CreateExtractValue(funcValue, 1, "") return } // getFuncType returns the type of a func value given a signature. func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type { return c.ctx.StructType([]llvm.Type{c.dataPtrType, c.funcPtrType}, false) } // getLLVMFunctionType returns a LLVM function type for a given signature. func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type { // Get the return type. var returnType llvm.Type switch typ.Results().Len() { case 0: // No return values. returnType = c.ctx.VoidType() case 1: // Just one return value. returnType = c.getLLVMType(typ.Results().At(0).Type()) default: // Multiple return values. Put them together in a struct. // This appears to be the common way to handle multiple return values in // LLVM. members := make([]llvm.Type, typ.Results().Len()) for i := 0; i < typ.Results().Len(); i++ { members[i] = c.getLLVMType(typ.Results().At(i).Type()) } returnType = c.ctx.StructType(members, false) } // Get the parameter types. var paramTypes []llvm.Type if typ.Recv() != nil { recv := c.getLLVMType(typ.Recv().Type()) if recv.StructName() == "runtime._interface" { // This is a call on an interface, not a concrete type. // The receiver is not an interface, but a i8* type. recv = c.dataPtrType } for _, info := range c.expandFormalParamType(recv, "", nil) { paramTypes = append(paramTypes, info.llvmType) } } for i := 0; i < typ.Params().Len(); i++ { subType := c.getLLVMType(typ.Params().At(i).Type()) for _, info := range c.expandFormalParamType(subType, "", nil) { paramTypes = append(paramTypes, info.llvmType) } } // All functions take these parameters at the end. paramTypes = append(paramTypes, c.dataPtrType) // context // Make a func type out of the signature. return llvm.FunctionType(returnType, paramTypes, false) } // parseMakeClosure makes a function value (with context) from the given // closure expression. func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) { if len(expr.Bindings) == 0 { panic("unexpected: MakeClosure without bound variables") } f := expr.Fn.(*ssa.Function) // Collect all bound variables. boundVars := make([]llvm.Value, len(expr.Bindings)) for i, binding := range expr.Bindings { // The context stores the bound variables. llvmBoundVar := b.getValue(binding, getPos(expr)) boundVars[i] = llvmBoundVar } // Store the bound variables in a single object, allocating it on the heap // if necessary. context := b.emitPointerPack(boundVars) // Create the closure. _, fn := b.getFunction(f) return b.createFuncValue(fn, context, f.Signature), nil } ================================================ FILE: compiler/gc.go ================================================ package compiler // This file provides IR transformations necessary for precise and portable // garbage collectors. import ( "go/token" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) // trackExpr inserts pointer tracking intrinsics for the GC if the expression is // one of the expressions that need this. func (b *builder) trackExpr(expr ssa.Value, value llvm.Value) { // There are uses of this expression, Make sure the pointers // are tracked during GC. switch expr := expr.(type) { case *ssa.Alloc, *ssa.MakeChan, *ssa.MakeMap: // These values are always of pointer type in IR. b.trackPointer(value) case *ssa.Call, *ssa.Convert, *ssa.MakeClosure, *ssa.MakeInterface, *ssa.MakeSlice, *ssa.Next: if !value.IsNil() { b.trackValue(value) } case *ssa.Select: if alloca, ok := b.selectRecvBuf[expr]; ok { if alloca.IsAUndefValue().IsNil() { b.trackPointer(alloca) } } case *ssa.UnOp: switch expr.Op { case token.MUL: // Pointer dereference. b.trackValue(value) case token.ARROW: // Channel receive operator. // It's not necessary to look at commaOk here, because in that // case it's just an aggregate and trackValue will extract the // pointer in there (if there is one). b.trackValue(value) } case *ssa.BinOp: switch expr.Op { case token.ADD: // String concatenation. b.trackValue(value) } } } // trackValue locates pointers in a value (possibly an aggregate) and tracks the // individual pointers func (b *builder) trackValue(value llvm.Value) { typ := value.Type() switch typ.TypeKind() { case llvm.PointerTypeKind: b.trackPointer(value) case llvm.StructTypeKind: if !typeHasPointers(typ) { return } numElements := typ.StructElementTypesCount() for i := 0; i < numElements; i++ { subValue := b.CreateExtractValue(value, i, "") b.trackValue(subValue) } case llvm.ArrayTypeKind: if !typeHasPointers(typ) { return } numElements := typ.ArrayLength() for i := 0; i < numElements; i++ { subValue := b.CreateExtractValue(value, i, "") b.trackValue(subValue) } } } // trackPointer creates a call to runtime.trackPointer, bitcasting the pointer // first if needed. The input value must be of LLVM pointer type. func (b *builder) trackPointer(value llvm.Value) { b.createRuntimeCall("trackPointer", []llvm.Value{value, b.stackChainAlloca}, "") } // typeHasPointers returns whether this type is a pointer or contains pointers. // If the type is an aggregate type, it will check whether there is a pointer // inside. func typeHasPointers(t llvm.Type) bool { switch t.TypeKind() { case llvm.PointerTypeKind: return true case llvm.StructTypeKind: for _, subType := range t.StructElementTypes() { if typeHasPointers(subType) { return true } } return false case llvm.ArrayTypeKind: if typeHasPointers(t.ElementType()) { return true } return false default: return false } } ================================================ FILE: compiler/goroutine.go ================================================ package compiler // This file implements the 'go' keyword to start a new goroutine. See // goroutine-lowering.go for more details. import ( "go/token" "go/types" "github.com/tinygo-org/tinygo/compiler/llvmutil" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) // createGo emits code to start a new goroutine. func (b *builder) createGo(instr *ssa.Go) { if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok { // We cheat. None of the builtins do any long or blocking operation, so // we might as well run these builtins right away without the program // noticing the difference. // Possible exceptions: // - copy: this is a possibly long operation, but not a blocking // operation. Semantically it makes no difference to run it right // away (not in a goroutine). However, in practice it makes no sense // to run copy in a goroutine as there is no way to (safely) know // when it is finished. // - panic: the error message would appear in the parent goroutine. // But because `go panic("err")` would halt the program anyway // (there is no recover), panicking right away would give the same // behavior as creating a goroutine, switching the scheduler to that // goroutine, and panicking there. So this optimization seems // correct. // - recover: because it runs in a new goroutine, it is never a // deferred function. Thus this is a no-op. if builtin.Name() == "recover" { // This is a no-op, even in a deferred function: // go recover() return } var argTypes []types.Type var argValues []llvm.Value for _, arg := range instr.Call.Args { argTypes = append(argTypes, arg.Type()) argValues = append(argValues, b.getValue(arg, getPos(instr))) } b.createBuiltin(argTypes, argValues, builtin.Name(), instr.Pos()) return } // Get all function parameters to pass to the goroutine. var params []llvm.Value for _, param := range instr.Call.Args { params = append(params, b.expandFormalParam(b.getValue(param, getPos(instr)))...) } var prefix string var funcPtr llvm.Value var funcType llvm.Type hasContext := false if callee := instr.Call.StaticCallee(); callee != nil { // Static callee is known. This makes it easier to start a new // goroutine. var context llvm.Value switch value := instr.Call.Value.(type) { case *ssa.Function: // Goroutine call is regular function call. No context is necessary. case *ssa.MakeClosure: // A goroutine call on a func value, but the callee is trivial to find. For // example: immediately applied functions. funcValue := b.getValue(value, getPos(instr)) context = b.extractFuncContext(funcValue) default: panic("StaticCallee returned an unexpected value") } if !context.IsNil() { params = append(params, context) // context parameter hasContext = true } funcType, funcPtr = b.getFunction(callee) } else if instr.Call.IsInvoke() { // This is a method call on an interface value. itf := b.getValue(instr.Call.Value, getPos(instr)) itfTypeCode := b.CreateExtractValue(itf, 0, "") itfValue := b.CreateExtractValue(itf, 1, "") funcPtr = b.getInvokeFunction(&instr.Call) funcType = funcPtr.GlobalValueType() params = append([]llvm.Value{itfValue}, params...) // start with receiver params = append(params, itfTypeCode) // end with typecode } else { // This is a function pointer. // At the moment, two extra params are passed to the newly started // goroutine: // * The function context, for closures. // * The function pointer (for tasks). var context llvm.Value funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value, getPos(instr))) funcType = b.getLLVMFunctionType(instr.Call.Value.Type().Underlying().(*types.Signature)) params = append(params, context, funcPtr) hasContext = true prefix = b.fn.RelString(nil) } paramBundle := b.emitPointerPack(params) var stackSize llvm.Value callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, false, instr.Pos()) if b.AutomaticStackSize { // The stack size is not known until after linking. Call a dummy // function that will be replaced with a load from a special ELF // section that contains the stack size (and is modified after // linking). stackSizeFnType, stackSizeFn := b.getFunction(b.program.ImportedPackage("internal/task").Members["getGoroutineStackSize"].(*ssa.Function)) stackSize = b.createCall(stackSizeFnType, stackSizeFn, []llvm.Value{callee, llvm.Undef(b.dataPtrType)}, "stacksize") } else { // The stack size is fixed at compile time. By emitting it here as a // constant, it can be optimized. if (b.Scheduler == "tasks" || b.Scheduler == "asyncify") && b.DefaultStackSize == 0 { b.addError(instr.Pos(), "default stack size for goroutines is not set") } stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false) } fnType, start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function)) b.createCall(fnType, start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.dataPtrType)}, "") } // Create an exported wrapper function for functions with the //go:wasmexport // pragma. This wrapper function is quite complex when the scheduler is enabled: // it needs to start a new goroutine each time the exported function is called. func (b *builder) createWasmExport() { pos := b.info.wasmExportPos if b.info.exported { // //export really shouldn't be used anymore when //go:wasmexport is // available, because //go:wasmexport is much better defined. b.addError(pos, "cannot use //export and //go:wasmexport at the same time") return } const suffix = "#wasmexport" // Declare the exported function. paramTypes := b.llvmFnType.ParamTypes() exportedFnType := llvm.FunctionType(b.llvmFnType.ReturnType(), paramTypes[:len(paramTypes)-1], false) exportedFn := llvm.AddFunction(b.mod, b.fn.RelString(nil)+suffix, exportedFnType) b.addStandardAttributes(exportedFn) llvmutil.AppendToGlobal(b.mod, "llvm.used", exportedFn) exportedFn.AddFunctionAttr(b.ctx.CreateStringAttribute("wasm-export-name", b.info.wasmExport)) // Create a builder for this wrapper function. builder := newBuilder(b.compilerContext, b.ctx.NewBuilder(), b.fn) defer builder.Dispose() // Define this function as a separate function in DWARF if b.Debug { if b.fn.Syntax() != nil { // Create debug info file if needed. pos := b.program.Fset.Position(pos) builder.difunc = builder.attachDebugInfoRaw(b.fn, exportedFn, suffix, pos.Filename, pos.Line) } builder.setDebugLocation(pos) } // Create a single basic block inside of it. bb := llvm.AddBasicBlock(exportedFn, "entry") builder.SetInsertPointAtEnd(bb) // Insert an assertion to make sure this //go:wasmexport function is not // called at a time when it is not allowed (for example, before the runtime // is initialized). builder.createRuntimeCall("wasmExportCheckRun", nil, "") if b.Scheduler == "none" { // When the scheduler has been disabled, this is really trivial: just // call the function. params := exportedFn.Params() params = append(params, llvm.ConstNull(b.dataPtrType)) // context parameter retval := builder.CreateCall(b.llvmFnType, b.llvmFn, params, "") if b.fn.Signature.Results() == nil { builder.CreateRetVoid() } else { builder.CreateRet(retval) } } else { // The scheduler is enabled, so we need to start a new goroutine, wait // for it to complete, and read the result value. // Build a function that looks like this: // // func foo#wasmexport(param0, param1, ..., paramN) { // var state *stateStruct // // // 'done' must be explicitly initialized ('state' is not zeroed) // state.done = false // // // store the parameters in the state object // state.param0 = param0 // state.param1 = param1 // ... // state.paramN = paramN // // // create a goroutine and push it to the runqueue // task.start(uintptr(gowrapper), &state) // // // run the scheduler // runtime.wasmExportRun(&state.done) // // // if there is a return value, load it and return // return state.result // } hasReturn := b.fn.Signature.Results() != nil // Build the state struct type. // It stores the function parameters, the 'done' flag, and reserves // space for a return value if needed. stateFields := exportedFnType.ParamTypes() numParams := len(stateFields) stateFields = append(stateFields, b.ctx.Int1Type()) // 'done' field if hasReturn { stateFields = append(stateFields, b.llvmFnType.ReturnType()) } stateStruct := b.ctx.StructType(stateFields, false) // Allocate the state struct on the stack. statePtr := builder.CreateAlloca(stateStruct, "status") // Initialize the 'done' field. doneGEP := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{ llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), uint64(numParams), false), }, "done.gep") builder.CreateStore(llvm.ConstNull(b.ctx.Int1Type()), doneGEP) // Store all parameters in the state object. for i, param := range exportedFn.Params() { gep := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{ llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false), }, "") builder.CreateStore(param, gep) } // Create a new goroutine and add it to the runqueue. wrapper := b.createGoroutineStartWrapper(b.llvmFnType, b.llvmFn, "", false, true, pos) stackSize := llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false) taskStartFnType, taskStartFn := builder.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function)) builder.createCall(taskStartFnType, taskStartFn, []llvm.Value{wrapper, statePtr, stackSize, llvm.Undef(b.dataPtrType)}, "") // Run the scheduler. builder.createRuntimeCall("wasmExportRun", []llvm.Value{doneGEP}, "") // Read the return value (if any) and return to the caller of the // //go:wasmexport function. if hasReturn { gep := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{ llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), uint64(numParams)+1, false), }, "") retval := builder.CreateLoad(b.llvmFnType.ReturnType(), gep, "retval") builder.CreateRet(retval) } else { builder.CreateRetVoid() } } } // createGoroutineStartWrapper creates a wrapper for the task-based // implementation of goroutines. For example, to call a function like this: // // func add(x, y int) int { ... } // // It creates a wrapper like this: // // func add$gowrapper(ptr *unsafe.Pointer) { // args := (*struct{ // x, y int // })(ptr) // add(args.x, args.y) // } // // This is useful because the task-based goroutine start implementation only // allows a single (pointer) argument to the newly started goroutine. Also, it // ignores the return value because newly started goroutines do not have a // return value. // // The hasContext parameter indicates whether the context parameter (the second // to last parameter of the function) is used for this wrapper. If hasContext is // false, the parameter bundle is assumed to have no context parameter and undef // is passed instead. func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.Value, prefix string, hasContext, isWasmExport bool, pos token.Pos) llvm.Value { var wrapper llvm.Value b := &builder{ compilerContext: c, Builder: c.ctx.NewBuilder(), } defer b.Dispose() var deadlock llvm.Value var deadlockType llvm.Type if c.Scheduler == "asyncify" { deadlockType, deadlock = c.getFunction(c.program.ImportedPackage("runtime").Members["deadlock"].(*ssa.Function)) } if !fn.IsAFunction().IsNil() { // See whether this wrapper has already been created. If so, return it. name := fn.Name() wrapperName := name + "$gowrapper" if isWasmExport { wrapperName += "-wasmexport" } wrapper = c.mod.NamedFunction(wrapperName) if !wrapper.IsNil() { return llvm.ConstPtrToInt(wrapper, c.uintptrType) } // Create the wrapper. wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType}, false) wrapper = llvm.AddFunction(c.mod, wrapperName, wrapperType) c.addStandardAttributes(wrapper) wrapper.SetLinkage(llvm.LinkOnceODRLinkage) wrapper.SetUnnamedAddr(true) wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name)) entry := c.ctx.AddBasicBlock(wrapper, "entry") b.SetInsertPointAtEnd(entry) if c.Debug { pos := c.program.Fset.Position(pos) diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{ File: c.getDIFile(pos.Filename), Parameters: nil, // do not show parameters in debugger Flags: 0, // ? }) difunc := c.dibuilder.CreateFunction(c.getDIFile(pos.Filename), llvm.DIFunction{ Name: "", File: c.getDIFile(pos.Filename), Line: pos.Line, Type: diFuncType, LocalToUnit: true, IsDefinition: true, ScopeLine: 0, Flags: llvm.FlagPrototyped, Optimized: true, }) wrapper.SetSubprogram(difunc) b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) } if !isWasmExport { // Regular 'go' instruction. // Create the list of params for the call. paramTypes := fnType.ParamTypes() if !hasContext { paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter } params := b.emitPointerUnpack(wrapper.Param(0), paramTypes) if !hasContext { params = append(params, llvm.Undef(c.dataPtrType)) // add dummy context parameter } // Create the call. b.CreateCall(fnType, fn, params, "") if c.Scheduler == "asyncify" { b.CreateCall(deadlockType, deadlock, []llvm.Value{ llvm.Undef(c.dataPtrType), }, "") } } else { // Goroutine started from a //go:wasmexport pragma. // The function looks like this: // // func foo$gowrapper-wasmexport(state *stateStruct) { // // load values // param0 := state.params[0] // param1 := state.params[1] // // // call wrapped functions // result := foo(param0, param1, ...) // // // store result value (if there is any) // state.result = result // // // finish exported function // state.done = true // runtime.wasmExportExit() // } // // The state object here looks like: // // struct state { // param0 // param1 // param* // etc // done bool // result returnType // } returnType := fnType.ReturnType() hasReturn := returnType != b.ctx.VoidType() statePtr := wrapper.Param(0) // Create the state struct (it must match the type in createWasmExport). stateFields := fnType.ParamTypes() numParams := len(stateFields) - 1 stateFields = stateFields[:numParams:numParams] // strip 'context' parameter stateFields = append(stateFields, c.ctx.Int1Type()) // 'done' bool if hasReturn { stateFields = append(stateFields, returnType) } stateStruct := b.ctx.StructType(stateFields, false) // Extract parameters from the state object, and call the function // that's being wrapped. var callParams []llvm.Value for i := 0; i < numParams; i++ { gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{ llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false), }, "") param := b.CreateLoad(stateFields[i], gep, "") callParams = append(callParams, param) } callParams = append(callParams, llvm.ConstNull(c.dataPtrType)) // add 'context' parameter result := b.CreateCall(fnType, fn, callParams, "") // Store the return value back into the shared state. // Unlike regular goroutines, these special //go:wasmexport // goroutines can return a value. if hasReturn { gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{ llvm.ConstInt(c.ctx.Int32Type(), 0, false), llvm.ConstInt(c.ctx.Int32Type(), uint64(numParams)+1, false), }, "result.ptr") b.CreateStore(result, gep) } // Mark this function as having finished executing. // This is important so the runtime knows the exported function // didn't block. doneGEP := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{ llvm.ConstInt(c.ctx.Int32Type(), 0, false), llvm.ConstInt(c.ctx.Int32Type(), uint64(numParams), false), }, "done.gep") b.CreateStore(llvm.ConstInt(b.ctx.Int1Type(), 1, false), doneGEP) // Call back into the runtime. This will exit the goroutine, switch // back to the scheduler, which will in turn return from the // //go:wasmexport function. b.createRuntimeCall("wasmExportExit", nil, "") } } else { // For a function pointer like this: // // var funcPtr func(x, y int) int // // A wrapper like the following is created: // // func .gowrapper(ptr *unsafe.Pointer) { // args := (*struct{ // x, y int // fn func(x, y int) int // })(ptr) // args.fn(x, y) // } // // With a bit of luck, identical wrapper functions like these can be // merged into one. // Create the wrapper. wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType}, false) wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType) c.addStandardAttributes(wrapper) wrapper.SetLinkage(llvm.LinkOnceODRLinkage) wrapper.SetUnnamedAddr(true) wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", "")) entry := c.ctx.AddBasicBlock(wrapper, "entry") b.SetInsertPointAtEnd(entry) if c.Debug { pos := c.program.Fset.Position(pos) diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{ File: c.getDIFile(pos.Filename), Parameters: nil, // do not show parameters in debugger Flags: 0, // ? }) difunc := c.dibuilder.CreateFunction(c.getDIFile(pos.Filename), llvm.DIFunction{ Name: "", File: c.getDIFile(pos.Filename), Line: pos.Line, Type: diFuncType, LocalToUnit: true, IsDefinition: true, ScopeLine: 0, Flags: llvm.FlagPrototyped, Optimized: true, }) wrapper.SetSubprogram(difunc) b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) } // Get the list of parameters, with the extra parameters at the end. paramTypes := fnType.ParamTypes() paramTypes = append(paramTypes, fn.Type()) // the last element is the function pointer params := b.emitPointerUnpack(wrapper.Param(0), paramTypes) // Get the function pointer. fnPtr := params[len(params)-1] params = params[:len(params)-1] // Create the call. b.CreateCall(fnType, fnPtr, params, "") if c.Scheduler == "asyncify" { b.CreateCall(deadlockType, deadlock, []llvm.Value{ llvm.Undef(c.dataPtrType), }, "") } } if c.Scheduler == "asyncify" { // The goroutine was terminated via deadlock. b.CreateUnreachable() } else { // Finish the function. Every basic block must end in a terminator, and // because goroutines never return a value we can simply return void. b.CreateRetVoid() } // Return a ptrtoint of the wrapper, not the function itself. return llvm.ConstPtrToInt(wrapper, c.uintptrType) } ================================================ FILE: compiler/inlineasm.go ================================================ package compiler // This file implements inline asm support by calling special functions. import ( "fmt" "go/constant" "go/token" "regexp" "strconv" "strings" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) // This is a compiler builtin, which emits a piece of inline assembly with no // operands or return values. It is useful for trivial instructions, like wfi in // ARM or sleep in AVR. // // func Asm(asm string) // // The provided assembly must be a constant. func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) { // Magic function: insert inline assembly instead of calling it. fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{}, false) asm := constant.StringVal(args[0].(*ssa.Const).Value) target := llvm.InlineAsm(fnType, asm, "", true, false, 0, false) return b.CreateCall(fnType, target, nil, ""), nil } // This is a compiler builtin, which allows assembly to be called in a flexible // way. // // func AsmFull(asm string, regs map[string]interface{}) uintptr // // The asm parameter must be a constant string. The regs parameter must be // provided immediately. For example: // // arm.AsmFull( // "str {value}, [{result}]", // map[string]interface{}{ // "value": 1, // "result": uintptr(unsafe.Pointer(&dest)), // }) func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) { asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value) registers := map[string]llvm.Value{} if registerMap, ok := instr.Args[1].(*ssa.MakeMap); ok { for _, r := range *registerMap.Referrers() { switch r := r.(type) { case *ssa.DebugRef: // ignore case *ssa.MapUpdate: if r.Block() != registerMap.Block() { return llvm.Value{}, b.makeError(instr.Pos(), "register value map must be created in the same basic block") } key := constant.StringVal(r.Key.(*ssa.Const).Value) registers[key] = b.getValue(r.Value.(*ssa.MakeInterface).X, getPos(instr)) case *ssa.Call: if r.Common() == instr { break } default: return llvm.Value{}, b.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String()) } } } // TODO: handle dollar signs in asm string registerNumbers := map[string]int{} var err error argTypes := []llvm.Type{} args := []llvm.Value{} constraints := []string{} hasOutput := false asmString = regexp.MustCompile(`\{\}`).ReplaceAllStringFunc(asmString, func(s string) string { hasOutput = true return "$0" }) if hasOutput { constraints = append(constraints, "=&r") registerNumbers[""] = 0 } asmString = regexp.MustCompile(`\{[a-zA-Z]+\}`).ReplaceAllStringFunc(asmString, func(s string) string { // TODO: skip strings like {r4} etc. that look like ARM push/pop // instructions. name := s[1 : len(s)-1] if _, ok := registers[name]; !ok { if err == nil { err = b.makeError(instr.Pos(), "unknown register name: "+name) } return s } if _, ok := registerNumbers[name]; !ok { registerNumbers[name] = len(registerNumbers) argTypes = append(argTypes, registers[name].Type()) args = append(args, registers[name]) switch registers[name].Type().TypeKind() { case llvm.IntegerTypeKind: constraints = append(constraints, "r") case llvm.PointerTypeKind: // Memory references require a type starting with LLVM 14, // probably as a preparation for opaque pointers. err = b.makeError(instr.Pos(), "support for pointer operands was dropped in TinyGo 0.23") return s default: err = b.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name) return s } } return fmt.Sprintf("${%v}", registerNumbers[name]) }) if err != nil { return llvm.Value{}, err } var outputType llvm.Type if hasOutput { outputType = b.uintptrType } else { outputType = b.ctx.VoidType() } fnType := llvm.FunctionType(outputType, argTypes, false) target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0, false) result := b.CreateCall(fnType, target, args, "") if hasOutput { return result, nil } else { // Make sure we return something valid. return llvm.ConstInt(b.uintptrType, 0, false), nil } } // This is a compiler builtin which emits an inline SVCall instruction. It can // be one of: // // func SVCall0(num uintptr) uintptr // func SVCall1(num uintptr, a1 interface{}) uintptr // func SVCall2(num uintptr, a1, a2 interface{}) uintptr // func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr // func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr // // The num parameter must be a constant. All other parameters may be any scalar // value supported by LLVM inline assembly. func (b *builder) emitSVCall(args []ssa.Value, pos token.Pos) (llvm.Value, error) { num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value) llvmArgs := []llvm.Value{} argTypes := []llvm.Type{} asm := "svc #" + strconv.FormatUint(num, 10) constraints := "={r0}" for i, arg := range args[1:] { arg = arg.(*ssa.MakeInterface).X if i == 0 { constraints += ",0" } else { constraints += ",{r" + strconv.Itoa(i) + "}" } llvmValue := b.getValue(arg, pos) llvmArgs = append(llvmArgs, llvmValue) argTypes = append(argTypes, llvmValue.Type()) } // Implement the ARM calling convention by marking r1-r3 as // clobbered. r0 is used as an output register so doesn't have to be // marked as clobbered. constraints += ",~{r1},~{r2},~{r3}" fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false) return b.CreateCall(fnType, target, llvmArgs, ""), nil } // This is a compiler builtin which emits an inline SVCall instruction. It can // be one of: // // func SVCall0(num uintptr) uintptr // func SVCall1(num uintptr, a1 interface{}) uintptr // func SVCall2(num uintptr, a1, a2 interface{}) uintptr // func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr // func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr // // The num parameter must be a constant. All other parameters may be any scalar // value supported by LLVM inline assembly. // Same as emitSVCall but for AArch64 func (b *builder) emitSV64Call(args []ssa.Value, pos token.Pos) (llvm.Value, error) { num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value) llvmArgs := []llvm.Value{} argTypes := []llvm.Type{} asm := "svc #" + strconv.FormatUint(num, 10) constraints := "={x0}" for i, arg := range args[1:] { arg = arg.(*ssa.MakeInterface).X if i == 0 { constraints += ",0" } else { constraints += ",{x" + strconv.Itoa(i) + "}" } llvmValue := b.getValue(arg, pos) llvmArgs = append(llvmArgs, llvmValue) argTypes = append(argTypes, llvmValue.Type()) } // Implement the ARM64 calling convention by marking x1-x7 as // clobbered. x0 is used as an output register so doesn't have to be // marked as clobbered. constraints += ",~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7}" fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false) return b.CreateCall(fnType, target, llvmArgs, ""), nil } // This is a compiler builtin which emits CSR instructions. It can be one of: // // func (csr CSR) Get() uintptr // func (csr CSR) Set(uintptr) // func (csr CSR) SetBits(uintptr) uintptr // func (csr CSR) ClearBits(uintptr) uintptr // // The csr parameter (method receiver) must be a constant. Other parameter can // be any value. func (b *builder) emitCSROperation(call *ssa.CallCommon) (llvm.Value, error) { csrConst, ok := call.Args[0].(*ssa.Const) if !ok { return llvm.Value{}, b.makeError(call.Pos(), "CSR must be constant") } csr := csrConst.Uint64() switch name := call.StaticCallee().Name(); name { case "Get": // Note that this instruction may have side effects, and thus must be // marked as such. fnType := llvm.FunctionType(b.uintptrType, nil, false) asm := fmt.Sprintf("csrr $0, %d", csr) target := llvm.InlineAsm(fnType, asm, "=r", true, false, 0, false) return b.CreateCall(fnType, target, nil, ""), nil case "Set": fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.uintptrType}, false) asm := fmt.Sprintf("csrw %d, $0", csr) target := llvm.InlineAsm(fnType, asm, "r", true, false, 0, false) return b.CreateCall(fnType, target, []llvm.Value{b.getValue(call.Args[1], getPos(call))}, ""), nil case "SetBits": // Note: it may be possible to optimize this to csrrsi in many cases. fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType}, false) asm := fmt.Sprintf("csrrs $0, %d, $1", csr) target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0, false) return b.CreateCall(fnType, target, []llvm.Value{b.getValue(call.Args[1], getPos(call))}, ""), nil case "ClearBits": // Note: it may be possible to optimize this to csrrci in many cases. fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType}, false) asm := fmt.Sprintf("csrrc $0, %d, $1", csr) target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0, false) return b.CreateCall(fnType, target, []llvm.Value{b.getValue(call.Args[1], getPos(call))}, ""), nil default: return llvm.Value{}, b.makeError(call.Pos(), "unknown CSR operation: "+name) } } // Implement runtime/interrupt.Checkpoint.Save. It needs to be implemented // directly at the call site. If it isn't implemented directly at the call site // (but instead through a function call), it might result in an overwritten // stack in the non-jump return case. func (b *builder) createInterruptCheckpoint(ptr ssa.Value) llvm.Value { addr := b.getValue(ptr, ptr.Pos()) b.createNilCheck(ptr, addr, "deref") stackPointer := b.readStackPointer() b.CreateStore(stackPointer, addr) return b.createCheckpoint(addr) } ================================================ FILE: compiler/interface.go ================================================ package compiler // This file transforms interface-related instructions (*ssa.MakeInterface, // *ssa.TypeAssert, calls on interface types) to an intermediate IR form, to be // lowered to the final form by the interface lowering pass. See // interface-lowering.go for more details. import ( "encoding/binary" "fmt" "go/token" "go/types" "strconv" "strings" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) // Type kinds for basic types. // They must match the constants for the Kind type in src/reflect/type.go. var basicTypes = [...]uint8{ types.Bool: 1, types.Int: 2, types.Int8: 3, types.Int16: 4, types.Int32: 5, types.Int64: 6, types.Uint: 7, types.Uint8: 8, types.Uint16: 9, types.Uint32: 10, types.Uint64: 11, types.Uintptr: 12, types.Float32: 13, types.Float64: 14, types.Complex64: 15, types.Complex128: 16, types.String: 17, types.UnsafePointer: 18, } // These must also match the constants for the Kind type in src/reflect/type.go. const ( typeKindChan = 19 typeKindInterface = 20 typeKindPointer = 21 typeKindSlice = 22 typeKindArray = 23 typeKindSignature = 24 typeKindMap = 25 typeKindStruct = 26 ) // Flags stored in the first byte of the struct field byte array. Must be kept // up to date with src/reflect/type.go. const ( structFieldFlagAnonymous = 1 << iota structFieldFlagHasTag structFieldFlagIsExported structFieldFlagIsEmbedded ) type reflectChanDir int const ( refRecvDir reflectChanDir = 1 << iota // <-chan refSendDir // chan<- refBothDir = refRecvDir | refSendDir // chan ) // createMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction. // It tries to put the type in the interface value, but if that's not possible, // it will do an allocation of the right size and put that in the interface // value field. // // An interface value is a {typecode, value} tuple named runtime._interface. func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value { itfValue := b.emitPointerPack([]llvm.Value{val}) itfType := b.getTypeCode(typ) itf := llvm.Undef(b.getLLVMRuntimeType("_interface")) itf = b.CreateInsertValue(itf, itfType, 0, "") itf = b.CreateInsertValue(itf, itfValue, 1, "") return itf } // extractValueFromInterface extract the value from an interface value // (runtime._interface) under the assumption that it is of the type given in // llvmType. The behavior is undefined if the interface is nil or llvmType // doesn't match the underlying type of the interface. func (b *builder) extractValueFromInterface(itf llvm.Value, llvmType llvm.Type) llvm.Value { valuePtr := b.CreateExtractValue(itf, 1, "typeassert.value.ptr") return b.emitPointerUnpack(valuePtr, []llvm.Type{llvmType})[0] } func (c *compilerContext) pkgPathPtr(pkgpath string) llvm.Value { pkgpathName := "reflect/types.type.pkgpath.empty" if pkgpath != "" { pkgpathName = "reflect/types.type.pkgpath:" + pkgpath } pkgpathGlobal := c.mod.NamedGlobal(pkgpathName) if pkgpathGlobal.IsNil() { pkgpathInitializer := c.ctx.ConstString(pkgpath+"\x00", false) pkgpathGlobal = llvm.AddGlobal(c.mod, pkgpathInitializer.Type(), pkgpathName) pkgpathGlobal.SetInitializer(pkgpathInitializer) pkgpathGlobal.SetAlignment(1) pkgpathGlobal.SetUnnamedAddr(true) pkgpathGlobal.SetLinkage(llvm.LinkOnceODRLinkage) pkgpathGlobal.SetGlobalConstant(true) } pkgPathPtr := llvm.ConstGEP(pkgpathGlobal.GlobalValueType(), pkgpathGlobal, []llvm.Value{ llvm.ConstInt(c.ctx.Int32Type(), 0, false), llvm.ConstInt(c.ctx.Int32Type(), 0, false), }) return pkgPathPtr } // getTypeCode returns a reference to a type code. // A type code is a pointer to a constant global that describes the type. // This function returns a pointer to the 'kind' field (which might not be the // first field in the struct). func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { // Resolve alias types: alias types are resolved at compile time. typ = types.Unalias(typ) ms := c.program.MethodSets.MethodSet(typ) hasMethodSet := ms.Len() != 0 _, isInterface := typ.Underlying().(*types.Interface) if isInterface { hasMethodSet = false } // As defined in https://pkg.go.dev/reflect#Type: // NumMethod returns the number of methods accessible using Method. // For a non-interface type, it returns the number of exported methods. // For an interface type, it returns the number of exported and unexported methods. var numMethods int for i := 0; i < ms.Len(); i++ { if isInterface || ms.At(i).Obj().Exported() { numMethods++ } } // Short-circuit all the global pointer logic here for pointers to pointers. if typ, ok := typ.(*types.Pointer); ok { if _, ok := typ.Elem().(*types.Pointer); ok { // For a pointer to a pointer, we just increase the pointer by 1 ptr := c.getTypeCode(typ.Elem()) // if the type is already *****T or higher, we can't make it. if typstr := typ.String(); strings.HasPrefix(typstr, "*****") { c.addError(token.NoPos, fmt.Sprintf("too many levels of pointers for typecode: %s", typstr)) } return llvm.ConstGEP(c.ctx.Int8Type(), ptr, []llvm.Value{ llvm.ConstInt(c.ctx.Int32Type(), 1, false), }) } } typeCodeName, isLocal := getTypeCodeName(typ) globalName := "reflect/types.type:" + typeCodeName var global llvm.Value if isLocal { // This type is a named type inside a function, like this: // // func foo() any { // type named int // return named(0) // } if obj := c.interfaceTypes.At(typ); obj != nil { global = obj.(llvm.Value) } } else { // Regular type (named or otherwise). global = c.mod.NamedGlobal(globalName) } if global.IsNil() { var typeFields []llvm.Value // Define the type fields. These must match the structs in // src/reflect/type.go (ptrType, arrayType, etc). See the comment at the // top of src/reflect/type.go for more information on the layout of these structs. typeFieldTypes := []*types.Var{ types.NewVar(token.NoPos, nil, "kind", types.Typ[types.Int8]), } switch typ := typ.(type) { case *types.Basic: typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), ) case *types.Named: name := typ.Obj().Name() var pkgname string if pkg := typ.Obj().Pkg(); pkg != nil { pkgname = pkg.Name() } typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]), types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "underlying", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "pkgpath", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "name", types.NewArray(types.Typ[types.Int8], int64(len(pkgname)+1+len(name)+1))), ) case *types.Chan: typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]), // reuse for select chan direction types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]), ) case *types.Slice: typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]), types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]), ) case *types.Pointer: typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]), types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]), ) case *types.Array: typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]), types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "length", types.Typ[types.Uintptr]), types.NewVar(token.NoPos, nil, "sliceOf", types.Typ[types.UnsafePointer]), ) case *types.Map: typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]), types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "keyType", types.Typ[types.UnsafePointer]), ) case *types.Struct: typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]), types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "pkgpath", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "size", types.Typ[types.Uint32]), types.NewVar(token.NoPos, nil, "numFields", types.Typ[types.Uint16]), types.NewVar(token.NoPos, nil, "fields", types.NewArray(c.getRuntimeType("structField"), int64(typ.NumFields()))), ) case *types.Interface: typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), ) // TODO: methods case *types.Signature: typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), ) // TODO: signature params and return values } if hasMethodSet { // This method set is appended at the start of the struct. It is // removed in the interface lowering pass. // TODO: don't remove these and instead do what upstream Go is doing // instead. See: https://research.swtch.com/interfaces. This can // likely be optimized in LLVM using // https://llvm.org/docs/TypeMetadata.html. typeFieldTypes = append([]*types.Var{ types.NewVar(token.NoPos, nil, "methodSet", types.Typ[types.UnsafePointer]), }, typeFieldTypes...) } globalType := types.NewStruct(typeFieldTypes, nil) global = llvm.AddGlobal(c.mod, c.getLLVMType(globalType), globalName) if isLocal { c.interfaceTypes.Set(typ, global) } metabyte := getTypeKind(typ) // Precompute these so we don't have to calculate them at runtime. if types.Comparable(typ) { metabyte |= 1 << 6 } if hashmapIsBinaryKey(typ) { metabyte |= 1 << 7 } switch typ := typ.(type) { case *types.Basic: typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))} case *types.Named: name := typ.Obj().Name() var pkgpath string var pkgname string if pkg := typ.Obj().Pkg(); pkg != nil { pkgpath = pkg.Path() pkgname = pkg.Name() } pkgPathPtr := c.pkgPathPtr(pkgpath) typeFields = []llvm.Value{ llvm.ConstInt(c.ctx.Int16Type(), uint64(numMethods), false), // numMethods c.getTypeCode(types.NewPointer(typ)), // ptrTo c.getTypeCode(typ.Underlying()), // underlying pkgPathPtr, // pkgpath pointer c.ctx.ConstString(pkgname+"."+name+"\x00", false), // name } metabyte |= 1 << 5 // "named" flag case *types.Chan: var dir reflectChanDir switch typ.Dir() { case types.SendRecv: dir = refBothDir case types.RecvOnly: dir = refRecvDir case types.SendOnly: dir = refSendDir } typeFields = []llvm.Value{ llvm.ConstInt(c.ctx.Int16Type(), uint64(dir), false), // actually channel direction c.getTypeCode(types.NewPointer(typ)), // ptrTo c.getTypeCode(typ.Elem()), // elementType } case *types.Slice: typeFields = []llvm.Value{ llvm.ConstInt(c.ctx.Int16Type(), 0, false), // numMethods c.getTypeCode(types.NewPointer(typ)), // ptrTo c.getTypeCode(typ.Elem()), // elementType } case *types.Pointer: typeFields = []llvm.Value{ llvm.ConstInt(c.ctx.Int16Type(), uint64(numMethods), false), // numMethods c.getTypeCode(typ.Elem()), } case *types.Array: typeFields = []llvm.Value{ llvm.ConstInt(c.ctx.Int16Type(), 0, false), // numMethods c.getTypeCode(types.NewPointer(typ)), // ptrTo c.getTypeCode(typ.Elem()), // elementType llvm.ConstInt(c.uintptrType, uint64(typ.Len()), false), // length c.getTypeCode(types.NewSlice(typ.Elem())), // slicePtr } case *types.Map: typeFields = []llvm.Value{ llvm.ConstInt(c.ctx.Int16Type(), 0, false), // numMethods c.getTypeCode(types.NewPointer(typ)), // ptrTo c.getTypeCode(typ.Elem()), // elem c.getTypeCode(typ.Key()), // key } case *types.Struct: var pkgpath string if typ.NumFields() > 0 { if pkg := typ.Field(0).Pkg(); pkg != nil { pkgpath = pkg.Path() } } pkgPathPtr := c.pkgPathPtr(pkgpath) llvmStructType := c.getLLVMType(typ) size := c.targetData.TypeStoreSize(llvmStructType) typeFields = []llvm.Value{ llvm.ConstInt(c.ctx.Int16Type(), uint64(numMethods), false), // numMethods c.getTypeCode(types.NewPointer(typ)), // ptrTo pkgPathPtr, llvm.ConstInt(c.ctx.Int32Type(), uint64(size), false), // size llvm.ConstInt(c.ctx.Int16Type(), uint64(typ.NumFields()), false), // numFields } structFieldType := c.getLLVMRuntimeType("structField") var fields []llvm.Value for i := 0; i < typ.NumFields(); i++ { field := typ.Field(i) offset := c.targetData.ElementOffset(llvmStructType, i) var flags uint8 if field.Anonymous() { flags |= structFieldFlagAnonymous } if typ.Tag(i) != "" { flags |= structFieldFlagHasTag } if token.IsExported(field.Name()) { flags |= structFieldFlagIsExported } if field.Embedded() { flags |= structFieldFlagIsEmbedded } var offsBytes [binary.MaxVarintLen32]byte offLen := binary.PutUvarint(offsBytes[:], offset) data := string(flags) + string(offsBytes[:offLen]) + field.Name() + "\x00" if typ.Tag(i) != "" { if len(typ.Tag(i)) > 0xff { c.addError(field.Pos(), fmt.Sprintf("struct tag is %d bytes which is too long, max is 255", len(typ.Tag(i)))) } data += string([]byte{byte(len(typ.Tag(i)))}) + typ.Tag(i) } dataInitializer := c.ctx.ConstString(data, false) dataGlobal := llvm.AddGlobal(c.mod, dataInitializer.Type(), globalName+"."+field.Name()) dataGlobal.SetInitializer(dataInitializer) dataGlobal.SetAlignment(1) dataGlobal.SetUnnamedAddr(true) dataGlobal.SetLinkage(llvm.InternalLinkage) dataGlobal.SetGlobalConstant(true) fieldType := c.getTypeCode(field.Type()) fields = append(fields, llvm.ConstNamedStruct(structFieldType, []llvm.Value{ fieldType, llvm.ConstGEP(dataGlobal.GlobalValueType(), dataGlobal, []llvm.Value{ llvm.ConstInt(c.ctx.Int32Type(), 0, false), llvm.ConstInt(c.ctx.Int32Type(), 0, false), }), })) } typeFields = append(typeFields, llvm.ConstArray(structFieldType, fields)) case *types.Interface: typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))} // TODO: methods case *types.Signature: typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))} // TODO: params, return values, etc } // Prepend metadata byte. typeFields = append([]llvm.Value{ llvm.ConstInt(c.ctx.Int8Type(), uint64(metabyte), false), }, typeFields...) if hasMethodSet { typeFields = append([]llvm.Value{ c.getTypeMethodSet(typ), }, typeFields...) } alignment := c.targetData.TypeAllocSize(c.dataPtrType) if alignment < 4 { alignment = 4 } globalValue := c.ctx.ConstStruct(typeFields, false) global.SetInitializer(globalValue) if isLocal { global.SetLinkage(llvm.InternalLinkage) } else { global.SetLinkage(llvm.LinkOnceODRLinkage) } global.SetGlobalConstant(true) global.SetAlignment(int(alignment)) if c.Debug { file := c.getDIFile("") diglobal := c.dibuilder.CreateGlobalVariableExpression(file, llvm.DIGlobalVariableExpression{ Name: "type " + typ.String(), File: file, Line: 1, Type: c.getDIType(globalType), LocalToUnit: false, Expr: c.dibuilder.CreateExpression(nil), AlignInBits: uint32(alignment * 8), }) global.AddMetadata(0, diglobal) } } offset := uint64(0) if hasMethodSet { // The pointer to the method set is always the first element of the // global (if there is a method set). However, the pointer we return // should point to the 'kind' field not the method set. offset = 1 } return llvm.ConstGEP(global.GlobalValueType(), global, []llvm.Value{ llvm.ConstInt(c.ctx.Int32Type(), 0, false), llvm.ConstInt(c.ctx.Int32Type(), offset, false), }) } // getTypeKind returns the type kind for the given type, as defined by // reflect.Kind. func getTypeKind(t types.Type) uint8 { switch t := t.Underlying().(type) { case *types.Basic: return basicTypes[t.Kind()] case *types.Chan: return typeKindChan case *types.Interface: return typeKindInterface case *types.Pointer: return typeKindPointer case *types.Slice: return typeKindSlice case *types.Array: return typeKindArray case *types.Signature: return typeKindSignature case *types.Map: return typeKindMap case *types.Struct: return typeKindStruct default: panic("unknown type") } } var basicTypeNames = [...]string{ types.Bool: "bool", types.Int: "int", types.Int8: "int8", types.Int16: "int16", types.Int32: "int32", types.Int64: "int64", types.Uint: "uint", types.Uint8: "uint8", types.Uint16: "uint16", types.Uint32: "uint32", types.Uint64: "uint64", types.Uintptr: "uintptr", types.Float32: "float32", types.Float64: "float64", types.Complex64: "complex64", types.Complex128: "complex128", types.String: "string", types.UnsafePointer: "unsafe.Pointer", } // getTypeCodeName returns a name for this type that can be used in the // interface lowering pass to assign type codes as expected by the reflect // package. See getTypeCodeNum. func getTypeCodeName(t types.Type) (string, bool) { switch t := types.Unalias(t).(type) { case *types.Named: if t.Obj().Parent() != t.Obj().Pkg().Scope() { return "named:" + t.String() + "$local", true } return "named:" + t.String(), false case *types.Array: s, isLocal := getTypeCodeName(t.Elem()) return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + s, isLocal case *types.Basic: return "basic:" + basicTypeNames[t.Kind()], false case *types.Chan: s, isLocal := getTypeCodeName(t.Elem()) var dir string switch t.Dir() { case types.SendOnly: dir = "s:" case types.RecvOnly: dir = "r:" case types.SendRecv: dir = "sr:" } return "chan:" + dir + s, isLocal case *types.Interface: isLocal := false methods := make([]string, t.NumMethods()) for i := 0; i < t.NumMethods(); i++ { name := t.Method(i).Name() if !token.IsExported(name) { name = t.Method(i).Pkg().Path() + "." + name } s, local := getTypeCodeName(t.Method(i).Type()) if local { isLocal = true } methods[i] = name + ":" + s } return "interface:" + "{" + strings.Join(methods, ",") + "}", isLocal case *types.Map: keyType, keyLocal := getTypeCodeName(t.Key()) elemType, elemLocal := getTypeCodeName(t.Elem()) return "map:" + "{" + keyType + "," + elemType + "}", keyLocal || elemLocal case *types.Pointer: s, isLocal := getTypeCodeName(t.Elem()) return "pointer:" + s, isLocal case *types.Signature: isLocal := false params := make([]string, t.Params().Len()) for i := 0; i < t.Params().Len(); i++ { s, local := getTypeCodeName(t.Params().At(i).Type()) if local { isLocal = true } params[i] = s } results := make([]string, t.Results().Len()) for i := 0; i < t.Results().Len(); i++ { s, local := getTypeCodeName(t.Results().At(i).Type()) if local { isLocal = true } results[i] = s } return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}", isLocal case *types.Slice: s, isLocal := getTypeCodeName(t.Elem()) return "slice:" + s, isLocal case *types.Struct: elems := make([]string, t.NumFields()) isLocal := false for i := 0; i < t.NumFields(); i++ { embedded := "" if t.Field(i).Embedded() { embedded = "#" } s, local := getTypeCodeName(t.Field(i).Type()) if local { isLocal = true } elems[i] = embedded + t.Field(i).Name() + ":" + s if t.Tag(i) != "" { elems[i] += "`" + t.Tag(i) + "`" } } return "struct:" + "{" + strings.Join(elems, ",") + "}", isLocal default: panic("unknown type: " + t.String()) } } // getTypeMethodSet returns a reference (GEP) to a global method set. This // method set should be unreferenced after the interface lowering pass. func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value { globalName := typ.String() + "$methodset" global := c.mod.NamedGlobal(globalName) if global.IsNil() { ms := c.program.MethodSets.MethodSet(typ) // Create method set. var signatures, wrappers []llvm.Value for i := 0; i < ms.Len(); i++ { method := ms.At(i) signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func)) signatures = append(signatures, signatureGlobal) fn := c.program.MethodValue(method) llvmFnType, llvmFn := c.getFunction(fn) if llvmFn.IsNil() { // compiler error, so panic panic("cannot find function: " + c.getFunctionInfo(fn).linkName) } wrapper := c.getInterfaceInvokeWrapper(fn, llvmFnType, llvmFn) wrappers = append(wrappers, wrapper) } // Construct global value. globalValue := c.ctx.ConstStruct([]llvm.Value{ llvm.ConstInt(c.uintptrType, uint64(ms.Len()), false), llvm.ConstArray(c.dataPtrType, signatures), c.ctx.ConstStruct(wrappers, false), }, false) global = llvm.AddGlobal(c.mod, globalValue.Type(), globalName) global.SetInitializer(globalValue) global.SetGlobalConstant(true) global.SetUnnamedAddr(true) global.SetLinkage(llvm.LinkOnceODRLinkage) } return global } // getMethodSignatureName returns a unique name (that can be used as the name of // a global) for the given method. func (c *compilerContext) getMethodSignatureName(method *types.Func) string { signature := methodSignature(method) var globalName string if token.IsExported(method.Name()) { globalName = "reflect/methods." + signature } else { globalName = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods." + signature } return globalName } // getMethodSignature returns a global variable which is a reference to an // external *i8 indicating the indicating the signature of this method. It is // used during the interface lowering pass. func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value { globalName := c.getMethodSignatureName(method) signatureGlobal := c.mod.NamedGlobal(globalName) if signatureGlobal.IsNil() { // TODO: put something useful in these globals, such as the method // signature. Useful to one day implement reflect.Value.Method(n). signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName) signatureGlobal.SetInitializer(llvm.ConstInt(c.ctx.Int8Type(), 0, false)) signatureGlobal.SetLinkage(llvm.LinkOnceODRLinkage) signatureGlobal.SetGlobalConstant(true) signatureGlobal.SetAlignment(1) } return signatureGlobal } // createTypeAssert will emit the code for a typeassert, used in if statements // and in type switches (Go SSA does not have type switches, only if/else // chains). Note that even though the Go SSA does not contain type switches, // LLVM will recognize the pattern and make it a real switch in many cases. // // Type asserts on concrete types are trivial: just compare type numbers. Type // asserts on interfaces are more difficult, see the comments in the function. func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value { itf := b.getValue(expr.X, getPos(expr)) assertedType := b.getLLVMType(expr.AssertedType) actualTypeNum := b.CreateExtractValue(itf, 0, "interface.type") commaOk := llvm.Value{} if intf, ok := expr.AssertedType.Underlying().(*types.Interface); ok { if intf.Empty() { // intf is the empty interface => no methods // This type assertion always succeeds, so we can just set commaOk to true. commaOk = llvm.ConstInt(b.ctx.Int1Type(), 1, true) } else { // Type assert on interface type with methods. // This is a call to an interface type assert function. // The interface lowering pass will define this function by filling it // with a type switch over all concrete types that implement this // interface, and returning whether it's one of the matched types. // This is very different from how interface asserts are implemented in // the main Go compiler, where the runtime checks whether the type // implements each method of the interface. See: // https://research.swtch.com/interfaces fn := b.getInterfaceImplementsFunc(expr.AssertedType) commaOk = b.CreateCall(fn.GlobalValueType(), fn, []llvm.Value{actualTypeNum}, "") } } else { name, _ := getTypeCodeName(expr.AssertedType) globalName := "reflect/types.typeid:" + name assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName) if assertedTypeCodeGlobal.IsNil() { // Create a new typecode global. assertedTypeCodeGlobal = llvm.AddGlobal(b.mod, b.ctx.Int8Type(), globalName) assertedTypeCodeGlobal.SetGlobalConstant(true) } // Type assert on concrete type. // Call runtime.typeAssert, which will be lowered to a simple icmp or // const false in the interface lowering pass. commaOk = b.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode") } // Add 2 new basic blocks (that should get optimized away): one for the // 'ok' case and one for all instructions following this type assert. // This is necessary because we need to insert the casted value or the // nil value based on whether the assert was successful. Casting before // this check tells LLVM that it can use this value and may // speculatively dereference pointers before the check. This can lead to // a miscompilation resulting in a segfault at runtime. // Additionally, this is even required by the Go spec: a failed // typeassert should return a zero value, not an incorrectly casted // value. prevBlock := b.GetInsertBlock() okBlock := b.insertBasicBlock("typeassert.ok") nextBlock := b.insertBasicBlock("typeassert.next") b.currentBlockInfo.exit = nextBlock // adjust outgoing block for phi nodes b.CreateCondBr(commaOk, okBlock, nextBlock) // Retrieve the value from the interface if the type assert was // successful. b.SetInsertPointAtEnd(okBlock) var valueOk llvm.Value if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok { // Type assert on interface type. Easy: just return the same // interface value. valueOk = itf } else { // Type assert on concrete type. Extract the underlying type from // the interface (but only after checking it matches). valueOk = b.extractValueFromInterface(itf, assertedType) } b.CreateBr(nextBlock) // Continue after the if statement. b.SetInsertPointAtEnd(nextBlock) phi := b.CreatePHI(assertedType, "typeassert.value") phi.AddIncoming([]llvm.Value{llvm.ConstNull(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock}) if expr.CommaOk { tuple := b.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(b.ctx.Int1Type())}, false) // create empty tuple tuple = b.CreateInsertValue(tuple, phi, 0, "") // insert value tuple = b.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean return tuple } else { // This is kind of dirty as the branch above becomes mostly useless, // but hopefully this gets optimized away. b.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "") return phi } } // getMethodsString returns a string to be used in the "tinygo-methods" string // attribute for interface functions. func (c *compilerContext) getMethodsString(itf *types.Interface) string { methods := make([]string, itf.NumMethods()) for i := range methods { methods[i] = c.getMethodSignatureName(itf.Method(i)) } return strings.Join(methods, "; ") } // getInterfaceImplementsFunc returns a declared function that works as a type // switch. The interface lowering pass will define this function. func (c *compilerContext) getInterfaceImplementsFunc(assertedType types.Type) llvm.Value { s, _ := getTypeCodeName(assertedType.Underlying()) fnName := s + ".$typeassert" llvmFn := c.mod.NamedFunction(fnName) if llvmFn.IsNil() { llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.dataPtrType}, false) llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType) c.addStandardDeclaredAttributes(llvmFn) methods := c.getMethodsString(assertedType.Underlying().(*types.Interface)) llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods)) } return llvmFn } // getInvokeFunction returns the thunk to call the given interface method. The // thunk is declared, not defined: it will be defined by the interface lowering // pass. func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value { s, _ := getTypeCodeName(instr.Value.Type().Underlying()) fnName := s + "." + instr.Method.Name() + "$invoke" llvmFn := c.mod.NamedFunction(fnName) if llvmFn.IsNil() { sig := instr.Method.Type().(*types.Signature) var paramTuple []*types.Var for i := 0; i < sig.Params().Len(); i++ { paramTuple = append(paramTuple, sig.Params().At(i)) } paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.UnsafePointer])) llvmFnType := c.getLLVMFunctionType(types.NewSignature(sig.Recv(), types.NewTuple(paramTuple...), sig.Results(), false)) llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType) c.addStandardDeclaredAttributes(llvmFn) llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-invoke", c.getMethodSignatureName(instr.Method))) methods := c.getMethodsString(instr.Value.Type().Underlying().(*types.Interface)) llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods)) } return llvmFn } // getInterfaceInvokeWrapper returns a wrapper for the given method so it can be // invoked from an interface. The wrapper takes in a pointer to the underlying // value, dereferences or unpacks it if necessary, and calls the real method. // If the method to wrap has a pointer receiver, no wrapping is necessary and // the function is returned directly. func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType llvm.Type, llvmFn llvm.Value) llvm.Value { wrapperName := llvmFn.Name() + "$invoke" wrapper := c.mod.NamedFunction(wrapperName) if !wrapper.IsNil() { // Wrapper already created. Return it directly. return wrapper } // Get the expanded receiver type. receiverType := c.getLLVMType(fn.Signature.Recv().Type()) var expandedReceiverType []llvm.Type for _, info := range c.expandFormalParamType(receiverType, "", nil) { expandedReceiverType = append(expandedReceiverType, info.llvmType) } // Does this method even need any wrapping? if len(expandedReceiverType) == 1 && receiverType.TypeKind() == llvm.PointerTypeKind { // Nothing to wrap. // Casting a function signature to a different signature and calling it // with a receiver pointer bitcasted to *i8 (as done in calls on an // interface) is hopefully a safe (defined) operation. return llvmFn } // create wrapper function paramTypes := append([]llvm.Type{c.dataPtrType}, llvmFnType.ParamTypes()[len(expandedReceiverType):]...) wrapFnType := llvm.FunctionType(llvmFnType.ReturnType(), paramTypes, false) wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType) c.addStandardAttributes(wrapper) wrapper.SetLinkage(llvm.LinkOnceODRLinkage) wrapper.SetUnnamedAddr(true) // Create a new builder just to create this wrapper. b := builder{ compilerContext: c, Builder: c.ctx.NewBuilder(), } defer b.Builder.Dispose() // add debug info if needed if c.Debug { pos := c.program.Fset.Position(fn.Pos()) difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line) b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) } // set up IR builder block := b.ctx.AddBasicBlock(wrapper, "entry") b.SetInsertPointAtEnd(block) receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0] params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...) if llvmFnType.ReturnType().TypeKind() == llvm.VoidTypeKind { b.CreateCall(llvmFnType, llvmFn, params, "") b.CreateRetVoid() } else { ret := b.CreateCall(llvmFnType, llvmFn, params, "ret") b.CreateRet(ret) } return wrapper } // methodSignature creates a readable version of a method signature (including // the function name, excluding the receiver name). This string is used // internally to match interfaces and to call the correct method on an // interface. Examples: // // String() string // Read([]byte) (int, error) func methodSignature(method *types.Func) string { return method.Name() + signature(method.Type().(*types.Signature)) } // Make a readable version of a function (pointer) signature. // Examples: // // () string // (string, int) (int, error) func signature(sig *types.Signature) string { s := "" if sig.Params().Len() == 0 { s += "()" } else { s += "(" for i := 0; i < sig.Params().Len(); i++ { if i > 0 { s += ", " } s += typestring(sig.Params().At(i).Type()) } s += ")" } if sig.Results().Len() == 0 { // keep as-is } else if sig.Results().Len() == 1 { s += " " + typestring(sig.Results().At(0).Type()) } else { s += " (" for i := 0; i < sig.Results().Len(); i++ { if i > 0 { s += ", " } s += typestring(sig.Results().At(i).Type()) } s += ")" } return s } // typestring returns a stable (human-readable) type string for the given type // that can be used for interface equality checks. It is almost (but not // exactly) the same as calling t.String(). The main difference is some // normalization around `byte` vs `uint8` for example. func typestring(t types.Type) string { // See: https://github.com/golang/go/blob/master/src/go/types/typestring.go switch t := types.Unalias(t).(type) { case *types.Array: return "[" + strconv.FormatInt(t.Len(), 10) + "]" + typestring(t.Elem()) case *types.Basic: return basicTypeNames[t.Kind()] case *types.Chan: switch t.Dir() { case types.SendRecv: return "chan (" + typestring(t.Elem()) + ")" case types.SendOnly: return "chan<- (" + typestring(t.Elem()) + ")" case types.RecvOnly: return "<-chan (" + typestring(t.Elem()) + ")" default: panic("unknown channel direction") } case *types.Interface: methods := make([]string, t.NumMethods()) for i := range methods { method := t.Method(i) methods[i] = method.Name() + signature(method.Type().(*types.Signature)) } return "interface{" + strings.Join(methods, ";") + "}" case *types.Map: return "map[" + typestring(t.Key()) + "]" + typestring(t.Elem()) case *types.Named: return t.String() case *types.Pointer: return "*" + typestring(t.Elem()) case *types.Signature: return "func" + signature(t) case *types.Slice: return "[]" + typestring(t.Elem()) case *types.Struct: fields := make([]string, t.NumFields()) for i := range fields { field := t.Field(i) fields[i] = field.Name() + " " + typestring(field.Type()) if tag := t.Tag(i); tag != "" { fields[i] += " " + strconv.Quote(tag) } } return "struct{" + strings.Join(fields, ";") + "}" default: panic("unknown type: " + t.String()) } } ================================================ FILE: compiler/interrupt.go ================================================ package compiler import ( "strconv" "strings" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) // createInterruptGlobal creates a new runtime/interrupt.Interrupt struct that // will be lowered to a real interrupt during interrupt lowering. // // This two-stage approach allows unused interrupts to be optimized away if // necessary. func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, error) { // Get the interrupt number, which must be a compile-time constant. id, ok := instr.Args[0].(*ssa.Const) if !ok { return llvm.Value{}, b.makeError(instr.Pos(), "interrupt ID is not a constant") } // Get the func value, which also must be a compile time constant. // Note that bound functions are allowed if the function has a pointer // receiver and is a global. This is rather strict but still allows for // idiomatic Go code. funcValue := b.getValue(instr.Args[1], getPos(instr)) if funcValue.IsAConstant().IsNil() { // Try to determine the cause of the non-constantness for a nice error // message. switch instr.Args[1].(type) { case *ssa.MakeClosure: // This may also be a bound method. return llvm.Value{}, b.makeError(instr.Pos(), "closures are not supported in interrupt.New") } // Fall back to a generic error. return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant") } funcRawPtr, funcContext := b.decodeFuncValue(funcValue) funcPtr := llvm.ConstPtrToInt(funcRawPtr, b.uintptrType) // Create a new global of type runtime/interrupt.handle. Globals of this // type are lowered in the interrupt lowering pass. // It must have an alignment of 1, otherwise LLVM thinks a ptrtoint of the // global has the lower bits unset. globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type() globalLLVMType := b.getLLVMType(globalType) globalName := b.fn.Package().Pkg.Path() + "$interrupt" + strconv.FormatInt(id.Int64(), 10) global := llvm.AddGlobal(b.mod, globalLLVMType, globalName) global.SetVisibility(llvm.HiddenVisibility) global.SetGlobalConstant(true) global.SetUnnamedAddr(true) global.SetAlignment(1) initializer := llvm.ConstNull(globalLLVMType) initializer = b.CreateInsertValue(initializer, funcContext, 0, "") initializer = b.CreateInsertValue(initializer, funcPtr, 1, "") initializer = b.CreateInsertValue(initializer, llvm.ConstNamedStruct(globalLLVMType.StructElementTypes()[2], []llvm.Value{ llvm.ConstInt(b.intType, uint64(id.Int64()), true), }), 2, "") global.SetInitializer(initializer) // Add debug info to the interrupt global. if b.Debug { pos := b.program.Fset.Position(instr.Pos()) diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{ Name: "interrupt" + strconv.FormatInt(id.Int64(), 10), LinkageName: globalName, File: b.getDIFile(pos.Filename), Line: pos.Line, Type: b.getDIType(globalType), Expr: b.dibuilder.CreateExpression(nil), LocalToUnit: false, }) global.AddMetadata(0, diglobal) } // Create the runtime/interrupt.Interrupt type. It is a struct with a single // member of type int. num := llvm.ConstPtrToInt(global, b.intType) interrupt := llvm.ConstNamedStruct(b.mod.GetTypeByName("runtime/interrupt.Interrupt"), []llvm.Value{num}) // Add dummy "use" call for AVR, because interrupts may be used even though // they are never referenced again. This is unlike Cortex-M or the RISC-V // PLIC where each interrupt must be enabled using the interrupt number, and // thus keeps the Interrupt object alive. // This call is removed during interrupt lowering. if strings.HasPrefix(b.Triple, "avr") { useFn := b.mod.NamedFunction("runtime/interrupt.use") if useFn.IsNil() { useFnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false) useFn = llvm.AddFunction(b.mod, "runtime/interrupt.use", useFnType) } b.CreateCall(useFn.GlobalValueType(), useFn, []llvm.Value{interrupt}, "") } return interrupt, nil } ================================================ FILE: compiler/intrinsics.go ================================================ package compiler // This file contains helper functions to create calls to LLVM intrinsics. import ( "go/token" "strconv" "strings" "tinygo.org/x/go-llvm" ) // Define unimplemented intrinsic functions. // // Some functions are either normally implemented in Go assembly (like // sync/atomic functions) or intentionally left undefined to be implemented // directly in the compiler (like runtime/volatile functions). Either way, look // for these and implement them if this is the case. func (b *builder) defineIntrinsicFunction() { name := b.fn.RelString(nil) switch { case name == "runtime.memcpy" || name == "runtime.memmove": b.createMemoryCopyImpl() case name == "runtime.memzero": b.createMemoryZeroImpl() case name == "runtime.stacksave": b.createStackSaveImpl() case name == "runtime.KeepAlive": b.createKeepAliveImpl() case name == "machine.keepAliveNoEscape": b.createMachineKeepAliveImpl() case strings.HasPrefix(name, "runtime/volatile.Load"): b.createVolatileLoad() case strings.HasPrefix(name, "runtime/volatile.Store"): b.createVolatileStore() case strings.HasPrefix(name, "sync/atomic.") && token.IsExported(b.fn.Name()): b.createFunctionStart(true) returnValue := b.createAtomicOp(b.fn.Name()) if !returnValue.IsNil() { b.CreateRet(returnValue) } else { b.CreateRetVoid() } } } // createMemoryCopyImpl creates a call to a builtin LLVM memcpy or memmove // function, declaring this function if needed. These calls are treated // specially by optimization passes possibly resulting in better generated code, // and will otherwise be lowered to regular libc memcpy/memmove calls. func (b *builder) createMemoryCopyImpl() { b.createFunctionStart(true) fnName := "llvm." + b.fn.Name() + ".p0.p0.i" + strconv.Itoa(b.uintptrType.IntTypeWidth()) llvmFn := b.mod.NamedFunction(fnName) if llvmFn.IsNil() { fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType, b.dataPtrType, b.uintptrType, b.ctx.Int1Type()}, false) llvmFn = llvm.AddFunction(b.mod, fnName, fnType) } var params []llvm.Value for _, param := range b.fn.Params { params = append(params, b.getValue(param, getPos(b.fn))) } params = append(params, llvm.ConstInt(b.ctx.Int1Type(), 0, false)) b.CreateCall(llvmFn.GlobalValueType(), llvmFn, params, "") b.CreateRetVoid() } // createMemoryZeroImpl creates calls to llvm.memset.* to zero a block of // memory, declaring the function if needed. These calls will be lowered to // regular libc memset calls if they aren't optimized out in a different way. func (b *builder) createMemoryZeroImpl() { b.createFunctionStart(true) llvmFn := b.getMemsetFunc() params := []llvm.Value{ b.getValue(b.fn.Params[0], getPos(b.fn)), llvm.ConstInt(b.ctx.Int8Type(), 0, false), b.getValue(b.fn.Params[1], getPos(b.fn)), llvm.ConstInt(b.ctx.Int1Type(), 0, false), } b.CreateCall(llvmFn.GlobalValueType(), llvmFn, params, "") b.CreateRetVoid() } // createStackSaveImpl creates a call to llvm.stacksave.p0 to read the current // stack pointer. func (b *builder) createStackSaveImpl() { b.createFunctionStart(true) sp := b.readStackPointer() b.CreateRet(sp) } // Return the llvm.memset.p0.i8 function declaration. func (c *compilerContext) getMemsetFunc() llvm.Value { fnName := "llvm.memset.p0.i" + strconv.Itoa(c.uintptrType.IntTypeWidth()) llvmFn := c.mod.NamedFunction(fnName) if llvmFn.IsNil() { fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType, c.ctx.Int8Type(), c.uintptrType, c.ctx.Int1Type()}, false) llvmFn = llvm.AddFunction(c.mod, fnName, fnType) } return llvmFn } // createKeepAlive creates the runtime.KeepAlive function. It is implemented // using inline assembly. func (b *builder) createKeepAliveImpl() { b.createFunctionStart(true) // Get the underlying value of the interface value. interfaceValue := b.getValue(b.fn.Params[0], getPos(b.fn)) pointerValue := b.CreateExtractValue(interfaceValue, 1, "") // Create an equivalent of the following C code, which is basically just a // nop but ensures the pointerValue is kept alive: // // __asm__ __volatile__("" : : "r"(pointerValue)) // // It should be portable to basically everything as the "r" register type // exists basically everywhere. asmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType}, false) asmFn := llvm.InlineAsm(asmType, "", "r", true, false, 0, false) b.createCall(asmType, asmFn, []llvm.Value{pointerValue}, "") b.CreateRetVoid() } // createAbiEscapeImpl implements the generic internal/abi.Escape function. It // currently only supports pointer types. func (b *builder) createAbiEscapeImpl() { b.createFunctionStart(true) // The first parameter is assumed to be a pointer. This is checked at the // call site of createAbiEscapeImpl. pointerValue := b.getValue(b.fn.Params[0], getPos(b.fn)) // Create an equivalent of the following C code, which is basically just a // nop but ensures the pointerValue is kept alive: // // __asm__ __volatile__("" : : "r"(pointerValue)) // // It should be portable to basically everything as the "r" register type // exists basically everywhere. asmType := llvm.FunctionType(b.dataPtrType, []llvm.Type{b.dataPtrType}, false) asmFn := llvm.InlineAsm(asmType, "", "=r,0", true, false, 0, false) result := b.createCall(asmType, asmFn, []llvm.Value{pointerValue}, "") b.CreateRet(result) } // Implement machine.keepAliveNoEscape, which makes sure the compiler keeps the // pointer parameter alive until this point (for GC). func (b *builder) createMachineKeepAliveImpl() { b.createFunctionStart(true) pointerValue := b.getValue(b.fn.Params[0], getPos(b.fn)) // See createKeepAliveImpl for details. asmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType}, false) asmFn := llvm.InlineAsm(asmType, "", "r", true, false, 0, false) b.createCall(asmType, asmFn, []llvm.Value{pointerValue}, "") b.CreateRetVoid() } var mathToLLVMMapping = map[string]string{ "math.Ceil": "llvm.ceil.f64", "math.Exp": "llvm.exp.f64", "math.Exp2": "llvm.exp2.f64", "math.Floor": "llvm.floor.f64", "math.Log": "llvm.log.f64", "math.Sqrt": "llvm.sqrt.f64", "math.Trunc": "llvm.trunc.f64", } // defineMathOp defines a math function body as a call to a LLVM intrinsic, // instead of the regular Go implementation. This allows LLVM to reason about // the math operation and (depending on the architecture) allows it to lower the // operation to very fast floating point instructions. If this is not possible, // LLVM will emit a call to a libm function that implements the same operation. // // One example of an optimization that LLVM can do is to convert // float32(math.Sqrt(float64(v))) to a 32-bit floating point operation, which is // beneficial on architectures where 64-bit floating point operations are (much) // more expensive than 32-bit ones. func (b *builder) defineMathOp() { b.createFunctionStart(true) llvmName := mathToLLVMMapping[b.fn.RelString(nil)] if llvmName == "" { panic("unreachable: unknown math operation") // sanity check } llvmFn := b.mod.NamedFunction(llvmName) if llvmFn.IsNil() { // The intrinsic doesn't exist yet, so declare it. // At the moment, all supported intrinsics have the form "double // foo(double %x)" so we can hardcode the signature here. llvmType := llvm.FunctionType(b.ctx.DoubleType(), []llvm.Type{b.ctx.DoubleType()}, false) llvmFn = llvm.AddFunction(b.mod, llvmName, llvmType) } // Create a call to the intrinsic. args := make([]llvm.Value, len(b.fn.Params)) for i, param := range b.fn.Params { args[i] = b.getValue(param, getPos(b.fn)) } result := b.CreateCall(llvmFn.GlobalValueType(), llvmFn, args, "") b.CreateRet(result) } // Implement most math/bits functions. // // This implements all the functions that operate on bits. It does not yet // implement the arithmetic functions (like bits.Add), which also have LLVM // intrinsics. func (b *builder) defineMathBitsIntrinsic() bool { if b.fn.Pkg.Pkg.Path() != "math/bits" { return false } name := b.fn.Name() switch name { case "LeadingZeros", "LeadingZeros8", "LeadingZeros16", "LeadingZeros32", "LeadingZeros64", "TrailingZeros", "TrailingZeros8", "TrailingZeros16", "TrailingZeros32", "TrailingZeros64": b.createFunctionStart(true) param := b.getValue(b.fn.Params[0], b.fn.Pos()) valueType := param.Type() var intrinsicName string if strings.HasPrefix(name, "Leading") { // LeadingZeros intrinsicName = "llvm.ctlz.i" + strconv.Itoa(valueType.IntTypeWidth()) } else { // TrailingZeros intrinsicName = "llvm.cttz.i" + strconv.Itoa(valueType.IntTypeWidth()) } llvmFn := b.mod.NamedFunction(intrinsicName) llvmFnType := llvm.FunctionType(valueType, []llvm.Type{valueType, b.ctx.Int1Type()}, false) if llvmFn.IsNil() { llvmFn = llvm.AddFunction(b.mod, intrinsicName, llvmFnType) } result := b.createCall(llvmFnType, llvmFn, []llvm.Value{ param, llvm.ConstInt(b.ctx.Int1Type(), 0, false), }, "") result = b.createZExtOrTrunc(result, b.intType) b.CreateRet(result) return true case "Len", "Len8", "Len16", "Len32", "Len64": // bits.Len can be implemented as: // (unsafe.Sizeof(v) * 8) - bits.LeadingZeros(n) // Not sure why this isn't already done in the standard library, as it // is much simpler than a lookup table. b.createFunctionStart(true) param := b.getValue(b.fn.Params[0], b.fn.Pos()) valueType := param.Type() valueBits := valueType.IntTypeWidth() intrinsicName := "llvm.ctlz.i" + strconv.Itoa(valueBits) llvmFn := b.mod.NamedFunction(intrinsicName) llvmFnType := llvm.FunctionType(valueType, []llvm.Type{valueType, b.ctx.Int1Type()}, false) if llvmFn.IsNil() { llvmFn = llvm.AddFunction(b.mod, intrinsicName, llvmFnType) } result := b.createCall(llvmFnType, llvmFn, []llvm.Value{ param, llvm.ConstInt(b.ctx.Int1Type(), 0, false), }, "") result = b.createZExtOrTrunc(result, b.intType) maxLen := llvm.ConstInt(b.intType, uint64(valueBits), false) // number of bits in the value result = b.CreateSub(maxLen, result, "") b.CreateRet(result) return true case "OnesCount", "OnesCount8", "OnesCount16", "OnesCount32", "OnesCount64": b.createFunctionStart(true) param := b.getValue(b.fn.Params[0], b.fn.Pos()) valueType := param.Type() intrinsicName := "llvm.ctpop.i" + strconv.Itoa(valueType.IntTypeWidth()) llvmFn := b.mod.NamedFunction(intrinsicName) llvmFnType := llvm.FunctionType(valueType, []llvm.Type{valueType}, false) if llvmFn.IsNil() { llvmFn = llvm.AddFunction(b.mod, intrinsicName, llvmFnType) } result := b.createCall(llvmFnType, llvmFn, []llvm.Value{param}, "") result = b.createZExtOrTrunc(result, b.intType) b.CreateRet(result) return true case "Reverse", "Reverse8", "Reverse16", "Reverse32", "Reverse64", "ReverseBytes", "ReverseBytes16", "ReverseBytes32", "ReverseBytes64": b.createFunctionStart(true) param := b.getValue(b.fn.Params[0], b.fn.Pos()) valueType := param.Type() var intrinsicName string if strings.HasPrefix(name, "ReverseBytes") { intrinsicName = "llvm.bswap.i" + strconv.Itoa(valueType.IntTypeWidth()) } else { // Reverse intrinsicName = "llvm.bitreverse.i" + strconv.Itoa(valueType.IntTypeWidth()) } llvmFn := b.mod.NamedFunction(intrinsicName) llvmFnType := llvm.FunctionType(valueType, []llvm.Type{valueType}, false) if llvmFn.IsNil() { llvmFn = llvm.AddFunction(b.mod, intrinsicName, llvmFnType) } result := b.createCall(llvmFnType, llvmFn, []llvm.Value{param}, "") b.CreateRet(result) return true case "RotateLeft", "RotateLeft8", "RotateLeft16", "RotateLeft32", "RotateLeft64": // Warning: the documentation says these functions must be constant time. // I do not think LLVM guarantees this, but there's a good chance LLVM // already recognized the rotate instruction so it probably won't get // any _worse_ by implementing these rotate functions. b.createFunctionStart(true) x := b.getValue(b.fn.Params[0], b.fn.Pos()) k := b.getValue(b.fn.Params[1], b.fn.Pos()) valueType := x.Type() intrinsicName := "llvm.fshl.i" + strconv.Itoa(valueType.IntTypeWidth()) llvmFn := b.mod.NamedFunction(intrinsicName) llvmFnType := llvm.FunctionType(valueType, []llvm.Type{valueType, valueType, valueType}, false) if llvmFn.IsNil() { llvmFn = llvm.AddFunction(b.mod, intrinsicName, llvmFnType) } k = b.createZExtOrTrunc(k, valueType) result := b.createCall(llvmFnType, llvmFn, []llvm.Value{x, x, k}, "") b.CreateRet(result) return true default: return false } } ================================================ FILE: compiler/ircheck/check.go ================================================ // Package ircheck implements a checker for LLVM IR, that goes a bit further // than the regular LLVM IR verifier. Note that it checks different things, so // this is not a replacement for the LLVM verifier but does catch things that // the LLVM verifier doesn't catch. package ircheck import ( "errors" "fmt" "tinygo.org/x/go-llvm" ) type checker struct { ctx llvm.Context } func (c *checker) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error { // prevent infinite recursion for self-referential types if _, ok := checked[t]; ok { return nil } checked[t] = struct{}{} // check for any context mismatches switch { case t.Context() == c.ctx: // this is correct case t.Context() == llvm.GlobalContext(): // somewhere we accidentally used the global context instead of a real context return fmt.Errorf("type %q uses global context", t.String()) default: // we used some other context by accident return fmt.Errorf("type %q uses context %v instead of the main context %v", t.String(), t.Context(), c.ctx) } // if this is a composite type, check the components of the type switch t.TypeKind() { case llvm.VoidTypeKind, llvm.LabelTypeKind, llvm.TokenTypeKind, llvm.MetadataTypeKind: // there should only be one of any of these if s, ok := specials[t.TypeKind()]; !ok { specials[t.TypeKind()] = t } else if s != t { return fmt.Errorf("duplicate special type %q: %v and %v", t.TypeKind().String(), t, s) } case llvm.FloatTypeKind, llvm.DoubleTypeKind, llvm.X86_FP80TypeKind, llvm.FP128TypeKind, llvm.PPC_FP128TypeKind: // floating point numbers are primitives - nothing to recurse case llvm.IntegerTypeKind: // integers are primitives - nothing to recurse case llvm.FunctionTypeKind: // check arguments and return(s) for i, v := range t.ParamTypes() { if err := c.checkType(v, checked, specials); err != nil { return fmt.Errorf("failed to verify argument %d of type %s: %s", i, t.String(), err.Error()) } } if err := c.checkType(t.ReturnType(), checked, specials); err != nil { return fmt.Errorf("failed to verify return type of type %s: %s", t.String(), err.Error()) } case llvm.StructTypeKind: // check all elements for i, v := range t.StructElementTypes() { if err := c.checkType(v, checked, specials); err != nil { return fmt.Errorf("failed to verify type of field %d of struct type %s: %s", i, t.String(), err.Error()) } } case llvm.ArrayTypeKind: // check element type if err := c.checkType(t.ElementType(), checked, specials); err != nil { return fmt.Errorf("failed to verify element type of array type %s: %s", t.String(), err.Error()) } case llvm.PointerTypeKind: // Pointers can't be checked in an opaque pointer world. case llvm.VectorTypeKind: // check element type if err := c.checkType(t.ElementType(), checked, specials); err != nil { return fmt.Errorf("failed to verify element type of vector type %s: %s", t.String(), err.Error()) } default: return fmt.Errorf("unrecognized kind %q of type %s", t.TypeKind(), t.String()) } return nil } func (c *checker) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error { // check type if err := c.checkType(v.Type(), types, specials); err != nil { return fmt.Errorf("failed to verify type of value: %s", err.Error()) } // check if this is an undefined void if v.IsUndef() && v.Type().TypeKind() == llvm.VoidTypeKind { return errors.New("encountered undefined void value") } return nil } func (c *checker) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error { // check value properties if err := c.checkValue(inst, types, specials); err != nil { return errorAt(inst, err.Error()) } // The alloca instruction can be present in every basic block. However, // allocas in basic blocks other than the entry basic block have a number of // problems: // * They are hard to optimize, leading to potential missed optimizations. // * They may cause stack overflows in loops that would otherwise be // innocent. // * They cause extra code to be generated, because it requires the use of // a frame pointer. // * Perhaps most importantly, the coroutine lowering pass of LLVM (as of // LLVM 9) cannot deal with these allocas: // https://llvm.org/docs/Coroutines.html // Therefore, alloca instructions should be limited to the entry block. if !inst.IsAAllocaInst().IsNil() { if inst.InstructionParent() != inst.InstructionParent().Parent().EntryBasicBlock() { return errorAt(inst, "internal error: non-static alloca") } } // check operands for i := 0; i < inst.OperandsCount(); i++ { if err := c.checkValue(inst.Operand(i), types, specials); err != nil { return errorAt(inst, fmt.Sprintf("failed to validate operand %d of instruction %q: %s", i, inst.Name(), err.Error())) } } return nil } func (c *checker) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error { // check basic block value and type var errs []error if err := c.checkValue(bb.AsValue(), types, specials); err != nil { errs = append(errs, errorAt(bb.Parent(), fmt.Sprintf("failed to validate value of basic block %s: %v", bb.AsValue().Name(), err))) } // check instructions for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) { if err := c.checkInstruction(inst, types, specials); err != nil { errs = append(errs, err) } } return errs } func (c *checker) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error { // check function value and type var errs []error if err := c.checkValue(fn, types, specials); err != nil { errs = append(errs, fmt.Errorf("failed to validate value of function %s: %s", fn.Name(), err.Error())) } // check basic blocks for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) { errs = append(errs, c.checkBasicBlock(bb, types, specials)...) } return errs } // Module checks the given module and returns a slice of error, if there are // any. func Module(mod llvm.Module) []error { // check for any context mismatches var errs []error c := checker{ ctx: mod.Context(), } if c.ctx == llvm.GlobalContext() { // somewhere we accidentally used the global context instead of a real context errs = append(errs, errors.New("module uses global context")) } types := map[llvm.Type]struct{}{} specials := map[llvm.TypeKind]llvm.Type{} for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { errs = append(errs, c.checkFunction(fn, types, specials)...) } for g := mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) { if err := c.checkValue(g, types, specials); err != nil { errs = append(errs, fmt.Errorf("failed to verify global %s of module: %s", g.Name(), err.Error())) } } return errs } ================================================ FILE: compiler/ircheck/errors.go ================================================ package ircheck import ( "go/scanner" "go/token" "path/filepath" "tinygo.org/x/go-llvm" ) // errorAt returns an error value at the location of the instruction. // The location information may not be complete as it depends on debug // information in the IR. func errorAt(inst llvm.Value, msg string) scanner.Error { return scanner.Error{ Pos: getPosition(inst), Msg: msg, } } // getPosition returns the position information for the given value, as far as // it is available. func getPosition(val llvm.Value) token.Position { if !val.IsAInstruction().IsNil() { loc := val.InstructionDebugLoc() if loc.IsNil() { return token.Position{} } file := loc.LocationScope().ScopeFile() return token.Position{ Filename: filepath.Join(file.FileDirectory(), file.FileFilename()), Line: int(loc.LocationLine()), Column: int(loc.LocationColumn()), } } else if !val.IsAFunction().IsNil() { loc := val.Subprogram() if loc.IsNil() { return token.Position{} } file := loc.ScopeFile() return token.Position{ Filename: filepath.Join(file.FileDirectory(), file.FileFilename()), Line: int(loc.SubprogramLine()), } } else { return token.Position{} } } ================================================ FILE: compiler/llvm.go ================================================ package compiler import ( "fmt" "go/token" "go/types" "math/big" "strings" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compiler/llvmutil" "tinygo.org/x/go-llvm" ) // This file contains helper functions for LLVM that are not exposed in the Go // bindings. // createTemporaryAlloca creates a new alloca in the entry block and adds // lifetime start information in the IR signalling that the alloca won't be used // before this point. // // This is useful for creating temporary allocas for intrinsics. Don't forget to // end the lifetime using emitLifetimeEnd after you're done with it. func (b *builder) createTemporaryAlloca(t llvm.Type, name string) (alloca, size llvm.Value) { return llvmutil.CreateTemporaryAlloca(b.Builder, b.mod, t, name) } // insertBasicBlock inserts a new basic block after the current basic block. // This is useful when inserting new basic blocks while converting a // *ssa.BasicBlock to a llvm.BasicBlock and the LLVM basic block needs some // extra blocks. // It does not update b.blockExits, this must be done by the caller. func (b *builder) insertBasicBlock(name string) llvm.BasicBlock { currentBB := b.Builder.GetInsertBlock() nextBB := llvm.NextBasicBlock(currentBB) if nextBB.IsNil() { // Last basic block in the function, so add one to the end. return b.ctx.AddBasicBlock(b.llvmFn, name) } // Insert a basic block before the next basic block - that is, at the // current insert location. return b.ctx.InsertBasicBlock(nextBB, name) } // emitLifetimeEnd signals the end of an (alloca) lifetime by calling the // llvm.lifetime.end intrinsic. It is commonly used together with // createTemporaryAlloca. func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) { llvmutil.EmitLifetimeEnd(b.Builder, b.mod, ptr, size) } // emitPointerPack packs the list of values into a single pointer value using // bitcasts, or else allocates a value on the heap if it cannot be packed in the // pointer value directly. It returns the pointer with the packed data. // If the values are all constants, they are be stored in a constant global and // deduplicated. func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value { valueTypes := make([]llvm.Type, len(values)) for i, value := range values { valueTypes[i] = value.Type() } packedType := b.ctx.StructType(valueTypes, false) // Allocate memory for the packed data. size := b.targetData.TypeAllocSize(packedType) if size == 0 { return llvm.ConstPointerNull(b.dataPtrType) } else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind { return values[0] } else if size <= b.targetData.TypeAllocSize(b.dataPtrType) { // Packed data fits in a pointer, so store it directly inside the // pointer. if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind { // Try to keep this cast in SSA form. return b.CreateIntToPtr(values[0], b.dataPtrType, "pack.int") } // Because packedType is a struct and we have to cast it to a *i8, store // it in a *i8 alloca first and load the *i8 value from there. This is // effectively a bitcast. packedAlloc, _ := b.createTemporaryAlloca(b.dataPtrType, "") if size < b.targetData.TypeAllocSize(b.dataPtrType) { // The alloca is bigger than the value that will be stored in it. // To avoid having some bits undefined, zero the alloca first. // Hopefully this will get optimized away. b.CreateStore(llvm.ConstNull(b.dataPtrType), packedAlloc) } // Store all values in the alloca. for i, value := range values { indices := []llvm.Value{ llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false), } gep := b.CreateInBoundsGEP(packedType, packedAlloc, indices, "") b.CreateStore(value, gep) } // Load value (the *i8) from the alloca. result := b.CreateLoad(b.dataPtrType, packedAlloc, "") // End the lifetime of the alloca, to help the optimizer. packedSize := llvm.ConstInt(b.ctx.Int64Type(), b.targetData.TypeAllocSize(packedAlloc.Type()), false) b.emitLifetimeEnd(packedAlloc, packedSize) return result } else { // Check if the values are all constants. constant := true for _, v := range values { if !v.IsConstant() { constant = false break } } if constant { // The data is known at compile time, so store it in a constant global. // The global address is marked as unnamed, which allows LLVM to merge duplicates. global := llvm.AddGlobal(b.mod, packedType, b.pkg.Path()+"$pack") global.SetInitializer(b.ctx.ConstStruct(values, false)) global.SetGlobalConstant(true) global.SetUnnamedAddr(true) global.SetLinkage(llvm.InternalLinkage) return global } // Packed data is bigger than a pointer, so allocate it on the heap. sizeValue := llvm.ConstInt(b.uintptrType, size, false) align := b.targetData.ABITypeAlignment(packedType) alloc := b.mod.NamedFunction("runtime.alloc") packedAlloc := b.CreateCall(alloc.GlobalValueType(), alloc, []llvm.Value{ sizeValue, llvm.ConstNull(b.dataPtrType), llvm.Undef(b.dataPtrType), // unused context parameter }, "") packedAlloc.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align))) if b.NeedsStackObjects { b.trackPointer(packedAlloc) } // Store all values in the heap pointer. for i, value := range values { indices := []llvm.Value{ llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false), } gep := b.CreateInBoundsGEP(packedType, packedAlloc, indices, "") b.CreateStore(value, gep) } // Return the original heap allocation pointer, which already is an *i8. return packedAlloc } } // emitPointerUnpack extracts a list of values packed using emitPointerPack. func (b *builder) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value { packedType := b.ctx.StructType(valueTypes, false) // Get a correctly-typed pointer to the packed data. var packedAlloc llvm.Value needsLifetimeEnd := false size := b.targetData.TypeAllocSize(packedType) if size == 0 { // No data to unpack. } else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind { // A single pointer is always stored directly. return []llvm.Value{ptr} } else if size <= b.targetData.TypeAllocSize(b.dataPtrType) { // Packed data stored directly in pointer. if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind { // Keep this cast in SSA form. return []llvm.Value{b.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")} } // Fallback: load it using an alloca. packedAlloc, _ = b.createTemporaryAlloca(b.dataPtrType, "unpack.raw.alloc") b.CreateStore(ptr, packedAlloc) needsLifetimeEnd = true } else { // Packed data stored on the heap. packedAlloc = ptr } // Load each value from the packed data. values := make([]llvm.Value, len(valueTypes)) for i, valueType := range valueTypes { if b.targetData.TypeAllocSize(valueType) == 0 { // This value has length zero, so there's nothing to load. values[i] = llvm.ConstNull(valueType) continue } indices := []llvm.Value{ llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false), } gep := b.CreateInBoundsGEP(packedType, packedAlloc, indices, "") values[i] = b.CreateLoad(valueType, gep, "") } if needsLifetimeEnd { allocSize := llvm.ConstInt(b.ctx.Int64Type(), b.targetData.TypeAllocSize(b.uintptrType), false) b.emitLifetimeEnd(packedAlloc, allocSize) } return values } // makeGlobalArray creates a new LLVM global with the given name and integers as // contents, and returns the global and initializer type. // Note that it is left with the default linkage etc., you should set // linkage/constant/etc properties yourself. func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType llvm.Type) (llvm.Type, llvm.Value) { globalType := llvm.ArrayType(elementType, len(buf)) global := llvm.AddGlobal(c.mod, globalType, name) value := llvm.Undef(globalType) for i := 0; i < len(buf); i++ { ch := uint64(buf[i]) value = c.builder.CreateInsertValue(value, llvm.ConstInt(elementType, ch, false), i, "") } global.SetInitializer(value) return globalType, global } // createObjectLayout returns a LLVM value (of type i8*) that describes where // there are pointers in the type t. If all the data fits in a word, it is // returned as a word. Otherwise it will store the data in a global. // // The value contains two pieces of information: the length of the object and // which words contain a pointer (indicated by setting the given bit to 1). For // arrays, only the element is stored. This works because the GC knows the // object size and can therefore know how this value is repeated in the object. // // For details on what's in this value, see src/runtime/gc_precise.go. func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Value { // Use the element type for arrays. This works even for nested arrays. for { kind := t.TypeKind() if kind == llvm.ArrayTypeKind { t = t.ElementType() continue } if kind == llvm.StructTypeKind { fields := t.StructElementTypes() if len(fields) == 1 { t = fields[0] continue } } break } // Do a few checks to see whether we need to generate any object layout // information at all. objectSizeBytes := c.targetData.TypeAllocSize(t) pointerSize := c.targetData.TypeAllocSize(c.dataPtrType) pointerAlignment := c.targetData.PrefTypeAlignment(c.dataPtrType) if objectSizeBytes < pointerSize { // Too small to contain a pointer. layout := (uint64(1) << 1) | 1 return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) } bitmap := c.getPointerBitmap(t, pos) if bitmap.BitLen() == 0 { // There are no pointers in this type, so we can simplify the layout. // TODO: this can be done in many other cases, e.g. when allocating an // array (like [4][]byte, which repeats a slice 4 times). layout := (uint64(1) << 1) | 1 return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) } if objectSizeBytes%uint64(pointerAlignment) != 0 { // This shouldn't happen except for packed structs, which aren't // currently used. c.addError(pos, "internal error: unexpected object size for object with pointer field") return llvm.ConstNull(c.dataPtrType) } objectSizeWords := objectSizeBytes / uint64(pointerAlignment) pointerBits := pointerSize * 8 var sizeFieldBits uint64 switch pointerBits { case 16: sizeFieldBits = 4 case 32: sizeFieldBits = 5 case 64: sizeFieldBits = 6 default: panic("unknown pointer size") } layoutFieldBits := pointerBits - 1 - sizeFieldBits // Try to emit the value as an inline integer. This is possible in most // cases. if objectSizeWords < layoutFieldBits { // If it can be stored directly in the pointer value, do so. // The runtime knows that if the least significant bit of the pointer is // set, the pointer contains the value itself. layout := bitmap.Uint64()<<(sizeFieldBits+1) | (objectSizeWords << 1) | 1 return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) } // Unfortunately, the object layout is too big to fit in a pointer-sized // integer. Store it in a global instead. // Try first whether the global already exists. All objects with a // particular name have the same type, so this is possible. globalName := "runtime/gc.layout:" + fmt.Sprintf("%d-%0*x", objectSizeWords, (objectSizeWords+15)/16, bitmap) global := c.mod.NamedGlobal(globalName) if !global.IsNil() { return global } // Create the global initializer. bitmapBytes := make([]byte, int(objectSizeWords+7)/8) bitmap.FillBytes(bitmapBytes) reverseBytes(bitmapBytes) // big-endian to little-endian var bitmapByteValues []llvm.Value for _, b := range bitmapBytes { bitmapByteValues = append(bitmapByteValues, llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false)) } initializer := c.ctx.ConstStruct([]llvm.Value{ llvm.ConstInt(c.uintptrType, objectSizeWords, false), llvm.ConstArray(c.ctx.Int8Type(), bitmapByteValues), }, false) global = llvm.AddGlobal(c.mod, initializer.Type(), globalName) global.SetInitializer(initializer) global.SetUnnamedAddr(true) global.SetGlobalConstant(true) global.SetLinkage(llvm.LinkOnceODRLinkage) if c.targetData.PrefTypeAlignment(c.uintptrType) < 2 { // AVR doesn't have alignment by default. global.SetAlignment(2) } if c.Debug && pos != token.NoPos { // Creating a fake global so that the value can be inspected in GDB. // For example, the layout for strings.stringFinder (as of Go version // 1.15) has the following type according to GDB: // type = struct { // uintptr numBits; // uint8 data[33]; // } // ...that's sort of a mixed C/Go type, but it is readable. More // importantly, these object layout globals can be read and printed by // GDB which may be useful for debugging. position := c.program.Fset.Position(pos) diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[position.Filename], llvm.DIGlobalVariableExpression{ Name: globalName, File: c.getDIFile(position.Filename), Line: position.Line, Type: c.getDIType(types.NewStruct([]*types.Var{ types.NewVar(pos, nil, "numBits", types.Typ[types.Uintptr]), types.NewVar(pos, nil, "data", types.NewArray(types.Typ[types.Byte], int64(len(bitmapByteValues)))), }, nil)), LocalToUnit: false, Expr: c.dibuilder.CreateExpression(nil), }) global.AddMetadata(0, diglobal) } return global } // getPointerBitmap scans the given LLVM type for pointers and sets bits in a // bigint at the word offset that contains a pointer. This scan is recursive. func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.Int { alignment := c.targetData.PrefTypeAlignment(c.dataPtrType) switch typ.TypeKind() { case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind: return big.NewInt(0) case llvm.PointerTypeKind: return big.NewInt(1) case llvm.StructTypeKind: ptrs := big.NewInt(0) for i, subtyp := range typ.StructElementTypes() { subptrs := c.getPointerBitmap(subtyp, pos) if subptrs.BitLen() == 0 { continue } offset := c.targetData.ElementOffset(typ, i) if offset%uint64(alignment) != 0 { // This error will let the compilation fail, but by continuing // the error can still easily be shown. c.addError(pos, "internal error: allocated struct contains unaligned pointer") continue } subptrs.Lsh(subptrs, uint(offset)/uint(alignment)) ptrs.Or(ptrs, subptrs) } return ptrs case llvm.ArrayTypeKind: subtyp := typ.ElementType() subptrs := c.getPointerBitmap(subtyp, pos) ptrs := big.NewInt(0) if subptrs.BitLen() == 0 { return ptrs } elementSize := c.targetData.TypeAllocSize(subtyp) if elementSize%uint64(alignment) != 0 { // This error will let the compilation fail (but continues so that // other errors can be shown). c.addError(pos, "internal error: allocated array contains unaligned pointer") return ptrs } for i := 0; i < typ.ArrayLength(); i++ { ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment)) ptrs.Or(ptrs, subptrs) } return ptrs default: // Should not happen. panic("unknown LLVM type") } } // archFamily returns the architecture from the LLVM triple but with some // architecture names ("armv6", "thumbv7m", etc) merged into a single // architecture name ("arm"). func (c *compilerContext) archFamily() string { return compileopts.CanonicalArchName(c.Triple) } // isThumb returns whether we're in ARM or in Thumb mode. It panics if the // features string is not one for an ARM architecture. func (c *compilerContext) isThumb() bool { var isThumb, isNotThumb bool for _, feature := range strings.Split(c.Features, ",") { if feature == "+thumb-mode" { isThumb = true } if feature == "-thumb-mode" { isNotThumb = true } } if isThumb == isNotThumb { panic("unexpected feature flags") } return isThumb } // readStackPointer emits a LLVM intrinsic call that returns the current stack // pointer as an *i8. func (b *builder) readStackPointer() llvm.Value { name := "llvm.stacksave.p0" if llvmutil.Version() < 18 { name = "llvm.stacksave" // backwards compatibility with LLVM 17 and below } stacksave := b.mod.NamedFunction(name) if stacksave.IsNil() { fnType := llvm.FunctionType(b.dataPtrType, nil, false) stacksave = llvm.AddFunction(b.mod, name, fnType) } return b.CreateCall(stacksave.GlobalValueType(), stacksave, nil, "") } // writeStackPointer emits a LLVM intrinsic call that updates the current stack // pointer. func (b *builder) writeStackPointer(sp llvm.Value) { name := "llvm.stackrestore.p0" if llvmutil.Version() < 18 { name = "llvm.stackrestore" // backwards compatibility with LLVM 17 and below } stackrestore := b.mod.NamedFunction(name) if stackrestore.IsNil() { fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType}, false) stackrestore = llvm.AddFunction(b.mod, name, fnType) } b.CreateCall(stackrestore.GlobalValueType(), stackrestore, []llvm.Value{sp}, "") } // createZExtOrTrunc lets the input value fit in the output type bits, by zero // extending or truncating the integer. func (b *builder) createZExtOrTrunc(value llvm.Value, t llvm.Type) llvm.Value { valueBits := value.Type().IntTypeWidth() resultBits := t.IntTypeWidth() if valueBits > resultBits { value = b.CreateTrunc(value, t, "") } else if valueBits < resultBits { value = b.CreateZExt(value, t, "") } return value } // Reverse a slice of bytes. From the wiki: // https://github.com/golang/go/wiki/SliceTricks#reversing func reverseBytes(buf []byte) { for i := len(buf)/2 - 1; i >= 0; i-- { opp := len(buf) - 1 - i buf[i], buf[opp] = buf[opp], buf[i] } } ================================================ FILE: compiler/llvmutil/llvm.go ================================================ // Package llvmutil contains utility functions used across multiple compiler // packages. For example, they may be used by both the compiler package and // transformation packages. // // Normally, utility packages are avoided. However, in this case, the utility // functions are non-trivial and hard to get right. Copying them to multiple // places would be a big risk if only one of them is updated. package llvmutil import ( "encoding/binary" "strconv" "strings" "tinygo.org/x/go-llvm" ) // CreateEntryBlockAlloca creates a new alloca in the entry block, even though // the IR builder is located elsewhere. It assumes that the insert point is // at the end of the current block. func CreateEntryBlockAlloca(builder llvm.Builder, t llvm.Type, name string) llvm.Value { currentBlock := builder.GetInsertBlock() entryBlock := currentBlock.Parent().EntryBasicBlock() if entryBlock.FirstInstruction().IsNil() { builder.SetInsertPointAtEnd(entryBlock) } else { builder.SetInsertPointBefore(entryBlock.FirstInstruction()) } alloca := builder.CreateAlloca(t, name) builder.SetInsertPointAtEnd(currentBlock) return alloca } // CreateTemporaryAlloca creates a new alloca in the entry block and adds // lifetime start information in the IR signalling that the alloca won't be used // before this point. // // This is useful for creating temporary allocas for intrinsics. Don't forget to // end the lifetime using emitLifetimeEnd after you're done with it. func CreateTemporaryAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, name string) (alloca, size llvm.Value) { ctx := t.Context() targetData := llvm.NewTargetData(mod.DataLayout()) defer targetData.Dispose() alloca = CreateEntryBlockAlloca(builder, t, name) size = llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false) fnType, fn := getLifetimeStartFunc(mod) builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "") return } // CreateInstructionAlloca creates an alloca in the entry block, and places lifetime control intrinsics around the instruction func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, inst llvm.Value, name string) llvm.Value { ctx := mod.Context() targetData := llvm.NewTargetData(mod.DataLayout()) defer targetData.Dispose() alloca := CreateEntryBlockAlloca(builder, t, name) builder.SetInsertPointBefore(inst) size := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false) fnType, fn := getLifetimeStartFunc(mod) builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "") if next := llvm.NextInstruction(inst); !next.IsNil() { builder.SetInsertPointBefore(next) } else { builder.SetInsertPointAtEnd(inst.InstructionParent()) } fnType, fn = getLifetimeEndFunc(mod) builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "") return alloca } // EmitLifetimeEnd signals the end of an (alloca) lifetime by calling the // llvm.lifetime.end intrinsic. It is commonly used together with // createTemporaryAlloca. func EmitLifetimeEnd(builder llvm.Builder, mod llvm.Module, ptr, size llvm.Value) { fnType, fn := getLifetimeEndFunc(mod) builder.CreateCall(fnType, fn, []llvm.Value{size, ptr}, "") } // getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it // first if it doesn't exist yet. func getLifetimeStartFunc(mod llvm.Module) (llvm.Type, llvm.Value) { fnName := "llvm.lifetime.start.p0" fn := mod.NamedFunction(fnName) ctx := mod.Context() ptrType := llvm.PointerType(ctx.Int8Type(), 0) fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false) if fn.IsNil() { fn = llvm.AddFunction(mod, fnName, fnType) } return fnType, fn } // getLifetimeEndFunc returns the llvm.lifetime.end intrinsic and creates it // first if it doesn't exist yet. func getLifetimeEndFunc(mod llvm.Module) (llvm.Type, llvm.Value) { fnName := "llvm.lifetime.end.p0" fn := mod.NamedFunction(fnName) ctx := mod.Context() ptrType := llvm.PointerType(ctx.Int8Type(), 0) fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false) if fn.IsNil() { fn = llvm.AddFunction(mod, fnName, fnType) } return fnType, fn } // SplitBasicBlock splits a LLVM basic block into two parts. All instructions // after afterInst are moved into a new basic block (created right after the // current one) with the given name. func SplitBasicBlock(builder llvm.Builder, afterInst llvm.Value, insertAfter llvm.BasicBlock, name string) llvm.BasicBlock { oldBlock := afterInst.InstructionParent() newBlock := afterInst.Type().Context().InsertBasicBlock(insertAfter, name) var nextInstructions []llvm.Value // values to move // Collect to-be-moved instructions. inst := afterInst for { inst = llvm.NextInstruction(inst) if inst.IsNil() { break } nextInstructions = append(nextInstructions, inst) } // Move instructions. builder.SetInsertPointAtEnd(newBlock) for _, inst := range nextInstructions { inst.RemoveFromParentAsInstruction() builder.Insert(inst) } // Find PHI nodes to update. var phiNodes []llvm.Value // PHI nodes to update for bb := insertAfter.Parent().FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) { for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) { if inst.IsAPHINode().IsNil() { continue } needsUpdate := false incomingCount := inst.IncomingCount() for i := 0; i < incomingCount; i++ { if inst.IncomingBlock(i) == oldBlock { needsUpdate = true break } } if !needsUpdate { // PHI node has no incoming edge from the old block. continue } phiNodes = append(phiNodes, inst) } } // Update PHI nodes. for _, phi := range phiNodes { builder.SetInsertPointBefore(phi) newPhi := builder.CreatePHI(phi.Type(), "") incomingCount := phi.IncomingCount() incomingVals := make([]llvm.Value, incomingCount) incomingBlocks := make([]llvm.BasicBlock, incomingCount) for i := 0; i < incomingCount; i++ { value := phi.IncomingValue(i) block := phi.IncomingBlock(i) if block == oldBlock { block = newBlock } incomingVals[i] = value incomingBlocks[i] = block } newPhi.AddIncoming(incomingVals, incomingBlocks) phi.ReplaceAllUsesWith(newPhi) phi.EraseFromParentAsInstruction() } return newBlock } // AppendToGlobal appends the given values to a global array like llvm.used. The global might // not exist yet. The values can be any pointer type, they will be cast to i8*. func AppendToGlobal(mod llvm.Module, globalName string, values ...llvm.Value) { // Read the existing values in the llvm.used array (if it exists). var usedValues []llvm.Value if used := mod.NamedGlobal(globalName); !used.IsNil() { builder := mod.Context().NewBuilder() defer builder.Dispose() usedInitializer := used.Initializer() num := usedInitializer.Type().ArrayLength() for i := 0; i < num; i++ { usedValues = append(usedValues, builder.CreateExtractValue(usedInitializer, i, "")) } used.EraseFromParentAsGlobal() } // Add the new values. ptrType := llvm.PointerType(mod.Context().Int8Type(), 0) for _, value := range values { // Note: the bitcast is necessary to cast AVR function pointers to // address space 0 pointer types. usedValues = append(usedValues, llvm.ConstPointerCast(value, ptrType)) } // Create a new array (with the old and new values). usedInitializer := llvm.ConstArray(ptrType, usedValues) used := llvm.AddGlobal(mod, usedInitializer.Type(), globalName) used.SetInitializer(usedInitializer) used.SetLinkage(llvm.AppendingLinkage) } // Version returns the LLVM major version. func Version() int { majorStr := strings.Split(llvm.Version, ".")[0] major, err := strconv.Atoi(majorStr) if err != nil { panic("unexpected error while parsing LLVM version: " + err.Error()) // should not happen } return major } // ByteOrder returns the byte order for the given target triple. Most targets are little // endian, but for example MIPS can be big-endian. func ByteOrder(target string) binary.ByteOrder { if strings.HasPrefix(target, "mips-") { return binary.BigEndian } else { return binary.LittleEndian } } ================================================ FILE: compiler/map.go ================================================ package compiler // This file emits the correct map intrinsics for map operations. import ( "go/token" "go/types" "github.com/tinygo-org/tinygo/src/tinygo" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) // createMakeMap creates a new map object (runtime.hashmap) by allocating and // initializing an appropriately sized object. func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) { mapType := expr.Type().Underlying().(*types.Map) keyType := mapType.Key().Underlying() llvmValueType := b.getLLVMType(mapType.Elem().Underlying()) var llvmKeyType llvm.Type var alg uint64 if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { // String keys. llvmKeyType = b.getLLVMType(keyType) alg = uint64(tinygo.HashmapAlgorithmString) } else if hashmapIsBinaryKey(keyType) { // Trivially comparable keys. llvmKeyType = b.getLLVMType(keyType) alg = uint64(tinygo.HashmapAlgorithmBinary) } else { // All other keys. Implemented as map[interface{}]valueType for ease of // implementation. llvmKeyType = b.getLLVMRuntimeType("_interface") alg = uint64(tinygo.HashmapAlgorithmInterface) } keySize := b.targetData.TypeAllocSize(llvmKeyType) valueSize := b.targetData.TypeAllocSize(llvmValueType) llvmKeySize := llvm.ConstInt(b.uintptrType, keySize, false) llvmValueSize := llvm.ConstInt(b.uintptrType, valueSize, false) sizeHint := llvm.ConstInt(b.uintptrType, 8, false) algEnum := llvm.ConstInt(b.ctx.Int8Type(), alg, false) if expr.Reserve != nil { sizeHint = b.getValue(expr.Reserve, getPos(expr)) var err error sizeHint, err = b.createConvert(expr.Reserve.Type(), types.Typ[types.Uintptr], sizeHint, expr.Pos()) if err != nil { return llvm.Value{}, err } } hashmap := b.createRuntimeCall("hashmapMake", []llvm.Value{llvmKeySize, llvmValueSize, sizeHint, algEnum}, "") return hashmap, nil } // createMapLookup returns the value in a map. It calls a runtime function // depending on the map key type to load the map value and its comma-ok value. func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) { llvmValueType := b.getLLVMType(valueType) // Allocate the memory for the resulting type. Do not zero this memory: it // will be zeroed by the hashmap get implementation if the key is not // present in the map. mapValueAlloca, mapValueAllocaSize := b.createTemporaryAlloca(llvmValueType, "hashmap.value") // We need the map size (with type uintptr) to pass to the hashmap*Get // functions. This is necessary because those *Get functions are valid on // nil maps, and they'll need to zero the value pointer by that number of // bytes. mapValueSize := mapValueAllocaSize if mapValueSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() { mapValueSize = llvm.ConstTrunc(mapValueSize, b.uintptrType) } // Do the lookup. How it is done depends on the key type. var commaOkValue llvm.Value origKeyType := keyType keyType = keyType.Underlying() if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { // key is a string params := []llvm.Value{m, key, mapValueAlloca, mapValueSize} commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "") } else if hashmapIsBinaryKey(keyType) { // key can be compared with runtime.memequal // Store the key in an alloca, in the entry block to avoid dynamic stack // growth. mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") b.CreateStore(key, mapKeyAlloca) b.zeroUndefBytes(b.getLLVMType(keyType), mapKeyAlloca) // Fetch the value from the hashmap. params := []llvm.Value{m, mapKeyAlloca, mapValueAlloca, mapValueSize} commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "") b.emitLifetimeEnd(mapKeyAlloca, mapKeySize) } else { // Not trivially comparable using memcmp. Make it an interface instead. itfKey := key if _, ok := keyType.(*types.Interface); !ok { // Not already an interface, so convert it to an interface now. itfKey = b.createMakeInterface(key, origKeyType, pos) } params := []llvm.Value{m, itfKey, mapValueAlloca, mapValueSize} commaOkValue = b.createRuntimeCall("hashmapInterfaceGet", params, "") } // Load the resulting value from the hashmap. The value is set to the zero // value if the key doesn't exist in the hashmap. mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "") b.emitLifetimeEnd(mapValueAlloca, mapValueAllocaSize) if commaOk { tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false)) tuple = b.CreateInsertValue(tuple, mapValue, 0, "") tuple = b.CreateInsertValue(tuple, commaOkValue, 1, "") return tuple, nil } else { return mapValue, nil } } // createMapUpdate updates a map key to a given value, by creating an // appropriate runtime call. func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) { valueAlloca, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value") b.CreateStore(value, valueAlloca) origKeyType := keyType keyType = keyType.Underlying() if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { // key is a string params := []llvm.Value{m, key, valueAlloca} b.createRuntimeCall("hashmapStringSet", params, "") } else if hashmapIsBinaryKey(keyType) { // key can be compared with runtime.memequal keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") b.CreateStore(key, keyAlloca) b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca) params := []llvm.Value{m, keyAlloca, valueAlloca} b.createRuntimeCall("hashmapBinarySet", params, "") b.emitLifetimeEnd(keyAlloca, keySize) } else { // Key is not trivially comparable, so compare it as an interface instead. itfKey := key if _, ok := keyType.(*types.Interface); !ok { // Not already an interface, so convert it to an interface first. itfKey = b.createMakeInterface(key, origKeyType, pos) } params := []llvm.Value{m, itfKey, valueAlloca} b.createRuntimeCall("hashmapInterfaceSet", params, "") } b.emitLifetimeEnd(valueAlloca, valueSize) } // createMapDelete deletes a key from a map by calling the appropriate runtime // function. It is the implementation of the Go delete() builtin. func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error { origKeyType := keyType keyType = keyType.Underlying() if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { // key is a string params := []llvm.Value{m, key} b.createRuntimeCall("hashmapStringDelete", params, "") return nil } else if hashmapIsBinaryKey(keyType) { keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") b.CreateStore(key, keyAlloca) b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca) params := []llvm.Value{m, keyAlloca} b.createRuntimeCall("hashmapBinaryDelete", params, "") b.emitLifetimeEnd(keyAlloca, keySize) return nil } else { // Key is not trivially comparable, so compare it as an interface // instead. itfKey := key if _, ok := keyType.(*types.Interface); !ok { // Not already an interface, so convert it to an interface first. itfKey = b.createMakeInterface(key, origKeyType, pos) } params := []llvm.Value{m, itfKey} b.createRuntimeCall("hashmapInterfaceDelete", params, "") return nil } } // Clear the given map. func (b *builder) createMapClear(m llvm.Value) { b.createRuntimeCall("hashmapClear", []llvm.Value{m}, "") } // createMapIteratorNext lowers the *ssa.Next instruction for iterating over a // map. It returns a tuple of {bool, key, value} with the result of the // iteration. func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llvm.Value) llvm.Value { // Determine the type of the values to return from the *ssa.Next // instruction. It is returned as {bool, keyType, valueType}. keyType := rangeVal.Type().Underlying().(*types.Map).Key() valueType := rangeVal.Type().Underlying().(*types.Map).Elem() llvmKeyType := b.getLLVMType(keyType) llvmValueType := b.getLLVMType(valueType) // There is a special case in which keys are stored as an interface value // instead of the value they normally are. This happens for non-trivially // comparable types such as float32 or some structs. isKeyStoredAsInterface := false if t, ok := keyType.Underlying().(*types.Basic); ok && t.Info()&types.IsString != 0 { // key is a string } else if hashmapIsBinaryKey(keyType) { // key can be compared with runtime.memequal } else { // The key is stored as an interface value, and may or may not be an // interface type (for example, float32 keys are stored as an interface // value). if _, ok := keyType.Underlying().(*types.Interface); !ok { isKeyStoredAsInterface = true } } // Determine the type of the key as stored in the map. llvmStoredKeyType := llvmKeyType if isKeyStoredAsInterface { llvmStoredKeyType = b.getLLVMRuntimeType("_interface") } // Extract the key and value from the map. mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(llvmStoredKeyType, "range.key") mapValueAlloca, mapValueSize := b.createTemporaryAlloca(llvmValueType, "range.value") ok := b.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyAlloca, mapValueAlloca}, "range.next") mapKey := b.CreateLoad(llvmStoredKeyType, mapKeyAlloca, "") mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "") if isKeyStoredAsInterface { // The key is stored as an interface but it isn't of interface type. // Extract the underlying value. mapKey = b.extractValueFromInterface(mapKey, llvmKeyType) } // End the lifetimes of the allocas, because we're done with them. b.emitLifetimeEnd(mapKeyAlloca, mapKeySize) b.emitLifetimeEnd(mapValueAlloca, mapValueSize) // Construct the *ssa.Next return value: {ok, mapKey, mapValue} tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.Int1Type(), llvmKeyType, llvmValueType}, false)) tuple = b.CreateInsertValue(tuple, ok, 0, "") tuple = b.CreateInsertValue(tuple, mapKey, 1, "") tuple = b.CreateInsertValue(tuple, mapValue, 2, "") return tuple } // Returns true if this key type does not contain strings, interfaces etc., so // can be compared with runtime.memequal. Note that padding bytes are undef // and can alter two "equal" structs being equal when compared with memequal. func hashmapIsBinaryKey(keyType types.Type) bool { switch keyType := keyType.Underlying().(type) { case *types.Basic: // TODO: unsafe.Pointer is also a binary key, but to support that we // need to fix an issue with interp first (see // https://github.com/tinygo-org/tinygo/pull/4898). return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0 case *types.Pointer: return true case *types.Struct: for i := 0; i < keyType.NumFields(); i++ { fieldType := keyType.Field(i).Type().Underlying() if !hashmapIsBinaryKey(fieldType) { return false } } return true case *types.Array: return hashmapIsBinaryKey(keyType.Elem()) default: return false } } func (b *builder) zeroUndefBytes(llvmType llvm.Type, ptr llvm.Value) error { // We know that hashmapIsBinaryKey is true, so we only have to handle those types that can show up there. // To zero all undefined bytes, we iterate over all the fields in the type. For each element, compute the // offset of that element. If it's Basic type, there are no internal padding bytes. For compound types, we recurse to ensure // we handle nested types. Next, we determine if there are any padding bytes before the next // element and zero those as well. zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) switch llvmType.TypeKind() { case llvm.IntegerTypeKind: // no padding bytes return nil case llvm.PointerTypeKind: // mo padding bytes return nil case llvm.ArrayTypeKind: llvmArrayType := llvmType llvmElemType := llvmType.ElementType() for i := 0; i < llvmArrayType.ArrayLength(); i++ { idx := llvm.ConstInt(b.uintptrType, uint64(i), false) elemPtr := b.CreateInBoundsGEP(llvmArrayType, ptr, []llvm.Value{zero, idx}, "") // zero any padding bytes in this element b.zeroUndefBytes(llvmElemType, elemPtr) } case llvm.StructTypeKind: llvmStructType := llvmType numFields := llvmStructType.StructElementTypesCount() llvmElementTypes := llvmStructType.StructElementTypes() for i := 0; i < numFields; i++ { idx := llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false) elemPtr := b.CreateInBoundsGEP(llvmStructType, ptr, []llvm.Value{zero, idx}, "") // zero any padding bytes in this field llvmElemType := llvmElementTypes[i] b.zeroUndefBytes(llvmElemType, elemPtr) // zero any padding bytes before the next field, if any offset := b.targetData.ElementOffset(llvmStructType, i) storeSize := b.targetData.TypeStoreSize(llvmElemType) fieldEndOffset := offset + storeSize var nextOffset uint64 if i < numFields-1 { nextOffset = b.targetData.ElementOffset(llvmStructType, i+1) } else { // Last field? Next offset is the total size of the allocate struct. nextOffset = b.targetData.TypeAllocSize(llvmStructType) } if fieldEndOffset != nextOffset { n := llvm.ConstInt(b.uintptrType, nextOffset-fieldEndOffset, false) llvmStoreSize := llvm.ConstInt(b.uintptrType, storeSize, false) paddingStart := b.CreateInBoundsGEP(b.ctx.Int8Type(), elemPtr, []llvm.Value{llvmStoreSize}, "") b.createRuntimeCall("memzero", []llvm.Value{paddingStart, n}, "") } } } return nil } ================================================ FILE: compiler/sizes.go ================================================ package compiler import ( "go/types" ) // The code in this file has been copied from // https://golang.org/src/go/types/sizes.go and modified to allow for int and // pointer sizes to differ. // The original license can be found here: // https://golang.org/LICENSE type stdSizes struct { IntSize int64 PtrSize int64 MaxAlign int64 } func (s *stdSizes) Alignof(T types.Type) int64 { // For arrays and structs, alignment is defined in terms // of alignment of the elements and fields, respectively. switch t := T.Underlying().(type) { case *types.Array: // spec: "For a variable x of array type: unsafe.Alignof(x) // is the same as unsafe.Alignof(x[0]), but at least 1." return s.Alignof(t.Elem()) case *types.Struct: // spec: "For a variable x of struct type: unsafe.Alignof(x) // is the largest of the values unsafe.Alignof(x.f) for each // field f of x, but at least 1." max := int64(1) for i := 0; i < t.NumFields(); i++ { f := t.Field(i) if a := s.Alignof(f.Type()); a > max { max = a } } return max case *types.Slice, *types.Interface: // Multiword data structures are effectively structs // in which each element has size WordSize. return s.PtrSize case *types.Basic: // Strings are like slices and interfaces. if t.Info()&types.IsString != 0 { return s.PtrSize } case *types.Signature: // Even though functions in tinygo are 2 pointers, they are not 2 pointer aligned return s.PtrSize } a := s.Sizeof(T) // may be 0 // spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1." if a < 1 { return 1 } // complex{64,128} are aligned like [2]float{32,64}. if t, ok := T.Underlying().(*types.Basic); ok && t.Info()&types.IsComplex != 0 { a /= 2 } if a > s.MaxAlign { return s.MaxAlign } return a } func (s *stdSizes) Offsetsof(fields []*types.Var) []int64 { offsets := make([]int64, len(fields)) var o int64 for i, f := range fields { a := s.Alignof(f.Type()) o = align(o, a) offsets[i] = o o += s.Sizeof(f.Type()) } return offsets } var basicSizes = [...]byte{ types.Bool: 1, types.Int8: 1, types.Int16: 2, types.Int32: 4, types.Int64: 8, types.Uint8: 1, types.Uint16: 2, types.Uint32: 4, types.Uint64: 8, types.Float32: 4, types.Float64: 8, types.Complex64: 8, types.Complex128: 16, } func (s *stdSizes) Sizeof(T types.Type) int64 { switch t := T.Underlying().(type) { case *types.Basic: k := t.Kind() if int(k) < len(basicSizes) { if s := basicSizes[k]; s > 0 { return int64(s) } } if k == types.String { return s.PtrSize * 2 } if k == types.Int || k == types.Uint { return s.IntSize } if k == types.Uintptr { return s.PtrSize } if k == types.UnsafePointer { return s.PtrSize } if k == types.Invalid { return 0 // only relevant when there is a type error somewhere } panic("unknown basic type: " + t.String()) case *types.Array: n := t.Len() if n <= 0 { return 0 } // n > 0 a := s.Alignof(t.Elem()) z := s.Sizeof(t.Elem()) return align(z, a)*(n-1) + z case *types.Slice: return s.PtrSize * 3 case *types.Struct: n := t.NumFields() if n == 0 { return 0 } fields := make([]*types.Var, t.NumFields()) maxAlign := int64(1) for i := range fields { field := t.Field(i) fields[i] = field al := s.Alignof(field.Type()) if al > maxAlign { maxAlign = al } } // Pick the size that fits this struct and add some alignment. Some // structs have some extra padding at the end which should also be taken // care of: // struct { int32 n; byte b } offsets := s.Offsetsof(fields) return align(offsets[n-1]+s.Sizeof(fields[n-1].Type()), maxAlign) case *types.Interface: return s.PtrSize * 2 case *types.Pointer, *types.Chan, *types.Map: return s.PtrSize case *types.Signature: // Func values in TinyGo are two words in size. return s.PtrSize * 2 default: panic("unknown type: " + t.String()) } } // align returns the smallest y >= x such that y % a == 0. func align(x, a int64) int64 { y := x + a - 1 return y - y%a } ================================================ FILE: compiler/symbol.go ================================================ package compiler // This file manages symbols, that is, functions and globals. It reads their // pragmas, determines the link name, etc. import ( "fmt" "go/ast" "go/token" "go/types" "strconv" "strings" "github.com/tinygo-org/tinygo/compiler/llvmutil" "github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/loader" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) // functionInfo contains some information about a function or method. In // particular, it contains information obtained from pragmas. // // The linkName value contains a valid link name, even if //go:linkname is not // present. type functionInfo struct { wasmModule string // go:wasm-module wasmName string // wasm-export-name or wasm-import-name in the IR wasmExport string // go:wasmexport is defined (export is unset, this adds an exported wrapper) wasmExportPos token.Pos // position of //go:wasmexport comment linkName string // go:linkname, go:export - the IR function name section string // go:section - object file section name exported bool // go:export, CGo interrupt bool // go:interrupt nobounds bool // go:nobounds noescape bool // go:noescape variadic bool // go:variadic (CGo only) inline inlineType // go:inline } type inlineType int // How much to inline. const ( // Default behavior. The compiler decides for itself whether any given // function will be inlined. Whether any function is inlined depends on the // optimization level. inlineDefault inlineType = iota // Inline hint, just like the C inline keyword (signalled using // //go:inline). The compiler will be more likely to inline this function, // but it is not a guarantee. inlineHint // Don't inline, just like the GCC noinline attribute. Signalled using // //go:noinline. inlineNone ) // Values for the allockind attribute. Source: // https://github.com/llvm/llvm-project/blob/release/16.x/llvm/include/llvm/IR/Attributes.h#L49 const ( allocKindAlloc = 1 << iota allocKindRealloc allocKindFree allocKindUninitialized allocKindZeroed allocKindAligned ) // getFunction returns the LLVM function for the given *ssa.Function, creating // it if needed. It can later be filled with compilerContext.createFunction(). func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) { info := c.getFunctionInfo(fn) llvmFn := c.mod.NamedFunction(info.linkName) if !llvmFn.IsNil() { return llvmFn.GlobalValueType(), llvmFn } var retType llvm.Type if fn.Signature.Results() == nil { retType = c.ctx.VoidType() } else if fn.Signature.Results().Len() == 1 { retType = c.getLLVMType(fn.Signature.Results().At(0).Type()) } else { results := make([]llvm.Type, 0, fn.Signature.Results().Len()) for i := 0; i < fn.Signature.Results().Len(); i++ { results = append(results, c.getLLVMType(fn.Signature.Results().At(i).Type())) } retType = c.ctx.StructType(results, false) } var paramInfos []paramInfo for _, param := range getParams(fn.Signature) { paramType := c.getLLVMType(param.Type()) paramFragmentInfos := c.expandFormalParamType(paramType, param.Name(), param.Type()) paramInfos = append(paramInfos, paramFragmentInfos...) } // Add an extra parameter as the function context. This context is used in // closures and bound methods, but should be optimized away when not used. if !info.exported { paramInfos = append(paramInfos, paramInfo{llvmType: c.dataPtrType, name: "context", elemSize: 0}) } var paramTypes []llvm.Type for _, info := range paramInfos { paramTypes = append(paramTypes, info.llvmType) } fnType := llvm.FunctionType(retType, paramTypes, info.variadic) llvmFn = llvm.AddFunction(c.mod, info.linkName, fnType) if strings.HasPrefix(c.Triple, "wasm") { // C functions without prototypes like this: // void foo(); // are actually variadic functions. However, it appears that it has been // decided in WebAssembly that such prototype-less functions are not // allowed in WebAssembly. // In C, this can only happen when there are zero parameters, hence this // check here. For more information: // https://reviews.llvm.org/D48443 // https://github.com/WebAssembly/tool-conventions/issues/16 if info.variadic && len(fn.Params) == 0 { attr := c.ctx.CreateStringAttribute("no-prototype", "") llvmFn.AddFunctionAttr(attr) } } c.addStandardDeclaredAttributes(llvmFn) dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null") for i, paramInfo := range paramInfos { if paramInfo.elemSize != 0 { dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, paramInfo.elemSize) llvmFn.AddAttributeAtIndex(i+1, dereferenceableOrNull) } if info.noescape && paramInfo.flags¶mIsGoParam != 0 && paramInfo.llvmType.TypeKind() == llvm.PointerTypeKind { // Parameters to functions with a //go:noescape parameter should get // the nocapture attribute. However, the context parameter should // not. // (It may be safe to add the nocapture parameter to the context // parameter, but I'd like to stay on the safe side here). nocapture := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0) llvmFn.AddAttributeAtIndex(i+1, nocapture) } if paramInfo.flags¶mIsReadonly != 0 && paramInfo.llvmType.TypeKind() == llvm.PointerTypeKind { // Readonly pointer parameters (like strings) benefit from being marked as readonly. readonly := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0) llvmFn.AddAttributeAtIndex(i+1, readonly) } } // Set a number of function or parameter attributes, depending on the // function. These functions are runtime functions that are known to have // certain attributes that might not be inferred by the compiler. switch info.linkName { case "abort": // On *nix systems, the "abort" functuion in libc is used to handle fatal panics. // Mark it as noreturn so LLVM can optimize away code. llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noreturn"), 0)) case "internal/abi.NoEscape": llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) case "machine.keepAliveNoEscape", "machine.unsafeNoEscape": llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) case "runtime.alloc": // Tell the optimizer that runtime.alloc is an allocator, meaning that it // returns values that are never null and never alias to an existing value. for _, attrName := range []string{"noalias", "nonnull"} { llvmFn.AddAttributeAtIndex(0, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(attrName), 0)) } // Add attributes to signal to LLVM that this is an allocator function. // This enables a number of optimizations. llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("allockind"), allocKindAlloc|allocKindZeroed)) llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("alloc-family", "runtime.alloc")) // Use a special value to indicate the first parameter: // > allocsize has two integer arguments, but because they're both 32 bits, we can // > pack them into one 64-bit value, at the cost of making said value // > nonsensical. // > // > In order to do this, we need to reserve one value of the second (optional) // > allocsize argument to signify "not present." llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("allocsize"), 0x0000_0000_ffff_ffff)) case "runtime.sliceAppend": // Appending a slice will only read the to-be-appended slice, it won't // be modified. llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)) case "runtime.sliceCopy": // Copying a slice won't capture any of the parameters. llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("writeonly"), 0)) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)) llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) case "runtime.stringFromBytes": llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)) case "runtime.stringFromRunes": llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)) case "runtime.trackPointer": // This function is necessary for tracking pointers on the stack in a // portable way (see gc_stack_portable.go). Indicate to the optimizer // that the only thing we'll do is read the pointer. llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)) case "__mulsi3", "__divmodsi4", "__udivmodsi4": if strings.Split(c.Triple, "-")[0] == "avr" { // These functions are compiler-rt/libgcc functions that are // currently implemented in Go. Assembly versions should appear in // LLVM 17 hopefully. Until then, they need to be made available to // the linker and the best way to do that is llvm.compiler.used. // I considered adding a pragma for this, but the LLVM language // reference explicitly says that this feature should not be exposed // to source languages: // > This is a rare construct that should only be used in rare // > circumstances, and should not be exposed to source languages. llvmutil.AppendToGlobal(c.mod, "llvm.compiler.used", llvmFn) } case "GetModuleHandleExA", "GetProcAddress", "GetSystemInfo", "GetSystemTimeAsFileTime", "LoadLibraryExW", "QueryPerformanceCounter", "QueryPerformanceFrequency", "QueryUnbiasedInterruptTime", "SetEnvironmentVariableA", "Sleep", "SystemFunction036", "VirtualAlloc": // On Windows we need to use a special calling convention for some // external calls. if c.GOOS == "windows" && c.GOARCH == "386" { llvmFn.SetFunctionCallConv(llvm.X86StdcallCallConv) } } // External/exported functions may not retain pointer values. // https://golang.org/cmd/cgo/#hdr-Passing_pointers if info.exported { if c.archFamily() == "wasm32" && len(fn.Blocks) == 0 { // We need to add the wasm-import-module and the wasm-import-name // attributes. if info.wasmModule != "" { llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("wasm-import-module", info.wasmModule)) } llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("wasm-import-name", info.wasmName)) } nocaptureKind := llvm.AttributeKindID("nocapture") nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0) for i, typ := range paramTypes { if typ.TypeKind() == llvm.PointerTypeKind { llvmFn.AddAttributeAtIndex(i+1, nocapture) } } } // Build the function if needed. c.maybeCreateSyntheticFunction(fn, llvmFn) return fnType, llvmFn } // If this is a synthetic function (such as a generic function or a wrapper), // create it now. func (c *compilerContext) maybeCreateSyntheticFunction(fn *ssa.Function, llvmFn llvm.Value) { // Synthetic functions are functions that do not appear in the source code, // they are artificially constructed. Usually they are wrapper functions // that are not referenced anywhere except in a SSA call instruction so // should be created right away. // The exception is the package initializer, which does appear in the // *ssa.Package members and so shouldn't be created here. if fn.Synthetic != "" && fn.Synthetic != "package initializer" && fn.Synthetic != "generic function" && fn.Synthetic != "range-over-func yield" { if origin := fn.Origin(); origin != nil && origin.RelString(nil) == "internal/abi.Escape" { // This is a special implementation or internal/abi.Escape, which // can only really be implemented in the compiler. // For simplicity we'll only implement pointer parameters for now. if _, ok := fn.Params[0].Type().Underlying().(*types.Pointer); ok { irbuilder := c.ctx.NewBuilder() defer irbuilder.Dispose() b := newBuilder(c, irbuilder, fn) b.createAbiEscapeImpl() llvmFn.SetLinkage(llvm.LinkOnceODRLinkage) llvmFn.SetUnnamedAddr(true) } // If the parameter is not of a pointer type, it will be left // unimplemented. This will result in a linker error if the function // is really called, making it clear it needs to be implemented. return } if len(fn.Blocks) == 0 { c.addError(fn.Pos(), "missing function body") return } irbuilder := c.ctx.NewBuilder() b := newBuilder(c, irbuilder, fn) b.createFunction() irbuilder.Dispose() llvmFn.SetLinkage(llvm.LinkOnceODRLinkage) llvmFn.SetUnnamedAddr(true) } } // getFunctionInfo returns information about a function that is not directly // present in *ssa.Function, such as the link name and whether it should be // exported. func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo { if info, ok := c.functionInfos[f]; ok { return info } info := functionInfo{ // Pick the default linkName. linkName: f.RelString(nil), } // Check for a few runtime functions that are treated specially. if info.linkName == "runtime.wasmEntryReactor" && c.BuildMode == "c-shared" { info.linkName = "_initialize" info.wasmName = "_initialize" info.exported = true } if info.linkName == "runtime.wasmEntryCommand" && c.BuildMode == "default" { info.linkName = "_start" info.wasmName = "_start" info.exported = true } if info.linkName == "runtime.wasmEntryLegacy" && c.BuildMode == "wasi-legacy" { info.linkName = "_start" info.wasmName = "_start" info.exported = true } // Check for //go: pragmas, which may change the link name (among others). c.parsePragmas(&info, f) c.functionInfos[f] = info return info } // parsePragmas is used by getFunctionInfo to parse function pragmas such as // //export or //go:noinline. func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { syntax := f.Syntax() if f.Origin() != nil { syntax = f.Origin().Syntax() } if syntax == nil { return } // Read all pragmas of this function. var pragmas []*ast.Comment hasWasmExport := false if decl, ok := syntax.(*ast.FuncDecl); ok && decl.Doc != nil { for _, comment := range decl.Doc.List { text := comment.Text if strings.HasPrefix(text, "//go:") || strings.HasPrefix(text, "//export ") { pragmas = append(pragmas, comment) if strings.HasPrefix(comment.Text, "//go:wasmexport ") { hasWasmExport = true } } } } // Parse each pragma. for _, comment := range pragmas { parts := strings.Fields(comment.Text) switch parts[0] { case "//export", "//go:export": if len(parts) != 2 { continue } if hasWasmExport { // //go:wasmexport overrides //export. continue } info.linkName = parts[1] info.wasmName = info.linkName info.exported = true case "//go:interrupt": if hasUnsafeImport(f.Pkg.Pkg) { info.interrupt = true } case "//go:wasm-module": // Alternative comment for setting the import module. // This is deprecated, use //go:wasmimport instead. if len(parts) != 2 { continue } info.wasmModule = parts[1] case "//go:wasmimport": // Import a WebAssembly function, for example a WASI function. // Original proposal: https://github.com/golang/go/issues/38248 // Allow globally: https://github.com/golang/go/issues/59149 if len(parts) != 3 { continue } if f.Blocks != nil { // Defined functions cannot be exported. c.addError(f.Pos(), "can only use //go:wasmimport on declarations") continue } c.checkWasmImportExport(f, comment.Text) info.exported = true info.wasmModule = parts[1] info.wasmName = parts[2] case "//go:wasmexport": if f.Blocks == nil { c.addError(f.Pos(), "can only use //go:wasmexport on definitions") continue } if len(parts) != 2 { c.addError(f.Pos(), fmt.Sprintf("expected one parameter to //go:wasmexport, not %d", len(parts)-1)) continue } name := parts[1] if name == "_start" || name == "_initialize" { c.addError(f.Pos(), fmt.Sprintf("//go:wasmexport does not allow %#v", name)) continue } if c.BuildMode != "c-shared" && f.RelString(nil) == "main.main" { c.addError(f.Pos(), fmt.Sprintf("//go:wasmexport does not allow main.main to be exported with -buildmode=%s", c.BuildMode)) continue } if c.archFamily() != "wasm32" { c.addError(f.Pos(), "//go:wasmexport is only supported on wasm") } c.checkWasmImportExport(f, comment.Text) info.wasmExport = name info.wasmExportPos = comment.Slash case "//go:inline": info.inline = inlineHint case "//go:noinline": info.inline = inlineNone case "//go:linkname": if len(parts) != 3 || parts[1] != f.Name() { continue } // Only enable go:linkname when the package imports "unsafe". // This is a slightly looser requirement than what gc uses: gc // requires the file to import "unsafe", not the package as a // whole. if hasUnsafeImport(f.Pkg.Pkg) { info.linkName = parts[2] } case "//go:section": // Only enable go:section when the package imports "unsafe". // go:section also implies go:noinline since inlining could // move the code to a different section than that requested. if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) { info.section = parts[1] info.inline = inlineNone } case "//go:nobounds": // Skip bounds checking in this function. Useful for some // runtime functions. // This is somewhat dangerous and thus only imported in packages // that import unsafe. if hasUnsafeImport(f.Pkg.Pkg) { info.nobounds = true } case "//go:noescape": // Don't let pointer parameters escape. // Following the upstream Go implementation, we only do this for // declarations, not definitions. if len(f.Blocks) == 0 { info.noescape = true } case "//go:variadic": // The //go:variadic pragma is emitted by the CGo preprocessing // pass for C variadic functions. This includes both explicit // (with ...) and implicit (no parameters in signature) // functions. if strings.HasPrefix(f.Name(), "_Cgo_") { // This prefix was created as a result of CGo preprocessing. info.variadic = true } } } if c.Nobounds { info.nobounds = true } } // Check whether this function can be used in //go:wasmimport or // //go:wasmexport. It will add an error if this is not the case. // // The list of allowed types is based on this proposal: // https://github.com/golang/go/issues/59149 func (c *compilerContext) checkWasmImportExport(f *ssa.Function, pragma string) { if c.pkg.Path() == "runtime" || c.pkg.Path() == "syscall/js" || c.pkg.Path() == "syscall" || c.pkg.Path() == "crypto/internal/sysrand" { // The runtime is a special case. Allow all kinds of parameters // (importantly, including pointers). return } if f.Signature.Results().Len() > 1 { c.addError(f.Signature.Results().At(1).Pos(), fmt.Sprintf("%s: too many return values", pragma)) } else if f.Signature.Results().Len() == 1 { result := f.Signature.Results().At(0) if !c.isValidWasmType(result.Type(), siteResult) { c.addError(result.Pos(), fmt.Sprintf("%s: unsupported result type %s", pragma, result.Type().String())) } } for _, param := range f.Params { // Check whether the type is allowed. // Only a very limited number of types can be mapped to WebAssembly. if !c.isValidWasmType(param.Type(), siteParam) { c.addError(param.Pos(), fmt.Sprintf("%s: unsupported parameter type %s", pragma, param.Type().String())) } } } // Check whether the type maps directly to a WebAssembly type. // // This reflects the relaxed type restrictions proposed here (except for structs.HostLayout): // https://github.com/golang/go/issues/66984 // // This previously reflected the additional restrictions documented here: // https://github.com/golang/go/issues/59149 func (c *compilerContext) isValidWasmType(typ types.Type, site wasmSite) bool { switch typ := typ.Underlying().(type) { case *types.Basic: switch typ.Kind() { case types.Bool: return true case types.Int8, types.Uint8, types.Int16, types.Uint16: return site == siteIndirect case types.Int32, types.Uint32, types.Int64, types.Uint64: return true case types.Float32, types.Float64: return true case types.Uintptr, types.UnsafePointer: return true case types.String: // string flattens to two values, so disallowed as a result return site == siteParam || site == siteIndirect } case *types.Array: return site == siteIndirect && c.isValidWasmType(typ.Elem(), siteIndirect) case *types.Struct: if site != siteIndirect { return false } // Structs with no fields do not need structs.HostLayout if typ.NumFields() == 0 { return true } hasHostLayout := true // default to true before detecting Go version // (*types.Package).GoVersion added in go1.21 if gv, ok := any(c.pkg).(interface{ GoVersion() string }); ok { if goenv.Compare(gv.GoVersion(), "go1.23") >= 0 { hasHostLayout = false // package structs added in go1.23 } } for i := 0; i < typ.NumFields(); i++ { ftyp := typ.Field(i).Type() if ftyp.String() == "structs.HostLayout" { hasHostLayout = true continue } if !c.isValidWasmType(ftyp, siteIndirect) { return false } } return hasHostLayout case *types.Pointer: return c.isValidWasmType(typ.Elem(), siteIndirect) } return false } type wasmSite int const ( siteParam wasmSite = iota siteResult siteIndirect // pointer or field ) // getParams returns the function parameters, including the receiver at the // start. This is an alternative to the Params member of *ssa.Function, which is // not yet populated when the package has not yet been built. func getParams(sig *types.Signature) []*types.Var { params := []*types.Var{} if sig.Recv() != nil { params = append(params, sig.Recv()) } for i := 0; i < sig.Params().Len(); i++ { params = append(params, sig.Params().At(i)) } return params } // addStandardDeclaredAttributes adds attributes that are set for any function, // whether declared or defined. func (c *compilerContext) addStandardDeclaredAttributes(llvmFn llvm.Value) { if c.SizeLevel >= 1 { // Set the "optsize" attribute to make slightly smaller binaries at the // cost of minimal performance loss (-Os in Clang). kind := llvm.AttributeKindID("optsize") attr := c.ctx.CreateEnumAttribute(kind, 0) llvmFn.AddFunctionAttr(attr) } if c.SizeLevel >= 2 { // Set the "minsize" attribute to reduce code size even further, // regardless of performance loss (-Oz in Clang). kind := llvm.AttributeKindID("minsize") attr := c.ctx.CreateEnumAttribute(kind, 0) llvmFn.AddFunctionAttr(attr) } if c.CPU != "" { llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("target-cpu", c.CPU)) } if c.Features != "" { llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("target-features", c.Features)) } } // addStandardDefinedAttributes adds the set of attributes that are added to // every function defined by TinyGo (even thunks/wrappers), possibly depending // on the architecture. It does not set attributes only set for declared // functions, use addStandardDeclaredAttributes for this. func (c *compilerContext) addStandardDefinedAttributes(llvmFn llvm.Value) { // TinyGo does not currently raise exceptions, so set the 'nounwind' flag. // This behavior matches Clang when compiling C source files. // It reduces binary size on Linux a little bit on non-x86_64 targets by // eliminating exception tables for these functions. llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nounwind"), 0)) if strings.Split(c.Triple, "-")[0] == "x86_64" { // Required by the ABI. // The uwtable has two possible values: sync (1) or async (2). We use // sync because we currently don't use async unwind tables. // For details, see: https://llvm.org/docs/LangRef.html#function-attributes llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("uwtable"), 1)) } } // addStandardAttributes adds all attributes added to defined functions. func (c *compilerContext) addStandardAttributes(llvmFn llvm.Value) { c.addStandardDeclaredAttributes(llvmFn) c.addStandardDefinedAttributes(llvmFn) } // globalInfo contains some information about a specific global. By default, // linkName is equal to .RelString(nil) on a global and extern is false, but for // some symbols this is different (due to //go:extern for example). type globalInfo struct { linkName string // go:extern, go:linkname extern bool // go:extern align int // go:align section string // go:section } // loadASTComments loads comments on globals from the AST, for use later in the // program. In particular, they are required for //go:extern pragmas on globals. func (c *compilerContext) loadASTComments(pkg *loader.Package) { for _, file := range pkg.Files { for _, decl := range file.Decls { switch decl := decl.(type) { case *ast.GenDecl: switch decl.Tok { case token.VAR: if len(decl.Specs) != 1 { continue } for _, spec := range decl.Specs { switch spec := spec.(type) { case *ast.ValueSpec: // decl.Tok == token.VAR for _, name := range spec.Names { id := pkg.Pkg.Path() + "." + name.Name c.astComments[id] = decl.Doc } } } } } } } } // getGlobal returns a LLVM IR global value for a Go SSA global. It is added to // the LLVM IR if it has not been added already. func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value { info := c.getGlobalInfo(g) llvmGlobal := c.mod.NamedGlobal(info.linkName) if llvmGlobal.IsNil() { typ := g.Type().(*types.Pointer).Elem() llvmType := c.getLLVMType(typ) llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName) // Set alignment from the //go:align comment. alignment := c.targetData.ABITypeAlignment(llvmType) if info.align > alignment { alignment = info.align } if alignment <= 0 || alignment&(alignment-1) != 0 { // Check for power-of-two (or 0). // See: https://stackoverflow.com/a/108360 c.addError(g.Pos(), "global variable alignment must be a positive power of two") } else { // Set the alignment only when it is a power of two. llvmGlobal.SetAlignment(alignment) } if c.Debug && !info.extern { // Add debug info. pos := c.program.Fset.Position(g.Pos()) diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{ Name: g.RelString(nil), LinkageName: info.linkName, File: c.getDIFile(pos.Filename), Line: pos.Line, Type: c.getDIType(typ), LocalToUnit: false, Expr: c.dibuilder.CreateExpression(nil), AlignInBits: uint32(alignment) * 8, }) llvmGlobal.AddMetadata(0, diglobal) } } return llvmGlobal } // getGlobalInfo returns some information about a specific global. func (c *compilerContext) getGlobalInfo(g *ssa.Global) globalInfo { info := globalInfo{ // Pick the default linkName. linkName: g.RelString(nil), } // Check for //go: pragmas, which may change the link name (among others). doc := c.astComments[info.linkName] if doc != nil { info.parsePragmas(doc, c, g) } return info } // Parse //go: pragma comments from the source. In particular, it parses the // //go:extern and //go:linkname pragmas on globals. func (info *globalInfo) parsePragmas(doc *ast.CommentGroup, c *compilerContext, g *ssa.Global) { for _, comment := range doc.List { if !strings.HasPrefix(comment.Text, "//go:") { continue } parts := strings.Fields(comment.Text) switch parts[0] { case "//go:extern": info.extern = true if len(parts) == 2 { info.linkName = parts[1] } case "//go:align": align, err := strconv.Atoi(parts[1]) if err == nil { info.align = align } case "//go:section": if len(parts) == 2 { info.section = parts[1] } case "//go:linkname": if len(parts) != 3 || parts[1] != g.Name() { continue } // Only enable go:linkname when the package imports "unsafe". // This is a slightly looser requirement than what gc uses: gc // requires the file to import "unsafe", not the package as a // whole. if hasUnsafeImport(g.Pkg.Pkg) { info.linkName = parts[2] } } } } // Get all methods of a type. func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection { ms := prog.MethodSets.MethodSet(typ) methods := make([]*types.Selection, ms.Len()) for i := 0; i < ms.Len(); i++ { methods[i] = ms.At(i) } return methods } // Return true if this package imports "unsafe", false otherwise. func hasUnsafeImport(pkg *types.Package) bool { for _, imp := range pkg.Imports() { if imp == types.Unsafe { return true } } return false } ================================================ FILE: compiler/syscall.go ================================================ package compiler // This file implements the syscall.Syscall and syscall.Syscall6 instructions as // compiler builtins. import ( "strconv" "strings" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) // createRawSyscall creates a system call with the provided system call number // and returns the result as a single integer (the system call result). The // result is not further interpreted (with the exception of MIPS to use the same // return value everywhere). func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) { num := b.getValue(call.Args[0], getPos(call)) switch { case b.GOARCH == "amd64" && b.GOOS == "linux": // Sources: // https://stackoverflow.com/a/2538212 // https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscall args := []llvm.Value{num} argTypes := []llvm.Type{b.uintptrType} // Constraints will look something like: // "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}" constraints := "={rax},0" for i, arg := range call.Args[1:] { constraints += "," + [...]string{ "{rdi}", "{rsi}", "{rdx}", "{r10}", "{r8}", "{r9}", }[i] llvmValue := b.getValue(arg, getPos(call)) args = append(args, llvmValue) argTypes = append(argTypes, llvmValue.Type()) } // rcx and r11 are clobbered by the syscall, so make sure they are not used constraints += ",~{rcx},~{r11}" fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel, false) return b.CreateCall(fnType, target, args, ""), nil case b.GOARCH == "386" && b.GOOS == "linux": // Sources: // syscall(2) man page // https://stackoverflow.com/a/2538212 // https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#int_0x80 args := []llvm.Value{num} argTypes := []llvm.Type{b.uintptrType} // Constraints will look something like: // "={eax},0,{ebx},{ecx},{edx},{esi},{edi},{ebp}" constraints := "={eax},0" for i, arg := range call.Args[1:] { constraints += "," + [...]string{ "{ebx}", "{ecx}", "{edx}", "{esi}", "{edi}", "{ebp}", }[i] llvmValue := b.getValue(arg, getPos(call)) args = append(args, llvmValue) argTypes = append(argTypes, llvmValue.Type()) } fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel, false) return b.CreateCall(fnType, target, args, ""), nil case b.GOARCH == "arm" && b.GOOS == "linux": if arch := b.archFamily(); arch != "arm" { // Some targets pretend to be linux/arm for compatibility but aren't // actually such a system. Make sure we emit an error instead of // creating inline assembly that will fail to compile. // See: https://github.com/tinygo-org/tinygo/issues/4959 return llvm.Value{}, b.makeError(call.Pos(), "system calls are not supported: target emulates a linux/arm system on "+arch) } // Implement the EABI system call convention for Linux. // Source: syscall(2) man page. args := []llvm.Value{} argTypes := []llvm.Type{} // Constraints will look something like: // ={r0},0,{r1},{r2},{r7},~{r3} constraints := "={r0}" for i, arg := range call.Args[1:] { constraints += "," + [...]string{ "0", // tie to output "{r1}", "{r2}", "{r3}", "{r4}", "{r5}", "{r6}", }[i] llvmValue := b.getValue(arg, getPos(call)) args = append(args, llvmValue) argTypes = append(argTypes, llvmValue.Type()) } args = append(args, num) argTypes = append(argTypes, b.uintptrType) constraints += ",{r7}" // syscall number for i := len(call.Args) - 1; i < 4; i++ { // r0-r3 get clobbered after the syscall returns constraints += ",~{r" + strconv.Itoa(i) + "}" } fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false) return b.CreateCall(fnType, target, args, ""), nil case b.GOARCH == "arm64" && b.GOOS == "linux": // Source: syscall(2) man page. args := []llvm.Value{} argTypes := []llvm.Type{} // Constraints will look something like: // ={x0},0,{x1},{x2},{x8},~{x3},~{x4},~{x5},~{x6},~{x7},~{x16},~{x17} constraints := "={x0}" for i, arg := range call.Args[1:] { constraints += "," + [...]string{ "0", // tie to output "{x1}", "{x2}", "{x3}", "{x4}", "{x5}", }[i] llvmValue := b.getValue(arg, getPos(call)) args = append(args, llvmValue) argTypes = append(argTypes, llvmValue.Type()) } args = append(args, num) argTypes = append(argTypes, b.uintptrType) constraints += ",{x8}" // syscall number for i := len(call.Args) - 1; i < 8; i++ { // x0-x7 may get clobbered during the syscall following the aarch64 // calling convention. constraints += ",~{x" + strconv.Itoa(i) + "}" } constraints += ",~{x16},~{x17}" // scratch registers fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false) return b.CreateCall(fnType, target, args, ""), nil case (b.GOARCH == "mips" || b.GOARCH == "mipsle") && b.GOOS == "linux": // Implement the system call convention for Linux. // Source: syscall(2) man page and musl: // https://git.musl-libc.org/cgit/musl/tree/arch/mips/syscall_arch.h // Also useful: // https://web.archive.org/web/20220529105937/https://www.linux-mips.org/wiki/Syscall // The syscall number goes in r2, the result also in r2. // Register r7 is both an input parameter and an output parameter: if it // is non-zero, the system call failed and r2 is the error code. // The code below implements the O32 syscall ABI, not the N32 ABI. It // could implement both at the same time if needed (like what appears to // be done in musl) by forcing arg5-arg7 into the right registers but // letting the compiler decide the registers should result in _slightly_ // faster and smaller code. args := []llvm.Value{num} argTypes := []llvm.Type{b.uintptrType} constraints := "={$2},={$7},0" syscallParams := call.Args[1:] if len(syscallParams) > 7 { // There is one syscall that uses 7 parameters: sync_file_range. // But only 7, not more. Go however only has Syscall6 and Syscall9. // Therefore, we can ignore the remaining parameters. syscallParams = syscallParams[:7] } for i, arg := range syscallParams { constraints += "," + [...]string{ "{$4}", // arg1 "{$5}", // arg2 "{$6}", // arg3 "1", // arg4, error return "r", // arg5 on the stack "r", // arg6 on the stack "r", // arg7 on the stack }[i] llvmValue := b.getValue(arg, getPos(call)) args = append(args, llvmValue) argTypes = append(argTypes, llvmValue.Type()) } // Create assembly code. // Parameters beyond the first 4 are passed on the stack instead of in // registers in the O32 syscall ABI. // We need ".set noat" because LLVM might pick register $1 ($at) as the // register for a parameter and apparently this is not allowed on MIPS // unless you use this specific pragma. asm := "syscall" switch len(syscallParams) { case 5: asm = "" + ".set noat\n" + "subu $$sp, $$sp, 32\n" + "sw $7, 16($$sp)\n" + // arg5 "syscall\n" + "addu $$sp, $$sp, 32\n" + ".set at\n" case 6: asm = "" + ".set noat\n" + "subu $$sp, $$sp, 32\n" + "sw $7, 16($$sp)\n" + // arg5 "sw $8, 20($$sp)\n" + // arg6 "syscall\n" + "addu $$sp, $$sp, 32\n" + ".set at\n" case 7: asm = "" + ".set noat\n" + "subu $$sp, $$sp, 32\n" + "sw $7, 16($$sp)\n" + // arg5 "sw $8, 20($$sp)\n" + // arg6 "sw $9, 24($$sp)\n" + // arg7 "syscall\n" + "addu $$sp, $$sp, 32\n" + ".set at\n" } constraints += ",~{$3},~{$4},~{$5},~{$6},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$24},~{$25},~{hi},~{lo},~{memory}" returnType := b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false) fnType := llvm.FunctionType(returnType, argTypes, false) target := llvm.InlineAsm(fnType, asm, constraints, true, true, 0, false) call := b.CreateCall(fnType, target, args, "") resultCode := b.CreateExtractValue(call, 0, "") // r2 errorFlag := b.CreateExtractValue(call, 1, "") // r7 // Pseudocode to return the result with the same convention as other // archs: // return (errorFlag != 0) ? -resultCode : resultCode; // At least on QEMU with the O32 ABI, the error code is always positive. zero := llvm.ConstInt(b.uintptrType, 0, false) isError := b.CreateICmp(llvm.IntNE, errorFlag, zero, "") negativeResult := b.CreateSub(zero, resultCode, "") result := b.CreateSelect(isError, negativeResult, resultCode, "") return result, nil default: return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH) } } // createSyscall emits instructions for the syscall.Syscall* family of // functions, depending on the target OS/arch. func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) { switch b.GOOS { case "linux": syscallResult, err := b.createRawSyscall(call) if err != nil { return syscallResult, err } // Return values: r0, r1 uintptr, err Errno // Pseudocode: // var err uintptr // if syscallResult < 0 && syscallResult > -4096 { // err = -syscallResult // } // return syscallResult, 0, err zero := llvm.ConstInt(b.uintptrType, 0, false) inrange1 := b.CreateICmp(llvm.IntSLT, syscallResult, llvm.ConstInt(b.uintptrType, 0, false), "") inrange2 := b.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(b.uintptrType, 0xfffffffffffff000, true), "") // -4096 hasError := b.CreateAnd(inrange1, inrange2, "") errResult := b.CreateSelect(hasError, b.CreateSub(zero, syscallResult, ""), zero, "syscallError") retval := llvm.Undef(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.uintptrType}, false)) retval = b.CreateInsertValue(retval, syscallResult, 0, "") retval = b.CreateInsertValue(retval, zero, 1, "") retval = b.CreateInsertValue(retval, errResult, 2, "") return retval, nil case "windows": // On Windows, syscall.Syscall* is basically just a function pointer // call. This is complicated in gc because of stack switching and the // different ABI, but easy in TinyGo: just call the function pointer. // The signature looks like this: // func Syscall(trap, nargs, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno) isI386 := strings.HasPrefix(b.Triple, "i386-") // Prepare input values. var paramTypes []llvm.Type var params []llvm.Value for _, val := range call.Args[2:] { param := b.getValue(val, getPos(call)) params = append(params, param) paramTypes = append(paramTypes, param.Type()) } llvmType := llvm.FunctionType(b.uintptrType, paramTypes, false) fn := b.getValue(call.Args[0], getPos(call)) fnPtr := b.CreateIntToPtr(fn, b.dataPtrType, "") // Prepare some functions that will be called later. setLastError := b.mod.NamedFunction("SetLastError") if setLastError.IsNil() { llvmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.ctx.Int32Type()}, false) setLastError = llvm.AddFunction(b.mod, "SetLastError", llvmType) if isI386 { setLastError.SetFunctionCallConv(llvm.X86StdcallCallConv) } } getLastError := b.mod.NamedFunction("GetLastError") if getLastError.IsNil() { llvmType := llvm.FunctionType(b.ctx.Int32Type(), nil, false) getLastError = llvm.AddFunction(b.mod, "GetLastError", llvmType) if isI386 { getLastError.SetFunctionCallConv(llvm.X86StdcallCallConv) } } // Now do the actual call. Pseudocode: // SetLastError(0) // r1 = trap(a1, a2, a3, ...) // err = uintptr(GetLastError()) // return r1, 0, err // Note that SetLastError/GetLastError could be replaced with direct // access to the thread control block, which is probably smaller and // faster. The Go runtime does this in assembly. // On windows/386, we also need to save/restore the stack pointer. I'm // not entirely sure why this is needed, but without it these calls // change the stack pointer leading to a crash soon after. setLastErrorCall := b.CreateCall(setLastError.GlobalValueType(), setLastError, []llvm.Value{llvm.ConstNull(b.ctx.Int32Type())}, "") var sp llvm.Value if isI386 { setLastErrorCall.SetInstructionCallConv(llvm.X86StdcallCallConv) sp = b.readStackPointer() } syscallResult := b.CreateCall(llvmType, fnPtr, params, "") if isI386 { syscallResult.SetInstructionCallConv(llvm.X86StdcallCallConv) b.writeStackPointer(sp) } errResult := b.CreateCall(getLastError.GlobalValueType(), getLastError, nil, "err") if isI386 { errResult.SetInstructionCallConv(llvm.X86StdcallCallConv) } if b.uintptrType != b.ctx.Int32Type() { errResult = b.CreateZExt(errResult, b.uintptrType, "err.uintptr") } // Return r1, 0, err retval := llvm.ConstNull(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.uintptrType}, false)) retval = b.CreateInsertValue(retval, syscallResult, 0, "") retval = b.CreateInsertValue(retval, errResult, 2, "") return retval, nil default: return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH) } } // createRawSyscallNoError emits instructions for the Linux-specific // syscall.rawSyscallNoError function. func (b *builder) createRawSyscallNoError(call *ssa.CallCommon) (llvm.Value, error) { syscallResult, err := b.createRawSyscall(call) if err != nil { return syscallResult, err } retval := llvm.ConstNull(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false)) retval = b.CreateInsertValue(retval, syscallResult, 0, "") retval = b.CreateInsertValue(retval, llvm.ConstInt(b.uintptrType, 0, false), 1, "") return retval, nil } // Lower a call to internal/abi.FuncPCABI0 on MacOS. // This function is called like this: // // syscall(abi.FuncPCABI0(libc_mkdir_trampoline), uintptr(unsafe.Pointer(_p0)), uintptr(mode), 0) // // So we'll want to return a function pointer (as uintptr) that points to the // libc function. Specifically, we _don't_ want to point to the trampoline // function (which is implemented in Go assembly which we can't read), but // rather to the actually intended function. For this we're going to assume that // all the functions follow a specific pattern: libc__trampoline. // // The return value is the function pointer as an uintptr, or a nil value if // this isn't possible (and a regular call should be made as fallback). func (b *builder) createDarwinFuncPCABI0Call(instr *ssa.CallCommon) llvm.Value { if b.GOOS != "darwin" { // This has only been tested on MacOS (and only seems to be used there). return llvm.Value{} } // Check that it uses a function call like syscall.libc_*_trampoline itf := instr.Args[0].(*ssa.MakeInterface) calledFn := itf.X.(*ssa.Function) if pkgName := calledFn.Pkg.Pkg.Path(); pkgName != "syscall" && pkgName != "internal/syscall/unix" { return llvm.Value{} } if !strings.HasPrefix(calledFn.Name(), "libc_") || !strings.HasSuffix(calledFn.Name(), "_trampoline") { return llvm.Value{} } // Extract the libc function name. name := strings.TrimPrefix(strings.TrimSuffix(calledFn.Name(), "_trampoline"), "libc_") if name == "open" { // Special case: open() is a variadic function and can't be called like // a regular function. Therefore, we need to use a wrapper implemented // in C. name = "syscall_libc_open" } if b.GOARCH == "amd64" { if name == "fdopendir" || name == "readdir_r" { // Hack to support amd64, which needs the $INODE64 suffix. // This is also done in upstream Go: // https://github.com/golang/go/commit/096ab3c21b88ccc7d411379d09fe6274e3159467 name += "$INODE64" } } // Obtain the C function. // Use a simple function (no parameters or return value) because all we need // is the address of the function. llvmFn := b.mod.NamedFunction(name) if llvmFn.IsNil() { llvmFnType := llvm.FunctionType(b.ctx.VoidType(), nil, false) llvmFn = llvm.AddFunction(b.mod, name, llvmFnType) } // Cast the function pointer to a uintptr (because that's what // abi.FuncPCABI0 returns). return b.CreatePtrToInt(llvmFn, b.uintptrType, "") } ================================================ FILE: compiler/testdata/basic.go ================================================ package main // Basic tests that don't need to be split into a separate file. func addInt(x, y int) int { return x + y } func equalInt(x, y int) bool { return x == y } func divInt(x, y int) int { return x / y } func divUint(x, y uint) uint { return x / y } func remInt(x, y int) int { return x % y } func remUint(x, y uint) uint { return x % y } func floatEQ(x, y float32) bool { return x == y } func floatNE(x, y float32) bool { return x != y } func floatLower(x, y float32) bool { return x < y } func floatLowerEqual(x, y float32) bool { return x <= y } func floatGreater(x, y float32) bool { return x > y } func floatGreaterEqual(x, y float32) bool { return x >= y } func complexReal(x complex64) float32 { return real(x) } func complexImag(x complex64) float32 { return imag(x) } func complexAdd(x, y complex64) complex64 { return x + y } func complexSub(x, y complex64) complex64 { return x - y } func complexMul(x, y complex64) complex64 { return x * y } // TODO: complexDiv (requires runtime call) // A type 'kv' also exists in function foo. Test that these two types don't // conflict with each other. type kv struct { v float32 x, y, z int } var kvGlobal kv func foo() { // Define a new 'kv' type. type kv struct { v byte x, y, z int } // Use this type. func(b kv) {}(kv{}) } type T1 []T1 type T2 [2]*T2 var a T1 var b T2 ================================================ FILE: compiler/testdata/basic.ll ================================================ ; ModuleID = 'basic.go' source_filename = "basic.go" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20" target triple = "wasm32-unknown-wasi" %main.kv = type { float, i32, i32, i32 } %main.kv.0 = type { i8, i32, i32, i32 } @main.kvGlobal = hidden global %main.kv zeroinitializer, align 4 @main.a = hidden global { ptr, i32, i32 } zeroinitializer, align 4 @main.b = hidden global [2 x ptr] zeroinitializer, align 4 ; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1 ; Function Attrs: nounwind define hidden void @main.init(ptr %context) unnamed_addr #2 { entry: ret void } ; Function Attrs: nounwind define hidden i32 @main.addInt(i32 %x, i32 %y, ptr %context) unnamed_addr #2 { entry: %0 = add i32 %x, %y ret i32 %0 } ; Function Attrs: nounwind define hidden i1 @main.equalInt(i32 %x, i32 %y, ptr %context) unnamed_addr #2 { entry: %0 = icmp eq i32 %x, %y ret i1 %0 } ; Function Attrs: nounwind define hidden i32 @main.divInt(i32 %x, i32 %y, ptr %context) unnamed_addr #2 { entry: %0 = icmp eq i32 %y, 0 br i1 %0, label %divbyzero.throw, label %divbyzero.next divbyzero.next: ; preds = %entry %1 = icmp eq i32 %y, -1 %2 = icmp eq i32 %x, -2147483648 %3 = and i1 %1, %2 %4 = select i1 %3, i32 1, i32 %y %5 = sdiv i32 %x, %4 ret i32 %5 divbyzero.throw: ; preds = %entry call void @runtime.divideByZeroPanic(ptr undef) #3 unreachable } declare void @runtime.divideByZeroPanic(ptr) #1 ; Function Attrs: nounwind define hidden i32 @main.divUint(i32 %x, i32 %y, ptr %context) unnamed_addr #2 { entry: %0 = icmp eq i32 %y, 0 br i1 %0, label %divbyzero.throw, label %divbyzero.next divbyzero.next: ; preds = %entry %1 = udiv i32 %x, %y ret i32 %1 divbyzero.throw: ; preds = %entry call void @runtime.divideByZeroPanic(ptr undef) #3 unreachable } ; Function Attrs: nounwind define hidden i32 @main.remInt(i32 %x, i32 %y, ptr %context) unnamed_addr #2 { entry: %0 = icmp eq i32 %y, 0 br i1 %0, label %divbyzero.throw, label %divbyzero.next divbyzero.next: ; preds = %entry %1 = icmp eq i32 %y, -1 %2 = icmp eq i32 %x, -2147483648 %3 = and i1 %1, %2 %4 = select i1 %3, i32 1, i32 %y %5 = srem i32 %x, %4 ret i32 %5 divbyzero.throw: ; preds = %entry call void @runtime.divideByZeroPanic(ptr undef) #3 unreachable } ; Function Attrs: nounwind define hidden i32 @main.remUint(i32 %x, i32 %y, ptr %context) unnamed_addr #2 { entry: %0 = icmp eq i32 %y, 0 br i1 %0, label %divbyzero.throw, label %divbyzero.next divbyzero.next: ; preds = %entry %1 = urem i32 %x, %y ret i32 %1 divbyzero.throw: ; preds = %entry call void @runtime.divideByZeroPanic(ptr undef) #3 unreachable } ; Function Attrs: nounwind define hidden i1 @main.floatEQ(float %x, float %y, ptr %context) unnamed_addr #2 { entry: %0 = fcmp oeq float %x, %y ret i1 %0 } ; Function Attrs: nounwind define hidden i1 @main.floatNE(float %x, float %y, ptr %context) unnamed_addr #2 { entry: %0 = fcmp une float %x, %y ret i1 %0 } ; Function Attrs: nounwind define hidden i1 @main.floatLower(float %x, float %y, ptr %context) unnamed_addr #2 { entry: %0 = fcmp olt float %x, %y ret i1 %0 } ; Function Attrs: nounwind define hidden i1 @main.floatLowerEqual(float %x, float %y, ptr %context) unnamed_addr #2 { entry: %0 = fcmp ole float %x, %y ret i1 %0 } ; Function Attrs: nounwind define hidden i1 @main.floatGreater(float %x, float %y, ptr %context) unnamed_addr #2 { entry: %0 = fcmp ogt float %x, %y ret i1 %0 } ; Function Attrs: nounwind define hidden i1 @main.floatGreaterEqual(float %x, float %y, ptr %context) unnamed_addr #2 { entry: %0 = fcmp oge float %x, %y ret i1 %0 } ; Function Attrs: nounwind define hidden float @main.complexReal(float %x.r, float %x.i, ptr %context) unnamed_addr #2 { entry: ret float %x.r } ; Function Attrs: nounwind define hidden float @main.complexImag(float %x.r, float %x.i, ptr %context) unnamed_addr #2 { entry: ret float %x.i } ; Function Attrs: nounwind define hidden { float, float } @main.complexAdd(float %x.r, float %x.i, float %y.r, float %y.i, ptr %context) unnamed_addr #2 { entry: %0 = fadd float %x.r, %y.r %1 = fadd float %x.i, %y.i %2 = insertvalue { float, float } undef, float %0, 0 %3 = insertvalue { float, float } %2, float %1, 1 ret { float, float } %3 } ; Function Attrs: nounwind define hidden { float, float } @main.complexSub(float %x.r, float %x.i, float %y.r, float %y.i, ptr %context) unnamed_addr #2 { entry: %0 = fsub float %x.r, %y.r %1 = fsub float %x.i, %y.i %2 = insertvalue { float, float } undef, float %0, 0 %3 = insertvalue { float, float } %2, float %1, 1 ret { float, float } %3 } ; Function Attrs: nounwind define hidden { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, ptr %context) unnamed_addr #2 { entry: %0 = fmul float %x.r, %y.r %1 = fmul float %x.i, %y.i %2 = fsub float %0, %1 %3 = fmul float %x.r, %y.i %4 = fmul float %x.i, %y.r %5 = fadd float %3, %4 %6 = insertvalue { float, float } undef, float %2, 0 %7 = insertvalue { float, float } %6, float %5, 1 ret { float, float } %7 } ; Function Attrs: nounwind define hidden void @main.foo(ptr %context) unnamed_addr #2 { entry: call void @"main.foo$1"(%main.kv.0 zeroinitializer, ptr undef) ret void } ; Function Attrs: nounwind define internal void @"main.foo$1"(%main.kv.0 %b, ptr %context) unnamed_addr #2 { entry: ret void } attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #1 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind } ================================================ FILE: compiler/testdata/channel.go ================================================ package main func chanIntSend(ch chan int) { ch <- 3 } func chanIntRecv(ch chan int) { <-ch } func chanZeroSend(ch chan struct{}) { ch <- struct{}{} } func chanZeroRecv(ch chan struct{}) { <-ch } func selectZeroRecv(ch1 chan int, ch2 chan struct{}) { select { case ch1 <- 1: case <-ch2: default: } } ================================================ FILE: compiler/testdata/channel.ll ================================================ ; ModuleID = 'channel.go' source_filename = "channel.go" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20" target triple = "wasm32-unknown-wasi" %runtime.channelOp = type { ptr, ptr, i32, ptr } %runtime.chanSelectState = type { ptr, ptr } ; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1 ; Function Attrs: nounwind define hidden void @main.init(ptr %context) unnamed_addr #2 { entry: ret void } ; Function Attrs: nounwind define hidden void @main.chanIntSend(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 { entry: %chan.op = alloca %runtime.channelOp, align 8 %chan.value = alloca i32, align 4 call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %chan.value) store i32 3, ptr %chan.value, align 4 call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) call void @runtime.chanSend(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #4 call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %chan.value) ret void } ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #3 declare void @runtime.chanSend(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #1 ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #3 ; Function Attrs: nounwind define hidden void @main.chanIntRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 { entry: %chan.op = alloca %runtime.channelOp, align 8 %chan.value = alloca i32, align 4 call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %chan.value) call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) %0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #4 call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %chan.value) call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) ret void } declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #1 ; Function Attrs: nounwind define hidden void @main.chanZeroSend(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 { entry: %chan.op = alloca %runtime.channelOp, align 8 call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) call void @runtime.chanSend(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #4 call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) ret void } ; Function Attrs: nounwind define hidden void @main.chanZeroRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 { entry: %chan.op = alloca %runtime.channelOp, align 8 call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) %0 = call i1 @runtime.chanRecv(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #4 call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) ret void } ; Function Attrs: nounwind define hidden void @main.selectZeroRecv(ptr dereferenceable_or_null(36) %ch1, ptr dereferenceable_or_null(36) %ch2, ptr %context) unnamed_addr #2 { entry: %select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8 %select.send.value = alloca i32, align 4 store i32 1, ptr %select.send.value, align 4 call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %select.states.alloca) store ptr %ch1, ptr %select.states.alloca, align 4 %select.states.alloca.repack1 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 4 store ptr %select.send.value, ptr %select.states.alloca.repack1, align 4 %0 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 8 store ptr %ch2, ptr %0, align 4 %.repack3 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 12 store ptr null, ptr %.repack3, align 4 %select.result = call { i32, i1 } @runtime.chanSelect(ptr undef, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr null, i32 0, i32 0, ptr undef) #4 call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %select.states.alloca) %1 = extractvalue { i32, i1 } %select.result, 0 %2 = icmp eq i32 %1, 0 br i1 %2, label %select.done, label %select.next select.done: ; preds = %select.body, %select.next, %entry ret void select.next: ; preds = %entry %3 = icmp eq i32 %1, 1 br i1 %3, label %select.body, label %select.done select.body: ; preds = %select.next br label %select.done } declare { i32, i1 } @runtime.chanSelect(ptr, ptr, i32, i32, ptr, i32, i32, ptr) #1 attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #1 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } attributes #4 = { nounwind } ================================================ FILE: compiler/testdata/defer-cortex-m-qemu.ll ================================================ ; ModuleID = 'defer.go' source_filename = "defer.go" target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64" target triple = "thumbv7m-unknown-unknown-eabi" %runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i8, %runtime._interface } %runtime._interface = type { ptr, ptr } ; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 ; Function Attrs: nounwind define hidden void @main.init(ptr %context) unnamed_addr #1 { entry: ret void } declare void @main.external(ptr) #2 ; Function Attrs: nounwind define hidden void @main.deferSimple(ptr %context) unnamed_addr #1 { entry: %defer.alloca = alloca { i32, ptr }, align 4 %deferPtr = alloca ptr, align 4 store ptr null, ptr %deferPtr, align 4 %deferframe.buf = alloca %runtime.deferFrame, align 4 %0 = call ptr @llvm.stacksave.p0() call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4 store i32 0, ptr %defer.alloca, align 4 %defer.alloca.repack15 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4 store ptr null, ptr %defer.alloca.repack15, align 4 store ptr %defer.alloca, ptr %deferPtr, align 4 %setjmp = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 %setjmp.result = icmp eq i32 %setjmp, 0 br i1 %setjmp.result, label %1, label %lpad 1: ; preds = %entry call void @main.external(ptr undef) #4 br label %rundefers.block rundefers.after: ; preds = %rundefers.end call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4 ret void rundefers.block: ; preds = %1 br label %rundefers.loophead rundefers.loophead: ; preds = %3, %rundefers.block %2 = load ptr, ptr %deferPtr, align 4 %stackIsNil = icmp eq ptr %2, null br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop rundefers.loop: ; preds = %rundefers.loophead %stack.next.gep = getelementptr inbounds nuw i8, ptr %2, i32 4 %stack.next = load ptr, ptr %stack.next.gep, align 4 store ptr %stack.next, ptr %deferPtr, align 4 %callback = load i32, ptr %2, align 4 switch i32 %callback, label %rundefers.default [ i32 0, label %rundefers.callback0 ] rundefers.callback0: ; preds = %rundefers.loop %setjmp1 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 %setjmp.result2 = icmp eq i32 %setjmp1, 0 br i1 %setjmp.result2, label %3, label %lpad 3: ; preds = %rundefers.callback0 call void @"main.deferSimple$1"(ptr undef) br label %rundefers.loophead rundefers.default: ; preds = %rundefers.loop unreachable rundefers.end: ; preds = %rundefers.loophead br label %rundefers.after recover: ; preds = %rundefers.end3 call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4 ret void lpad: ; preds = %rundefers.callback012, %rundefers.callback0, %entry br label %rundefers.loophead6 rundefers.loophead6: ; preds = %5, %lpad %4 = load ptr, ptr %deferPtr, align 4 %stackIsNil7 = icmp eq ptr %4, null br i1 %stackIsNil7, label %rundefers.end3, label %rundefers.loop5 rundefers.loop5: ; preds = %rundefers.loophead6 %stack.next.gep8 = getelementptr inbounds nuw i8, ptr %4, i32 4 %stack.next9 = load ptr, ptr %stack.next.gep8, align 4 store ptr %stack.next9, ptr %deferPtr, align 4 %callback11 = load i32, ptr %4, align 4 switch i32 %callback11, label %rundefers.default4 [ i32 0, label %rundefers.callback012 ] rundefers.callback012: ; preds = %rundefers.loop5 %setjmp13 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 %setjmp.result14 = icmp eq i32 %setjmp13, 0 br i1 %setjmp.result14, label %5, label %lpad 5: ; preds = %rundefers.callback012 call void @"main.deferSimple$1"(ptr undef) br label %rundefers.loophead6 rundefers.default4: ; preds = %rundefers.loop5 unreachable rundefers.end3: ; preds = %rundefers.loophead6 br label %recover } ; Function Attrs: nocallback nofree nosync nounwind willreturn declare ptr @llvm.stacksave.p0() #3 declare void @runtime.setupDeferFrame(ptr dereferenceable_or_null(24), ptr, ptr) #2 declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(24), ptr) #2 ; Function Attrs: nounwind define internal void @"main.deferSimple$1"(ptr %context) unnamed_addr #1 { entry: call void @runtime.printlock(ptr undef) #4 call void @runtime.printint32(i32 3, ptr undef) #4 call void @runtime.printunlock(ptr undef) #4 ret void } declare void @runtime.printlock(ptr) #2 declare void @runtime.printint32(i32, ptr) #2 declare void @runtime.printunlock(ptr) #2 ; Function Attrs: nounwind define hidden void @main.deferMultiple(ptr %context) unnamed_addr #1 { entry: %defer.alloca2 = alloca { i32, ptr }, align 4 %defer.alloca = alloca { i32, ptr }, align 4 %deferPtr = alloca ptr, align 4 store ptr null, ptr %deferPtr, align 4 %deferframe.buf = alloca %runtime.deferFrame, align 4 %0 = call ptr @llvm.stacksave.p0() call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4 store i32 0, ptr %defer.alloca, align 4 %defer.alloca.repack22 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4 store ptr null, ptr %defer.alloca.repack22, align 4 store ptr %defer.alloca, ptr %deferPtr, align 4 store i32 1, ptr %defer.alloca2, align 4 %defer.alloca2.repack23 = getelementptr inbounds nuw i8, ptr %defer.alloca2, i32 4 store ptr %defer.alloca, ptr %defer.alloca2.repack23, align 4 store ptr %defer.alloca2, ptr %deferPtr, align 4 %setjmp = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 %setjmp.result = icmp eq i32 %setjmp, 0 br i1 %setjmp.result, label %1, label %lpad 1: ; preds = %entry call void @main.external(ptr undef) #4 br label %rundefers.block rundefers.after: ; preds = %rundefers.end call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4 ret void rundefers.block: ; preds = %1 br label %rundefers.loophead rundefers.loophead: ; preds = %4, %3, %rundefers.block %2 = load ptr, ptr %deferPtr, align 4 %stackIsNil = icmp eq ptr %2, null br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop rundefers.loop: ; preds = %rundefers.loophead %stack.next.gep = getelementptr inbounds nuw i8, ptr %2, i32 4 %stack.next = load ptr, ptr %stack.next.gep, align 4 store ptr %stack.next, ptr %deferPtr, align 4 %callback = load i32, ptr %2, align 4 switch i32 %callback, label %rundefers.default [ i32 0, label %rundefers.callback0 i32 1, label %rundefers.callback1 ] rundefers.callback0: ; preds = %rundefers.loop %setjmp3 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 %setjmp.result4 = icmp eq i32 %setjmp3, 0 br i1 %setjmp.result4, label %3, label %lpad 3: ; preds = %rundefers.callback0 call void @"main.deferMultiple$1"(ptr undef) br label %rundefers.loophead rundefers.callback1: ; preds = %rundefers.loop %setjmp5 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 %setjmp.result6 = icmp eq i32 %setjmp5, 0 br i1 %setjmp.result6, label %4, label %lpad 4: ; preds = %rundefers.callback1 call void @"main.deferMultiple$2"(ptr undef) br label %rundefers.loophead rundefers.default: ; preds = %rundefers.loop unreachable rundefers.end: ; preds = %rundefers.loophead br label %rundefers.after recover: ; preds = %rundefers.end7 call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4 ret void lpad: ; preds = %rundefers.callback119, %rundefers.callback016, %rundefers.callback1, %rundefers.callback0, %entry br label %rundefers.loophead10 rundefers.loophead10: ; preds = %7, %6, %lpad %5 = load ptr, ptr %deferPtr, align 4 %stackIsNil11 = icmp eq ptr %5, null br i1 %stackIsNil11, label %rundefers.end7, label %rundefers.loop9 rundefers.loop9: ; preds = %rundefers.loophead10 %stack.next.gep12 = getelementptr inbounds nuw i8, ptr %5, i32 4 %stack.next13 = load ptr, ptr %stack.next.gep12, align 4 store ptr %stack.next13, ptr %deferPtr, align 4 %callback15 = load i32, ptr %5, align 4 switch i32 %callback15, label %rundefers.default8 [ i32 0, label %rundefers.callback016 i32 1, label %rundefers.callback119 ] rundefers.callback016: ; preds = %rundefers.loop9 %setjmp17 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 %setjmp.result18 = icmp eq i32 %setjmp17, 0 br i1 %setjmp.result18, label %6, label %lpad 6: ; preds = %rundefers.callback016 call void @"main.deferMultiple$1"(ptr undef) br label %rundefers.loophead10 rundefers.callback119: ; preds = %rundefers.loop9 %setjmp20 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 %setjmp.result21 = icmp eq i32 %setjmp20, 0 br i1 %setjmp.result21, label %7, label %lpad 7: ; preds = %rundefers.callback119 call void @"main.deferMultiple$2"(ptr undef) br label %rundefers.loophead10 rundefers.default8: ; preds = %rundefers.loop9 unreachable rundefers.end7: ; preds = %rundefers.loophead10 br label %recover } ; Function Attrs: nounwind define internal void @"main.deferMultiple$1"(ptr %context) unnamed_addr #1 { entry: call void @runtime.printlock(ptr undef) #4 call void @runtime.printint32(i32 3, ptr undef) #4 call void @runtime.printunlock(ptr undef) #4 ret void } ; Function Attrs: nounwind define internal void @"main.deferMultiple$2"(ptr %context) unnamed_addr #1 { entry: call void @runtime.printlock(ptr undef) #4 call void @runtime.printint32(i32 5, ptr undef) #4 call void @runtime.printunlock(ptr undef) #4 ret void } ; Function Attrs: nounwind define hidden void @main.deferInfiniteLoop(ptr %context) unnamed_addr #1 { entry: %deferPtr = alloca ptr, align 4 store ptr null, ptr %deferPtr, align 4 %deferframe.buf = alloca %runtime.deferFrame, align 4 %0 = call ptr @llvm.stacksave.p0() call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4 br label %for.body for.body: ; preds = %for.body, %entry %defer.next = load ptr, ptr %deferPtr, align 4 %defer.alloc.call = call dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #4 store i32 0, ptr %defer.alloc.call, align 4 %defer.alloc.call.repack1 = getelementptr inbounds nuw i8, ptr %defer.alloc.call, i32 4 store ptr %defer.next, ptr %defer.alloc.call.repack1, align 4 %defer.alloc.call.repack3 = getelementptr inbounds nuw i8, ptr %defer.alloc.call, i32 8 store i32 8, ptr %defer.alloc.call.repack3, align 4 store ptr %defer.alloc.call, ptr %deferPtr, align 4 br label %for.body recover: ; preds = %rundefers.end ret void lpad: ; No predecessors! br label %rundefers.loophead rundefers.loophead: ; preds = %rundefers.callback0, %lpad br i1 poison, label %rundefers.end, label %rundefers.loop rundefers.loop: ; preds = %rundefers.loophead switch i32 poison, label %rundefers.default [ i32 0, label %rundefers.callback0 ] rundefers.callback0: ; preds = %rundefers.loop br label %rundefers.loophead rundefers.default: ; preds = %rundefers.loop unreachable rundefers.end: ; preds = %rundefers.loophead br label %recover } ; Function Attrs: nounwind define hidden void @main.deferLoop(ptr %context) unnamed_addr #1 { entry: %deferPtr = alloca ptr, align 4 store ptr null, ptr %deferPtr, align 4 %deferframe.buf = alloca %runtime.deferFrame, align 4 %0 = call ptr @llvm.stacksave.p0() call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4 br label %for.loop for.loop: ; preds = %for.body, %entry %1 = phi i32 [ 0, %entry ], [ %3, %for.body ] %2 = icmp slt i32 %1, 10 br i1 %2, label %for.body, label %for.done for.body: ; preds = %for.loop %defer.next = load ptr, ptr %deferPtr, align 4 %defer.alloc.call = call dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #4 store i32 0, ptr %defer.alloc.call, align 4 %defer.alloc.call.repack13 = getelementptr inbounds nuw i8, ptr %defer.alloc.call, i32 4 store ptr %defer.next, ptr %defer.alloc.call.repack13, align 4 %defer.alloc.call.repack15 = getelementptr inbounds nuw i8, ptr %defer.alloc.call, i32 8 store i32 %1, ptr %defer.alloc.call.repack15, align 4 store ptr %defer.alloc.call, ptr %deferPtr, align 4 %3 = add i32 %1, 1 br label %for.loop for.done: ; preds = %for.loop br label %rundefers.block rundefers.after: ; preds = %rundefers.end call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4 ret void rundefers.block: ; preds = %for.done br label %rundefers.loophead rundefers.loophead: ; preds = %rundefers.callback0, %rundefers.block %4 = load ptr, ptr %deferPtr, align 4 %stackIsNil = icmp eq ptr %4, null br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop rundefers.loop: ; preds = %rundefers.loophead %stack.next.gep = getelementptr inbounds nuw i8, ptr %4, i32 4 %stack.next = load ptr, ptr %stack.next.gep, align 4 store ptr %stack.next, ptr %deferPtr, align 4 %callback = load i32, ptr %4, align 4 switch i32 %callback, label %rundefers.default [ i32 0, label %rundefers.callback0 ] rundefers.callback0: ; preds = %rundefers.loop %gep = getelementptr inbounds nuw i8, ptr %4, i32 8 %param = load i32, ptr %gep, align 4 call void @runtime.printlock(ptr undef) #4 call void @runtime.printint32(i32 %param, ptr undef) #4 call void @runtime.printunlock(ptr undef) #4 br label %rundefers.loophead rundefers.default: ; preds = %rundefers.loop unreachable rundefers.end: ; preds = %rundefers.loophead br label %rundefers.after recover: ; preds = %rundefers.end1 ret void lpad: ; No predecessors! br label %rundefers.loophead4 rundefers.loophead4: ; preds = %rundefers.callback010, %lpad br i1 poison, label %rundefers.end1, label %rundefers.loop3 rundefers.loop3: ; preds = %rundefers.loophead4 switch i32 poison, label %rundefers.default2 [ i32 0, label %rundefers.callback010 ] rundefers.callback010: ; preds = %rundefers.loop3 br label %rundefers.loophead4 rundefers.default2: ; preds = %rundefers.loop3 unreachable rundefers.end1: ; preds = %rundefers.loophead4 br label %recover } ; Function Attrs: nounwind define hidden void @main.deferBetweenLoops(ptr %context) unnamed_addr #1 { entry: %defer.alloca = alloca { i32, ptr, i32 }, align 4 %deferPtr = alloca ptr, align 4 store ptr null, ptr %deferPtr, align 4 %deferframe.buf = alloca %runtime.deferFrame, align 4 %0 = call ptr @llvm.stacksave.p0() call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4 br label %for.loop for.loop: ; preds = %for.body, %entry %1 = phi i32 [ 0, %entry ], [ %3, %for.body ] %2 = icmp slt i32 %1, 10 br i1 %2, label %for.body, label %for.done for.body: ; preds = %for.loop %3 = add i32 %1, 1 br label %for.loop for.done: ; preds = %for.loop %defer.next = load ptr, ptr %deferPtr, align 4 store i32 0, ptr %defer.alloca, align 4 %defer.alloca.repack16 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4 store ptr %defer.next, ptr %defer.alloca.repack16, align 4 %defer.alloca.repack18 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 8 store i32 1, ptr %defer.alloca.repack18, align 4 store ptr %defer.alloca, ptr %deferPtr, align 4 br label %for.loop1 for.loop1: ; preds = %for.body2, %for.done %4 = phi i32 [ 0, %for.done ], [ %6, %for.body2 ] %5 = icmp slt i32 %4, 10 br i1 %5, label %for.body2, label %for.done3 for.body2: ; preds = %for.loop1 %6 = add i32 %4, 1 br label %for.loop1 for.done3: ; preds = %for.loop1 br label %rundefers.block rundefers.after: ; preds = %rundefers.end call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4 ret void rundefers.block: ; preds = %for.done3 br label %rundefers.loophead rundefers.loophead: ; preds = %rundefers.callback0, %rundefers.block %7 = load ptr, ptr %deferPtr, align 4 %stackIsNil = icmp eq ptr %7, null br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop rundefers.loop: ; preds = %rundefers.loophead %stack.next.gep = getelementptr inbounds nuw i8, ptr %7, i32 4 %stack.next = load ptr, ptr %stack.next.gep, align 4 store ptr %stack.next, ptr %deferPtr, align 4 %callback = load i32, ptr %7, align 4 switch i32 %callback, label %rundefers.default [ i32 0, label %rundefers.callback0 ] rundefers.callback0: ; preds = %rundefers.loop %gep = getelementptr inbounds nuw i8, ptr %7, i32 8 %param = load i32, ptr %gep, align 4 call void @runtime.printlock(ptr undef) #4 call void @runtime.printint32(i32 %param, ptr undef) #4 call void @runtime.printunlock(ptr undef) #4 br label %rundefers.loophead rundefers.default: ; preds = %rundefers.loop unreachable rundefers.end: ; preds = %rundefers.loophead br label %rundefers.after recover: ; preds = %rundefers.end4 ret void lpad: ; No predecessors! br label %rundefers.loophead7 rundefers.loophead7: ; preds = %rundefers.callback013, %lpad br i1 poison, label %rundefers.end4, label %rundefers.loop6 rundefers.loop6: ; preds = %rundefers.loophead7 switch i32 poison, label %rundefers.default5 [ i32 0, label %rundefers.callback013 ] rundefers.callback013: ; preds = %rundefers.loop6 br label %rundefers.loophead7 rundefers.default5: ; preds = %rundefers.loop6 unreachable rundefers.end4: ; preds = %rundefers.loophead7 br label %recover } attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } attributes #1 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } attributes #2 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } attributes #3 = { nocallback nofree nosync nounwind willreturn } attributes #4 = { nounwind } attributes #5 = { nounwind returns_twice } ================================================ FILE: compiler/testdata/defer.go ================================================ package main func external() func deferSimple() { defer func() { print(3) }() external() } func deferMultiple() { defer func() { print(3) }() defer func() { print(5) }() external() } func deferInfiniteLoop() { for { defer print(8) } } func deferLoop() { for i := 0; i < 10; i++ { defer print(i) } } func deferBetweenLoops() { for i := 0; i < 10; i++ { } defer print(1) for i := 0; i < 10; i++ { } } ================================================ FILE: compiler/testdata/errors.go ================================================ package main import ( "structs" "unsafe" ) //go:wasmimport modulename empty func empty() // ERROR: can only use //go:wasmimport on declarations // //go:wasmimport modulename implementation func implementation() { } type Uint uint32 type S struct { _ structs.HostLayout a [4]uint32 b uintptr d float32 e float64 } //go:wasmimport modulename validparam func validparam(a int32, b uint64, c float64, d unsafe.Pointer, e Uint, f uintptr, g string, h *int32, i *S, j *struct{}, k *[8]uint8) // ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type [4]uint32 // ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type []byte // ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type struct{a int} // ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type chan struct{} // ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type func() // ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type int // ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type uint // ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type [8]int // //go:wasmimport modulename invalidparam func invalidparam(a [4]uint32, b []byte, c struct{ a int }, d chan struct{}, e func(), f int, g uint, h [8]int) // ERROR: //go:wasmimport modulename invalidparam_no_hostlayout: unsupported parameter type *struct{int} // ERROR: //go:wasmimport modulename invalidparam_no_hostlayout: unsupported parameter type *struct{string} // //go:wasmimport modulename invalidparam_no_hostlayout func invalidparam_no_hostlayout(a *struct{ int }, b *struct{ string }) //go:wasmimport modulename validreturn_int32 func validreturn_int32() int32 //go:wasmimport modulename validreturn_ptr_int32 func validreturn_ptr_int32() *int32 //go:wasmimport modulename validreturn_ptr_string func validreturn_ptr_string() *string //go:wasmimport modulename validreturn_ptr_struct func validreturn_ptr_struct() *S //go:wasmimport modulename validreturn_ptr_struct func validreturn_ptr_empty_struct() *struct{} //go:wasmimport modulename validreturn_ptr_array func validreturn_ptr_array() *[8]uint8 //go:wasmimport modulename validreturn_unsafe_pointer func validreturn_unsafe_pointer() unsafe.Pointer // ERROR: //go:wasmimport modulename manyreturns: too many return values // //go:wasmimport modulename manyreturns func manyreturns() (int32, int32) // ERROR: //go:wasmimport modulename invalidreturn_int: unsupported result type int // //go:wasmimport modulename invalidreturn_int func invalidreturn_int() int // ERROR: //go:wasmimport modulename invalidreturn_int: unsupported result type uint // //go:wasmimport modulename invalidreturn_int func invalidreturn_uint() uint // ERROR: //go:wasmimport modulename invalidreturn_func: unsupported result type func() // //go:wasmimport modulename invalidreturn_func func invalidreturn_func() func() // ERROR: //go:wasmimport modulename invalidreturn_pointer_array_int: unsupported result type *[8]int // //go:wasmimport modulename invalidreturn_pointer_array_int func invalidreturn_pointer_array_int() *[8]int // ERROR: //go:wasmimport modulename invalidreturn_slice_byte: unsupported result type []byte // //go:wasmimport modulename invalidreturn_slice_byte func invalidreturn_slice_byte() []byte // ERROR: //go:wasmimport modulename invalidreturn_chan_int: unsupported result type chan int // //go:wasmimport modulename invalidreturn_chan_int func invalidreturn_chan_int() chan int // ERROR: //go:wasmimport modulename invalidreturn_string: unsupported result type string // //go:wasmimport modulename invalidreturn_string func invalidreturn_string() string ================================================ FILE: compiler/testdata/float.go ================================================ package main // Test converting floats to ints. func f32tou32(v float32) uint32 { return uint32(v) } func maxu32f() float32 { return float32(^uint32(0)) } func maxu32tof32() uint32 { f := float32(^uint32(0)) return uint32(f) } func inftoi32() (uint32, uint32, int32, int32) { inf := 1.0 inf /= 0.0 return uint32(inf), uint32(-inf), int32(inf), int32(-inf) } func u32tof32tou32(v uint32) uint32 { return uint32(float32(v)) } func f32tou32tof32(v float32) float32 { return float32(uint32(v)) } func f32tou8(v float32) uint8 { return uint8(v) } func f32toi8(v float32) int8 { return int8(v) } ================================================ FILE: compiler/testdata/float.ll ================================================ ; ModuleID = 'float.go' source_filename = "float.go" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20" target triple = "wasm32-unknown-wasi" ; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1 ; Function Attrs: nounwind define hidden void @main.init(ptr %context) unnamed_addr #2 { entry: ret void } ; Function Attrs: nounwind define hidden i32 @main.f32tou32(float %v, ptr %context) unnamed_addr #2 { entry: %positive = fcmp oge float %v, 0.000000e+00 %withinmax = fcmp ole float %v, 0x41EFFFFFC0000000 %inbounds = and i1 %positive, %withinmax %saturated = sext i1 %positive to i32 %normal = fptoui float %v to i32 %0 = select i1 %inbounds, i32 %normal, i32 %saturated ret i32 %0 } ; Function Attrs: nounwind define hidden float @main.maxu32f(ptr %context) unnamed_addr #2 { entry: ret float 0x41F0000000000000 } ; Function Attrs: nounwind define hidden i32 @main.maxu32tof32(ptr %context) unnamed_addr #2 { entry: ret i32 -1 } ; Function Attrs: nounwind define hidden { i32, i32, i32, i32 } @main.inftoi32(ptr %context) unnamed_addr #2 { entry: ret { i32, i32, i32, i32 } { i32 -1, i32 0, i32 2147483647, i32 -2147483648 } } ; Function Attrs: nounwind define hidden i32 @main.u32tof32tou32(i32 %v, ptr %context) unnamed_addr #2 { entry: %0 = uitofp i32 %v to float %withinmax = fcmp ole float %0, 0x41EFFFFFC0000000 %normal = fptoui float %0 to i32 %1 = select i1 %withinmax, i32 %normal, i32 -1 ret i32 %1 } ; Function Attrs: nounwind define hidden float @main.f32tou32tof32(float %v, ptr %context) unnamed_addr #2 { entry: %positive = fcmp oge float %v, 0.000000e+00 %withinmax = fcmp ole float %v, 0x41EFFFFFC0000000 %inbounds = and i1 %positive, %withinmax %saturated = sext i1 %positive to i32 %normal = fptoui float %v to i32 %0 = select i1 %inbounds, i32 %normal, i32 %saturated %1 = uitofp i32 %0 to float ret float %1 } ; Function Attrs: nounwind define hidden i8 @main.f32tou8(float %v, ptr %context) unnamed_addr #2 { entry: %positive = fcmp oge float %v, 0.000000e+00 %withinmax = fcmp ole float %v, 2.550000e+02 %inbounds = and i1 %positive, %withinmax %saturated = sext i1 %positive to i8 %normal = fptoui float %v to i8 %0 = select i1 %inbounds, i8 %normal, i8 %saturated ret i8 %0 } ; Function Attrs: nounwind define hidden i8 @main.f32toi8(float %v, ptr %context) unnamed_addr #2 { entry: %abovemin = fcmp oge float %v, -1.280000e+02 %belowmax = fcmp ole float %v, 1.270000e+02 %inbounds = and i1 %abovemin, %belowmax %saturated = select i1 %abovemin, i8 127, i8 -128 %isnan = fcmp uno float %v, 0.000000e+00 %remapped = select i1 %isnan, i8 0, i8 %saturated %normal = fptosi float %v to i8 %0 = select i1 %inbounds, i8 %normal, i8 %remapped ret i8 %0 } attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #1 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } ================================================ FILE: compiler/testdata/func.go ================================================ package main func foo(callback func(int)) { callback(3) } func bar() { foo(someFunc) } func someFunc(int) { } ================================================ FILE: compiler/testdata/func.ll ================================================ ; ModuleID = 'func.go' source_filename = "func.go" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20" target triple = "wasm32-unknown-wasi" ; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1 ; Function Attrs: nounwind define hidden void @main.init(ptr %context) unnamed_addr #2 { entry: ret void } ; Function Attrs: nounwind define hidden void @main.foo(ptr %callback.context, ptr %callback.funcptr, ptr %context) unnamed_addr #2 { entry: %0 = icmp eq ptr %callback.funcptr, null br i1 %0, label %fpcall.throw, label %fpcall.next fpcall.next: ; preds = %entry call void %callback.funcptr(i32 3, ptr %callback.context) #3 ret void fpcall.throw: ; preds = %entry call void @runtime.nilPanic(ptr undef) #3 unreachable } declare void @runtime.nilPanic(ptr) #1 ; Function Attrs: nounwind define hidden void @main.bar(ptr %context) unnamed_addr #2 { entry: call void @main.foo(ptr undef, ptr nonnull @main.someFunc, ptr undef) ret void } ; Function Attrs: nounwind define hidden void @main.someFunc(i32 %arg0, ptr %context) unnamed_addr #2 { entry: ret void } attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #1 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind } ================================================ FILE: compiler/testdata/gc.go ================================================ package main var ( scalar1 *byte scalar2 *int32 scalar3 *int64 scalar4 *float32 array1 *[3]byte array2 *[71]byte array3 *[3]*byte struct1 *struct{} struct2 *struct { x int y int } struct3 *struct { x *byte y [60]uintptr z *byte } struct4 *struct { x *byte y [61]uintptr } slice1 []byte slice2 []*int slice3 [][]byte ) func newScalar() { scalar1 = new(byte) scalar2 = new(int32) scalar3 = new(int64) scalar4 = new(float32) } func newArray() { array1 = new([3]byte) array2 = new([71]byte) array3 = new([3]*byte) } func newStruct() { struct1 = new(struct{}) struct2 = new(struct { x int y int }) struct3 = new(struct { x *byte y [60]uintptr z *byte }) struct4 = new(struct { x *byte y [61]uintptr }) } func newFuncValue() *func() { return new(func()) } func makeSlice() { slice1 = make([]byte, 5) slice2 = make([]*int, 5) slice3 = make([][]byte, 5) } func makeInterface(v complex128) interface{} { return v // always stored in an allocation } ================================================ FILE: compiler/testdata/gc.ll ================================================ ; ModuleID = 'gc.go' source_filename = "gc.go" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20" target triple = "wasm32-unknown-wasi" %runtime._interface = type { ptr, ptr } @main.scalar1 = hidden global ptr null, align 4 @main.scalar2 = hidden global ptr null, align 4 @main.scalar3 = hidden global ptr null, align 4 @main.scalar4 = hidden global ptr null, align 4 @main.array1 = hidden global ptr null, align 4 @main.array2 = hidden global ptr null, align 4 @main.array3 = hidden global ptr null, align 4 @main.struct1 = hidden global ptr null, align 4 @main.struct2 = hidden global ptr null, align 4 @main.struct3 = hidden global ptr null, align 4 @main.struct4 = hidden global ptr null, align 4 @main.slice1 = hidden global { ptr, i32, i32 } zeroinitializer, align 4 @main.slice2 = hidden global { ptr, i32, i32 } zeroinitializer, align 4 @main.slice3 = hidden global { ptr, i32, i32 } zeroinitializer, align 4 @"runtime/gc.layout:62-2000000000000001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00 " } @"runtime/gc.layout:62-0001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00\00" } @"reflect/types.type:basic:complex128" = linkonce_odr constant { i8, ptr } { i8 80, ptr @"reflect/types.type:pointer:basic:complex128" }, align 4 @"reflect/types.type:pointer:basic:complex128" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:complex128" }, align 4 ; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1 ; Function Attrs: nounwind define hidden void @main.init(ptr %context) unnamed_addr #2 { entry: ret void } ; Function Attrs: nounwind define hidden void @main.newScalar(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %new = call align 1 dereferenceable(1) ptr @runtime.alloc(i32 1, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new, ptr @main.scalar1, align 4 %new1 = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new1, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new1, ptr @main.scalar2, align 4 %new2 = call align 8 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new2, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new2, ptr @main.scalar3, align 4 %new3 = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new3, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new3, ptr @main.scalar4, align 4 ret void } ; Function Attrs: nounwind define hidden void @main.newArray(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %new = call align 1 dereferenceable(3) ptr @runtime.alloc(i32 3, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new, ptr @main.array1, align 4 %new1 = call align 1 dereferenceable(71) ptr @runtime.alloc(i32 71, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new1, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new1, ptr @main.array2, align 4 %new2 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 67 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new2, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new2, ptr @main.array3, align 4 ret void } ; Function Attrs: nounwind define hidden void @main.newStruct(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %new = call align 1 ptr @runtime.alloc(i32 0, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new, ptr @main.struct1, align 4 %new1 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new1, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new1, ptr @main.struct2, align 4 %new2 = call align 4 dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-2000000000000001", ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new2, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new2, ptr @main.struct3, align 4 %new3 = call align 4 dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-0001", ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new3, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new3, ptr @main.struct4, align 4 ret void } ; Function Attrs: nounwind define hidden ptr @main.newFuncValue(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %new = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 197 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3 ret ptr %new } ; Function Attrs: nounwind define hidden void @main.makeSlice(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %makeslice = call align 1 dereferenceable(5) ptr @runtime.alloc(i32 5, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %makeslice, ptr nonnull %stackalloc, ptr undef) #3 store ptr %makeslice, ptr @main.slice1, align 4 store i32 5, ptr getelementptr inbounds nuw (i8, ptr @main.slice1, i32 4), align 4 store i32 5, ptr getelementptr inbounds nuw (i8, ptr @main.slice1, i32 8), align 4 %makeslice1 = call align 4 dereferenceable(20) ptr @runtime.alloc(i32 20, ptr nonnull inttoptr (i32 67 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %makeslice1, ptr nonnull %stackalloc, ptr undef) #3 store ptr %makeslice1, ptr @main.slice2, align 4 store i32 5, ptr getelementptr inbounds nuw (i8, ptr @main.slice2, i32 4), align 4 store i32 5, ptr getelementptr inbounds nuw (i8, ptr @main.slice2, i32 8), align 4 %makeslice3 = call align 4 dereferenceable(60) ptr @runtime.alloc(i32 60, ptr nonnull inttoptr (i32 71 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %makeslice3, ptr nonnull %stackalloc, ptr undef) #3 store ptr %makeslice3, ptr @main.slice3, align 4 store i32 5, ptr getelementptr inbounds nuw (i8, ptr @main.slice3, i32 4), align 4 store i32 5, ptr getelementptr inbounds nuw (i8, ptr @main.slice3, i32 8), align 4 ret void } ; Function Attrs: nounwind define hidden %runtime._interface @main.makeInterface(double %v.r, double %v.i, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %0 = call align 8 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #3 store double %v.r, ptr %0, align 8 %.repack1 = getelementptr inbounds nuw i8, ptr %0, i32 8 store double %v.i, ptr %.repack1, align 8 %1 = insertvalue %runtime._interface { ptr @"reflect/types.type:basic:complex128", ptr undef }, ptr %0, 1 call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:basic:complex128", ptr nonnull %stackalloc, ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #3 ret %runtime._interface %1 } attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #1 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind } ================================================ FILE: compiler/testdata/generics.go ================================================ package main import "unsafe" type Coord interface { int | float32 } type Point[T Coord] struct { X, Y T } func Add[T Coord](a, b Point[T]) Point[T] { checkSize(unsafe.Alignof(a)) checkSize(unsafe.Sizeof(a)) return Point[T]{ X: a.X + b.X, Y: a.Y + b.Y, } } func main() { var af, bf Point[float32] Add(af, bf) var ai, bi Point[int] Add(ai, bi) } func checkSize(uintptr) ================================================ FILE: compiler/testdata/generics.ll ================================================ ; ModuleID = 'generics.go' source_filename = "generics.go" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20" target triple = "wasm32-unknown-wasi" %"main.Point[int]" = type { i32, i32 } %"main.Point[float32]" = type { float, float } declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) #0 declare void @runtime.trackPointer(i8* nocapture readonly, i8*) #0 ; Function Attrs: nounwind define hidden void @main.init(i8* %context) unnamed_addr #1 { entry: ret void } ; Function Attrs: nounwind define hidden void @main.main(i8* %context) unnamed_addr #1 { entry: %bi = alloca %"main.Point[int]", align 8 %ai = alloca %"main.Point[int]", align 8 %bf = alloca %"main.Point[float32]", align 8 %af = alloca %"main.Point[float32]", align 8 %af.repack = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %af, i32 0, i32 0 store float 0.000000e+00, float* %af.repack, align 8 %af.repack1 = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %af, i32 0, i32 1 store float 0.000000e+00, float* %af.repack1, align 4 %0 = bitcast %"main.Point[float32]"* %af to i8* call void @runtime.trackPointer(i8* nonnull %0, i8* undef) #2 %bf.repack = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %bf, i32 0, i32 0 store float 0.000000e+00, float* %bf.repack, align 8 %bf.repack2 = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %bf, i32 0, i32 1 store float 0.000000e+00, float* %bf.repack2, align 4 %1 = bitcast %"main.Point[float32]"* %bf to i8* call void @runtime.trackPointer(i8* nonnull %1, i8* undef) #2 %.elt = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %af, i32 0, i32 0 %.unpack = load float, float* %.elt, align 8 %.elt3 = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %af, i32 0, i32 1 %.unpack4 = load float, float* %.elt3, align 4 %.elt5 = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %bf, i32 0, i32 0 %.unpack6 = load float, float* %.elt5, align 8 %.elt7 = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %bf, i32 0, i32 1 %.unpack8 = load float, float* %.elt7, align 4 %2 = call %"main.Point[float32]" @"main.Add[float32]"(float %.unpack, float %.unpack4, float %.unpack6, float %.unpack8, i8* undef) %ai.repack = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %ai, i32 0, i32 0 store i32 0, i32* %ai.repack, align 8 %ai.repack9 = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %ai, i32 0, i32 1 store i32 0, i32* %ai.repack9, align 4 %3 = bitcast %"main.Point[int]"* %ai to i8* call void @runtime.trackPointer(i8* nonnull %3, i8* undef) #2 %bi.repack = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %bi, i32 0, i32 0 store i32 0, i32* %bi.repack, align 8 %bi.repack10 = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %bi, i32 0, i32 1 store i32 0, i32* %bi.repack10, align 4 %4 = bitcast %"main.Point[int]"* %bi to i8* call void @runtime.trackPointer(i8* nonnull %4, i8* undef) #2 %.elt11 = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %ai, i32 0, i32 0 %.unpack12 = load i32, i32* %.elt11, align 8 %.elt13 = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %ai, i32 0, i32 1 %.unpack14 = load i32, i32* %.elt13, align 4 %.elt15 = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %bi, i32 0, i32 0 %.unpack16 = load i32, i32* %.elt15, align 8 %.elt17 = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %bi, i32 0, i32 1 %.unpack18 = load i32, i32* %.elt17, align 4 %5 = call %"main.Point[int]" @"main.Add[int]"(i32 %.unpack12, i32 %.unpack14, i32 %.unpack16, i32 %.unpack18, i8* undef) ret void } ; Function Attrs: nounwind define linkonce_odr hidden %"main.Point[float32]" @"main.Add[float32]"(float %a.X, float %a.Y, float %b.X, float %b.Y, i8* %context) unnamed_addr #1 { entry: %complit = alloca %"main.Point[float32]", align 8 %b = alloca %"main.Point[float32]", align 8 %a = alloca %"main.Point[float32]", align 8 %a.repack = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %a, i32 0, i32 0 store float 0.000000e+00, float* %a.repack, align 8 %a.repack9 = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %a, i32 0, i32 1 store float 0.000000e+00, float* %a.repack9, align 4 %0 = bitcast %"main.Point[float32]"* %a to i8* call void @runtime.trackPointer(i8* nonnull %0, i8* undef) #2 %a.repack10 = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %a, i32 0, i32 0 store float %a.X, float* %a.repack10, align 8 %a.repack11 = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %a, i32 0, i32 1 store float %a.Y, float* %a.repack11, align 4 %b.repack = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %b, i32 0, i32 0 store float 0.000000e+00, float* %b.repack, align 8 %b.repack13 = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %b, i32 0, i32 1 store float 0.000000e+00, float* %b.repack13, align 4 %1 = bitcast %"main.Point[float32]"* %b to i8* call void @runtime.trackPointer(i8* nonnull %1, i8* undef) #2 %b.repack14 = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %b, i32 0, i32 0 store float %b.X, float* %b.repack14, align 8 %b.repack15 = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %b, i32 0, i32 1 store float %b.Y, float* %b.repack15, align 4 call void @main.checkSize(i32 4, i8* undef) #2 call void @main.checkSize(i32 8, i8* undef) #2 %complit.repack = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %complit, i32 0, i32 0 store float 0.000000e+00, float* %complit.repack, align 8 %complit.repack17 = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %complit, i32 0, i32 1 store float 0.000000e+00, float* %complit.repack17, align 4 %2 = bitcast %"main.Point[float32]"* %complit to i8* call void @runtime.trackPointer(i8* nonnull %2, i8* undef) #2 %3 = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %complit, i32 0, i32 0 br i1 false, label %deref.throw, label %deref.next deref.next: ; preds = %entry br i1 false, label %deref.throw1, label %deref.next2 deref.next2: ; preds = %deref.next %4 = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %b, i32 0, i32 0 %5 = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %a, i32 0, i32 0 %6 = load float, float* %5, align 8 %7 = load float, float* %4, align 8 %8 = fadd float %6, %7 br i1 false, label %deref.throw3, label %deref.next4 deref.next4: ; preds = %deref.next2 br i1 false, label %deref.throw5, label %deref.next6 deref.next6: ; preds = %deref.next4 %9 = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %b, i32 0, i32 1 %10 = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %a, i32 0, i32 1 %11 = load float, float* %10, align 4 %12 = load float, float* %9, align 4 br i1 false, label %store.throw, label %store.next store.next: ; preds = %deref.next6 store float %8, float* %3, align 8 br i1 false, label %store.throw7, label %store.next8 store.next8: ; preds = %store.next %13 = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %complit, i32 0, i32 1 %14 = fadd float %11, %12 store float %14, float* %13, align 4 %.elt = getelementptr inbounds %"main.Point[float32]", %"main.Point[float32]"* %complit, i32 0, i32 0 %.unpack = load float, float* %.elt, align 8 %15 = insertvalue %"main.Point[float32]" undef, float %.unpack, 0 %16 = insertvalue %"main.Point[float32]" %15, float %14, 1 ret %"main.Point[float32]" %16 deref.throw: ; preds = %entry unreachable deref.throw1: ; preds = %deref.next unreachable deref.throw3: ; preds = %deref.next2 unreachable deref.throw5: ; preds = %deref.next4 unreachable store.throw: ; preds = %deref.next6 unreachable store.throw7: ; preds = %store.next unreachable } declare void @main.checkSize(i32, i8*) #0 declare void @runtime.nilPanic(i8*) #0 ; Function Attrs: nounwind define linkonce_odr hidden %"main.Point[int]" @"main.Add[int]"(i32 %a.X, i32 %a.Y, i32 %b.X, i32 %b.Y, i8* %context) unnamed_addr #1 { entry: %complit = alloca %"main.Point[int]", align 8 %b = alloca %"main.Point[int]", align 8 %a = alloca %"main.Point[int]", align 8 %a.repack = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %a, i32 0, i32 0 store i32 0, i32* %a.repack, align 8 %a.repack9 = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %a, i32 0, i32 1 store i32 0, i32* %a.repack9, align 4 %0 = bitcast %"main.Point[int]"* %a to i8* call void @runtime.trackPointer(i8* nonnull %0, i8* undef) #2 %a.repack10 = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %a, i32 0, i32 0 store i32 %a.X, i32* %a.repack10, align 8 %a.repack11 = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %a, i32 0, i32 1 store i32 %a.Y, i32* %a.repack11, align 4 %b.repack = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %b, i32 0, i32 0 store i32 0, i32* %b.repack, align 8 %b.repack13 = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %b, i32 0, i32 1 store i32 0, i32* %b.repack13, align 4 %1 = bitcast %"main.Point[int]"* %b to i8* call void @runtime.trackPointer(i8* nonnull %1, i8* undef) #2 %b.repack14 = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %b, i32 0, i32 0 store i32 %b.X, i32* %b.repack14, align 8 %b.repack15 = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %b, i32 0, i32 1 store i32 %b.Y, i32* %b.repack15, align 4 call void @main.checkSize(i32 4, i8* undef) #2 call void @main.checkSize(i32 8, i8* undef) #2 %complit.repack = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %complit, i32 0, i32 0 store i32 0, i32* %complit.repack, align 8 %complit.repack17 = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %complit, i32 0, i32 1 store i32 0, i32* %complit.repack17, align 4 %2 = bitcast %"main.Point[int]"* %complit to i8* call void @runtime.trackPointer(i8* nonnull %2, i8* undef) #2 %3 = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %complit, i32 0, i32 0 br i1 false, label %deref.throw, label %deref.next deref.next: ; preds = %entry br i1 false, label %deref.throw1, label %deref.next2 deref.next2: ; preds = %deref.next %4 = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %b, i32 0, i32 0 %5 = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %a, i32 0, i32 0 %6 = load i32, i32* %5, align 8 %7 = load i32, i32* %4, align 8 %8 = add i32 %6, %7 br i1 false, label %deref.throw3, label %deref.next4 deref.next4: ; preds = %deref.next2 br i1 false, label %deref.throw5, label %deref.next6 deref.next6: ; preds = %deref.next4 %9 = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %b, i32 0, i32 1 %10 = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %a, i32 0, i32 1 %11 = load i32, i32* %10, align 4 %12 = load i32, i32* %9, align 4 br i1 false, label %store.throw, label %store.next store.next: ; preds = %deref.next6 store i32 %8, i32* %3, align 8 br i1 false, label %store.throw7, label %store.next8 store.next8: ; preds = %store.next %13 = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %complit, i32 0, i32 1 %14 = add i32 %11, %12 store i32 %14, i32* %13, align 4 %.elt = getelementptr inbounds %"main.Point[int]", %"main.Point[int]"* %complit, i32 0, i32 0 %.unpack = load i32, i32* %.elt, align 8 %15 = insertvalue %"main.Point[int]" undef, i32 %.unpack, 0 %16 = insertvalue %"main.Point[int]" %15, i32 %14, 1 ret %"main.Point[int]" %16 deref.throw: ; preds = %entry unreachable deref.throw1: ; preds = %deref.next unreachable deref.throw3: ; preds = %deref.next2 unreachable deref.throw5: ; preds = %deref.next4 unreachable store.throw: ; preds = %deref.next6 unreachable store.throw7: ; preds = %store.next unreachable } attributes #0 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } attributes #1 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } attributes #2 = { nounwind } ================================================ FILE: compiler/testdata/go1.20.go ================================================ package main import "unsafe" func unsafeSliceData(s []int) *int { return unsafe.SliceData(s) } func unsafeString(ptr *byte, len int16) string { return unsafe.String(ptr, len) } func unsafeStringData(s string) *byte { return unsafe.StringData(s) } ================================================ FILE: compiler/testdata/go1.20.ll ================================================ ; ModuleID = 'go1.20.go' source_filename = "go1.20.go" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20" target triple = "wasm32-unknown-wasi" %runtime._string = type { ptr, i32 } ; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1 ; Function Attrs: nounwind define hidden void @main.init(ptr %context) unnamed_addr #2 { entry: ret void } ; Function Attrs: nounwind define hidden ptr @main.unsafeSliceData(ptr %s.data, i32 %s.len, i32 %s.cap, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 call void @runtime.trackPointer(ptr %s.data, ptr nonnull %stackalloc, ptr undef) #3 ret ptr %s.data } ; Function Attrs: nounwind define hidden %runtime._string @main.unsafeString(ptr dereferenceable_or_null(1) %ptr, i16 %len, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %0 = icmp slt i16 %len, 0 %1 = icmp eq ptr %ptr, null %2 = icmp ne i16 %len, 0 %3 = and i1 %1, %2 %4 = or i1 %3, %0 br i1 %4, label %unsafe.String.throw, label %unsafe.String.next unsafe.String.next: ; preds = %entry %5 = zext nneg i16 %len to i32 %6 = insertvalue %runtime._string undef, ptr %ptr, 0 %7 = insertvalue %runtime._string %6, i32 %5, 1 call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #3 ret %runtime._string %7 unsafe.String.throw: ; preds = %entry call void @runtime.unsafeSlicePanic(ptr undef) #3 unreachable } declare void @runtime.unsafeSlicePanic(ptr) #1 ; Function Attrs: nounwind define hidden ptr @main.unsafeStringData(ptr readonly %s.data, i32 %s.len, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 call void @runtime.trackPointer(ptr %s.data, ptr nonnull %stackalloc, ptr undef) #3 ret ptr %s.data } attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #1 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind } ================================================ FILE: compiler/testdata/go1.21.go ================================================ package main func min1(a int) int { return min(a) } func min2(a, b int) int { return min(a, b) } func min3(a, b, c int) int { return min(a, b, c) } func min4(a, b, c, d int) int { return min(a, b, c, d) } func minUint8(a, b uint8) uint8 { return min(a, b) } func minUnsigned(a, b uint) uint { return min(a, b) } func minFloat32(a, b float32) float32 { return min(a, b) } func minFloat64(a, b float64) float64 { return min(a, b) } func minString(a, b string) string { return min(a, b) } func maxInt(a, b int) int { return max(a, b) } func maxUint(a, b uint) uint { return max(a, b) } func maxFloat32(a, b float32) float32 { return max(a, b) } func maxString(a, b string) string { return max(a, b) } func clearSlice(s []int) { clear(s) } func clearZeroSizedSlice(s []struct{}) { clear(s) } func clearMap(m map[string]int) { clear(m) } ================================================ FILE: compiler/testdata/go1.21.ll ================================================ ; ModuleID = 'go1.21.go' source_filename = "go1.21.go" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20" target triple = "wasm32-unknown-wasi" %runtime._string = type { ptr, i32 } ; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1 ; Function Attrs: nounwind define hidden void @main.init(ptr %context) unnamed_addr #2 { entry: ret void } ; Function Attrs: nounwind define hidden i32 @main.min1(i32 %a, ptr %context) unnamed_addr #2 { entry: ret i32 %a } ; Function Attrs: nounwind define hidden i32 @main.min2(i32 %a, i32 %b, ptr %context) unnamed_addr #2 { entry: %0 = call i32 @llvm.smin.i32(i32 %a, i32 %b) ret i32 %0 } ; Function Attrs: nounwind define hidden i32 @main.min3(i32 %a, i32 %b, i32 %c, ptr %context) unnamed_addr #2 { entry: %0 = call i32 @llvm.smin.i32(i32 %a, i32 %b) %1 = call i32 @llvm.smin.i32(i32 %0, i32 %c) ret i32 %1 } ; Function Attrs: nounwind define hidden i32 @main.min4(i32 %a, i32 %b, i32 %c, i32 %d, ptr %context) unnamed_addr #2 { entry: %0 = call i32 @llvm.smin.i32(i32 %a, i32 %b) %1 = call i32 @llvm.smin.i32(i32 %0, i32 %c) %2 = call i32 @llvm.smin.i32(i32 %1, i32 %d) ret i32 %2 } ; Function Attrs: nounwind define hidden i8 @main.minUint8(i8 %a, i8 %b, ptr %context) unnamed_addr #2 { entry: %0 = call i8 @llvm.umin.i8(i8 %a, i8 %b) ret i8 %0 } ; Function Attrs: nounwind define hidden i32 @main.minUnsigned(i32 %a, i32 %b, ptr %context) unnamed_addr #2 { entry: %0 = call i32 @llvm.umin.i32(i32 %a, i32 %b) ret i32 %0 } ; Function Attrs: nounwind define hidden float @main.minFloat32(float %a, float %b, ptr %context) unnamed_addr #2 { entry: %0 = fcmp olt float %a, %b %1 = select i1 %0, float %a, float %b ret float %1 } ; Function Attrs: nounwind define hidden double @main.minFloat64(double %a, double %b, ptr %context) unnamed_addr #2 { entry: %0 = fcmp olt double %a, %b %1 = select i1 %0, double %a, double %b ret double %1 } ; Function Attrs: nounwind define hidden %runtime._string @main.minString(ptr readonly %a.data, i32 %a.len, ptr readonly %b.data, i32 %b.len, ptr %context) unnamed_addr #2 { entry: %0 = insertvalue %runtime._string zeroinitializer, ptr %a.data, 0 %1 = insertvalue %runtime._string %0, i32 %a.len, 1 %2 = insertvalue %runtime._string zeroinitializer, ptr %b.data, 0 %3 = insertvalue %runtime._string %2, i32 %b.len, 1 %stackalloc = alloca i8, align 1 %4 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr undef) #5 %5 = select i1 %4, %runtime._string %1, %runtime._string %3 %6 = select i1 %4, ptr %a.data, ptr %b.data call void @runtime.trackPointer(ptr %6, ptr nonnull %stackalloc, ptr undef) #5 ret %runtime._string %5 } declare i1 @runtime.stringLess(ptr readonly, i32, ptr readonly, i32, ptr) #1 ; Function Attrs: nounwind define hidden i32 @main.maxInt(i32 %a, i32 %b, ptr %context) unnamed_addr #2 { entry: %0 = call i32 @llvm.smax.i32(i32 %a, i32 %b) ret i32 %0 } ; Function Attrs: nounwind define hidden i32 @main.maxUint(i32 %a, i32 %b, ptr %context) unnamed_addr #2 { entry: %0 = call i32 @llvm.umax.i32(i32 %a, i32 %b) ret i32 %0 } ; Function Attrs: nounwind define hidden float @main.maxFloat32(float %a, float %b, ptr %context) unnamed_addr #2 { entry: %0 = fcmp ogt float %a, %b %1 = select i1 %0, float %a, float %b ret float %1 } ; Function Attrs: nounwind define hidden %runtime._string @main.maxString(ptr readonly %a.data, i32 %a.len, ptr readonly %b.data, i32 %b.len, ptr %context) unnamed_addr #2 { entry: %0 = insertvalue %runtime._string zeroinitializer, ptr %a.data, 0 %1 = insertvalue %runtime._string %0, i32 %a.len, 1 %2 = insertvalue %runtime._string zeroinitializer, ptr %b.data, 0 %3 = insertvalue %runtime._string %2, i32 %b.len, 1 %stackalloc = alloca i8, align 1 %4 = call i1 @runtime.stringLess(ptr %b.data, i32 %b.len, ptr %a.data, i32 %a.len, ptr undef) #5 %5 = select i1 %4, %runtime._string %1, %runtime._string %3 %6 = select i1 %4, ptr %a.data, ptr %b.data call void @runtime.trackPointer(ptr %6, ptr nonnull %stackalloc, ptr undef) #5 ret %runtime._string %5 } ; Function Attrs: nounwind define hidden void @main.clearSlice(ptr %s.data, i32 %s.len, i32 %s.cap, ptr %context) unnamed_addr #2 { entry: %0 = shl i32 %s.len, 2 call void @llvm.memset.p0.i32(ptr align 4 %s.data, i8 0, i32 %0, i1 false) ret void } ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: write) declare void @llvm.memset.p0.i32(ptr nocapture writeonly, i8, i32, i1 immarg) #3 ; Function Attrs: nounwind define hidden void @main.clearZeroSizedSlice(ptr %s.data, i32 %s.len, i32 %s.cap, ptr %context) unnamed_addr #2 { entry: ret void } ; Function Attrs: nounwind define hidden void @main.clearMap(ptr dereferenceable_or_null(40) %m, ptr %context) unnamed_addr #2 { entry: call void @runtime.hashmapClear(ptr %m, ptr undef) #5 ret void } declare void @runtime.hashmapClear(ptr dereferenceable_or_null(40), ptr) #1 ; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) declare i32 @llvm.smin.i32(i32, i32) #4 ; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) declare i8 @llvm.umin.i8(i8, i8) #4 ; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) declare i32 @llvm.umin.i32(i32, i32) #4 ; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) declare i32 @llvm.smax.i32(i32, i32) #4 ; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) declare i32 @llvm.umax.i32(i32, i32) #4 attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #1 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nocallback nofree nounwind willreturn memory(argmem: write) } attributes #4 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } attributes #5 = { nounwind } ================================================ FILE: compiler/testdata/goroutine-cortex-m-qemu-tasks.ll ================================================ ; ModuleID = 'goroutine.go' source_filename = "goroutine.go" target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64" target triple = "thumbv7m-unknown-unknown-eabi" @"main$string" = internal unnamed_addr constant [4 x i8] c"test", align 1 ; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 ; Function Attrs: nounwind define hidden void @main.init(ptr %context) unnamed_addr #1 { entry: ret void } ; Function Attrs: nounwind define hidden void @main.regularFunctionGoroutine(ptr %context) unnamed_addr #1 { entry: %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"main.regularFunction$gowrapper" to i32), ptr undef) #9 call void @"internal/task.start"(i32 ptrtoint (ptr @"main.regularFunction$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 %stacksize, ptr undef) #9 ret void } declare void @main.regularFunction(i32, ptr) #2 ; Function Attrs: nounwind define linkonce_odr void @"main.regularFunction$gowrapper"(ptr %0) unnamed_addr #3 { entry: %unpack.int = ptrtoint ptr %0 to i32 call void @main.regularFunction(i32 %unpack.int, ptr undef) #9 ret void } declare i32 @"internal/task.getGoroutineStackSize"(i32, ptr) #2 declare void @"internal/task.start"(i32, ptr, i32, ptr) #2 ; Function Attrs: nounwind define hidden void @main.inlineFunctionGoroutine(ptr %context) unnamed_addr #1 { entry: %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"main.inlineFunctionGoroutine$1$gowrapper" to i32), ptr undef) #9 call void @"internal/task.start"(i32 ptrtoint (ptr @"main.inlineFunctionGoroutine$1$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 %stacksize, ptr undef) #9 ret void } ; Function Attrs: nounwind define internal void @"main.inlineFunctionGoroutine$1"(i32 %x, ptr %context) unnamed_addr #1 { entry: ret void } ; Function Attrs: nounwind define linkonce_odr void @"main.inlineFunctionGoroutine$1$gowrapper"(ptr %0) unnamed_addr #4 { entry: %unpack.int = ptrtoint ptr %0 to i32 call void @"main.inlineFunctionGoroutine$1"(i32 %unpack.int, ptr undef) ret void } ; Function Attrs: nounwind define hidden void @main.closureFunctionGoroutine(ptr %context) unnamed_addr #1 { entry: %n = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 store i32 3, ptr %n, align 4 %0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #9 store i32 5, ptr %0, align 4 %1 = getelementptr inbounds nuw i8, ptr %0, i32 4 store ptr %n, ptr %1, align 4 %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr undef) #9 call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #9 %2 = load i32, ptr %n, align 4 call void @runtime.printlock(ptr undef) #9 call void @runtime.printint32(i32 %2, ptr undef) #9 call void @runtime.printunlock(ptr undef) #9 ret void } ; Function Attrs: nounwind define internal void @"main.closureFunctionGoroutine$1"(i32 %x, ptr %context) unnamed_addr #1 { entry: store i32 7, ptr %context, align 4 ret void } ; Function Attrs: nounwind define linkonce_odr void @"main.closureFunctionGoroutine$1$gowrapper"(ptr %0) unnamed_addr #5 { entry: %1 = load i32, ptr %0, align 4 %2 = getelementptr inbounds nuw i8, ptr %0, i32 4 %3 = load ptr, ptr %2, align 4 call void @"main.closureFunctionGoroutine$1"(i32 %1, ptr %3) ret void } declare void @runtime.printlock(ptr) #2 declare void @runtime.printint32(i32, ptr) #2 declare void @runtime.printunlock(ptr) #2 ; Function Attrs: nounwind define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #1 { entry: %0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #9 store i32 5, ptr %0, align 4 %1 = getelementptr inbounds nuw i8, ptr %0, i32 4 store ptr %fn.context, ptr %1, align 4 %2 = getelementptr inbounds nuw i8, ptr %0, i32 8 store ptr %fn.funcptr, ptr %2, align 4 %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr undef) #9 call void @"internal/task.start"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #9 ret void } ; Function Attrs: nounwind define linkonce_odr void @main.funcGoroutine.gowrapper(ptr %0) unnamed_addr #6 { entry: %1 = load i32, ptr %0, align 4 %2 = getelementptr inbounds nuw i8, ptr %0, i32 4 %3 = load ptr, ptr %2, align 4 %4 = getelementptr inbounds nuw i8, ptr %0, i32 8 %5 = load ptr, ptr %4, align 4 call void %5(i32 %1, ptr %3) #9 ret void } ; Function Attrs: nounwind define hidden void @main.recoverBuiltinGoroutine(ptr %context) unnamed_addr #1 { entry: ret void } ; Function Attrs: nounwind define hidden void @main.copyBuiltinGoroutine(ptr %dst.data, i32 %dst.len, i32 %dst.cap, ptr %src.data, i32 %src.len, i32 %src.cap, ptr %context) unnamed_addr #1 { entry: %copy.n = call i32 @runtime.sliceCopy(ptr %dst.data, ptr %src.data, i32 %dst.len, i32 %src.len, i32 1, ptr undef) #9 ret void } declare i32 @runtime.sliceCopy(ptr nocapture writeonly, ptr nocapture readonly, i32, i32, i32, ptr) #2 ; Function Attrs: nounwind define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 { entry: call void @runtime.chanClose(ptr %ch, ptr undef) #9 ret void } declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #2 ; Function Attrs: nounwind define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 { entry: %0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #9 store ptr %itf.value, ptr %0, align 4 %1 = getelementptr inbounds nuw i8, ptr %0, i32 4 store ptr @"main$string", ptr %1, align 4 %2 = getelementptr inbounds nuw i8, ptr %0, i32 8 store i32 4, ptr %2, align 4 %3 = getelementptr inbounds nuw i8, ptr %0, i32 12 store ptr %itf.typecode, ptr %3, align 4 %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr undef) #9 call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #9 ret void } declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr, ptr, i32, ptr, ptr) #7 ; Function Attrs: nounwind define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(ptr %0) unnamed_addr #8 { entry: %1 = load ptr, ptr %0, align 4 %2 = getelementptr inbounds nuw i8, ptr %0, i32 4 %3 = load ptr, ptr %2, align 4 %4 = getelementptr inbounds nuw i8, ptr %0, i32 8 %5 = load i32, ptr %4, align 4 %6 = getelementptr inbounds nuw i8, ptr %0, i32 12 %7 = load ptr, ptr %6, align 4 call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, ptr %7, ptr undef) #9 ret void } attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } attributes #1 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } attributes #2 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } attributes #3 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="main.regularFunction" } attributes #4 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" } attributes #5 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="main.closureFunctionGoroutine$1" } attributes #6 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper" } attributes #7 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" } attributes #8 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" } attributes #9 = { nounwind } ================================================ FILE: compiler/testdata/goroutine-wasm-asyncify.ll ================================================ ; ModuleID = 'goroutine.go' source_filename = "goroutine.go" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20" target triple = "wasm32-unknown-wasi" @"main$string" = internal unnamed_addr constant [4 x i8] c"test", align 1 ; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1 ; Function Attrs: nounwind define hidden void @main.init(ptr %context) unnamed_addr #2 { entry: ret void } ; Function Attrs: nounwind define hidden void @main.regularFunctionGoroutine(ptr %context) unnamed_addr #2 { entry: call void @"internal/task.start"(i32 ptrtoint (ptr @"main.regularFunction$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 65536, ptr undef) #9 ret void } declare void @main.regularFunction(i32, ptr) #1 declare void @runtime.deadlock(ptr) #1 ; Function Attrs: nounwind define linkonce_odr void @"main.regularFunction$gowrapper"(ptr %0) unnamed_addr #3 { entry: %unpack.int = ptrtoint ptr %0 to i32 call void @main.regularFunction(i32 %unpack.int, ptr undef) #9 call void @runtime.deadlock(ptr undef) #9 unreachable } declare void @"internal/task.start"(i32, ptr, i32, ptr) #1 ; Function Attrs: nounwind define hidden void @main.inlineFunctionGoroutine(ptr %context) unnamed_addr #2 { entry: call void @"internal/task.start"(i32 ptrtoint (ptr @"main.inlineFunctionGoroutine$1$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 65536, ptr undef) #9 ret void } ; Function Attrs: nounwind define internal void @"main.inlineFunctionGoroutine$1"(i32 %x, ptr %context) unnamed_addr #2 { entry: ret void } ; Function Attrs: nounwind define linkonce_odr void @"main.inlineFunctionGoroutine$1$gowrapper"(ptr %0) unnamed_addr #4 { entry: %unpack.int = ptrtoint ptr %0 to i32 call void @"main.inlineFunctionGoroutine$1"(i32 %unpack.int, ptr undef) call void @runtime.deadlock(ptr undef) #9 unreachable } ; Function Attrs: nounwind define hidden void @main.closureFunctionGoroutine(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %n = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 call void @runtime.trackPointer(ptr nonnull %n, ptr nonnull %stackalloc, ptr undef) #9 store i32 3, ptr %n, align 4 call void @runtime.trackPointer(ptr nonnull %n, ptr nonnull %stackalloc, ptr undef) #9 call void @runtime.trackPointer(ptr nonnull @"main.closureFunctionGoroutine$1", ptr nonnull %stackalloc, ptr undef) #9 %0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #9 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #9 store i32 5, ptr %0, align 4 %1 = getelementptr inbounds nuw i8, ptr %0, i32 4 store ptr %n, ptr %1, align 4 call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #9 %2 = load i32, ptr %n, align 4 call void @runtime.printlock(ptr undef) #9 call void @runtime.printint32(i32 %2, ptr undef) #9 call void @runtime.printunlock(ptr undef) #9 ret void } ; Function Attrs: nounwind define internal void @"main.closureFunctionGoroutine$1"(i32 %x, ptr %context) unnamed_addr #2 { entry: store i32 7, ptr %context, align 4 ret void } ; Function Attrs: nounwind define linkonce_odr void @"main.closureFunctionGoroutine$1$gowrapper"(ptr %0) unnamed_addr #5 { entry: %1 = load i32, ptr %0, align 4 %2 = getelementptr inbounds nuw i8, ptr %0, i32 4 %3 = load ptr, ptr %2, align 4 call void @"main.closureFunctionGoroutine$1"(i32 %1, ptr %3) call void @runtime.deadlock(ptr undef) #9 unreachable } declare void @runtime.printlock(ptr) #1 declare void @runtime.printint32(i32, ptr) #1 declare void @runtime.printunlock(ptr) #1 ; Function Attrs: nounwind define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #9 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #9 store i32 5, ptr %0, align 4 %1 = getelementptr inbounds nuw i8, ptr %0, i32 4 store ptr %fn.context, ptr %1, align 4 %2 = getelementptr inbounds nuw i8, ptr %0, i32 8 store ptr %fn.funcptr, ptr %2, align 4 call void @"internal/task.start"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr nonnull %0, i32 65536, ptr undef) #9 ret void } ; Function Attrs: nounwind define linkonce_odr void @main.funcGoroutine.gowrapper(ptr %0) unnamed_addr #6 { entry: %1 = load i32, ptr %0, align 4 %2 = getelementptr inbounds nuw i8, ptr %0, i32 4 %3 = load ptr, ptr %2, align 4 %4 = getelementptr inbounds nuw i8, ptr %0, i32 8 %5 = load ptr, ptr %4, align 4 call void %5(i32 %1, ptr %3) #9 call void @runtime.deadlock(ptr undef) #9 unreachable } ; Function Attrs: nounwind define hidden void @main.recoverBuiltinGoroutine(ptr %context) unnamed_addr #2 { entry: ret void } ; Function Attrs: nounwind define hidden void @main.copyBuiltinGoroutine(ptr %dst.data, i32 %dst.len, i32 %dst.cap, ptr %src.data, i32 %src.len, i32 %src.cap, ptr %context) unnamed_addr #2 { entry: %copy.n = call i32 @runtime.sliceCopy(ptr %dst.data, ptr %src.data, i32 %dst.len, i32 %src.len, i32 1, ptr undef) #9 ret void } declare i32 @runtime.sliceCopy(ptr nocapture writeonly, ptr nocapture readonly, i32, i32, i32, ptr) #1 ; Function Attrs: nounwind define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 { entry: call void @runtime.chanClose(ptr %ch, ptr undef) #9 ret void } declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #1 ; Function Attrs: nounwind define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #9 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #9 store ptr %itf.value, ptr %0, align 4 %1 = getelementptr inbounds nuw i8, ptr %0, i32 4 store ptr @"main$string", ptr %1, align 4 %2 = getelementptr inbounds nuw i8, ptr %0, i32 8 store i32 4, ptr %2, align 4 %3 = getelementptr inbounds nuw i8, ptr %0, i32 12 store ptr %itf.typecode, ptr %3, align 4 call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #9 ret void } declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr, ptr, i32, ptr, ptr) #7 ; Function Attrs: nounwind define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(ptr %0) unnamed_addr #8 { entry: %1 = load ptr, ptr %0, align 4 %2 = getelementptr inbounds nuw i8, ptr %0, i32 4 %3 = load ptr, ptr %2, align 4 %4 = getelementptr inbounds nuw i8, ptr %0, i32 8 %5 = load i32, ptr %4, align 4 %6 = getelementptr inbounds nuw i8, ptr %0, i32 12 %7 = load ptr, ptr %6, align 4 call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, ptr %7, ptr undef) #9 call void @runtime.deadlock(ptr undef) #9 unreachable } attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #1 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="main.regularFunction" } attributes #4 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" } attributes #5 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="main.closureFunctionGoroutine$1" } attributes #6 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper" } attributes #7 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" } attributes #8 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" } attributes #9 = { nounwind } ================================================ FILE: compiler/testdata/goroutine.go ================================================ package main func regularFunctionGoroutine() { go regularFunction(5) } func inlineFunctionGoroutine() { go func(x int) { }(5) } func closureFunctionGoroutine() { n := 3 go func(x int) { n = 7 }(5) print(n) // note: this is racy (but good enough for this test) } func funcGoroutine(fn func(x int)) { go fn(5) } func recoverBuiltinGoroutine() { // This is a no-op. go recover() } func copyBuiltinGoroutine(dst, src []byte) { // This is not run in a goroutine. While this copy operation can indeed take // some time (if there is a lot of data to copy), there is no race-free way // to make use of the result so it's unlikely applications will make use of // it. And doing it this way should be just within the Go specification. go copy(dst, src) } func closeBuiltinGoroutine(ch chan int) { // This builtin is executed directly, not in a goroutine. // The observed behavior is the same. go close(ch) } func regularFunction(x int) type simpleInterface interface { Print(string) } func startInterfaceMethod(itf simpleInterface) { go itf.Print("test") } ================================================ FILE: compiler/testdata/interface.go ================================================ // This file tests interface types and interface builtins. package main // Test interface construction. func simpleType() interface{} { return 0 } func pointerType() interface{} { // Pointers have an element type, in this case int. var v *int return v } func interfaceType() interface{} { // Interfaces can exist in interfaces, but only indirectly (through // pointers). var v *error return v } func anonymousInterfaceType() interface{} { var v *interface { String() string } return v } // Test interface builtins. func isInt(itf interface{}) bool { _, ok := itf.(int) return ok } func isError(itf interface{}) bool { // Interface assert on (builtin) named interface type. _, ok := itf.(error) return ok } func isStringer(itf interface{}) bool { // Interface assert on anonymous interface type. _, ok := itf.(interface { String() string }) return ok } type fooInterface interface { String() string foo(int) byte } func callFooMethod(itf fooInterface) uint8 { return itf.foo(3) } func callErrorMethod(itf error) string { return itf.Error() } ================================================ FILE: compiler/testdata/interface.ll ================================================ ; ModuleID = 'interface.go' source_filename = "interface.go" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20" target triple = "wasm32-unknown-wasi" %runtime._interface = type { ptr, ptr } %runtime._string = type { ptr, i32 } @"reflect/types.type:basic:int" = linkonce_odr constant { i8, ptr } { i8 -62, ptr @"reflect/types.type:pointer:basic:int" }, align 4 @"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:int" }, align 4 @"reflect/types.type:pointer:named:error" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:named:error" }, align 4 @"reflect/types.type:named:error" = linkonce_odr constant { i8, i16, ptr, ptr, ptr, [7 x i8] } { i8 116, i16 1, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}", ptr @"reflect/types.type.pkgpath.empty", [7 x i8] c".error\00" }, align 4 @"reflect/types.type.pkgpath.empty" = linkonce_odr unnamed_addr constant [1 x i8] zeroinitializer, align 1 @"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr } { i8 84, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }, align 4 @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4 @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:interface:{String:func:{}{basic:string}}" }, align 4 @"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr } { i8 84, ptr @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" }, align 4 @"reflect/types.typeid:basic:int" = external constant i8 ; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1 ; Function Attrs: nounwind define hidden void @main.init(ptr %context) unnamed_addr #2 { entry: ret void } ; Function Attrs: nounwind define hidden %runtime._interface @main.simpleType(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:basic:int", ptr nonnull %stackalloc, ptr undef) #7 call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #7 ret %runtime._interface { ptr @"reflect/types.type:basic:int", ptr null } } ; Function Attrs: nounwind define hidden %runtime._interface @main.pointerType(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:pointer:basic:int", ptr nonnull %stackalloc, ptr undef) #7 call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #7 ret %runtime._interface { ptr @"reflect/types.type:pointer:basic:int", ptr null } } ; Function Attrs: nounwind define hidden %runtime._interface @main.interfaceType(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:pointer:named:error", ptr nonnull %stackalloc, ptr undef) #7 call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #7 ret %runtime._interface { ptr @"reflect/types.type:pointer:named:error", ptr null } } ; Function Attrs: nounwind define hidden %runtime._interface @main.anonymousInterfaceType(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}", ptr nonnull %stackalloc, ptr undef) #7 call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #7 ret %runtime._interface { ptr @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}", ptr null } } ; Function Attrs: nounwind define hidden i1 @main.isInt(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #2 { entry: %typecode = call i1 @runtime.typeAssert(ptr %itf.typecode, ptr nonnull @"reflect/types.typeid:basic:int", ptr undef) #7 br i1 %typecode, label %typeassert.ok, label %typeassert.next typeassert.next: ; preds = %typeassert.ok, %entry ret i1 %typecode typeassert.ok: ; preds = %entry br label %typeassert.next } declare i1 @runtime.typeAssert(ptr, ptr dereferenceable_or_null(1), ptr) #1 ; Function Attrs: nounwind define hidden i1 @main.isError(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #2 { entry: %0 = call i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(ptr %itf.typecode) #7 br i1 %0, label %typeassert.ok, label %typeassert.next typeassert.next: ; preds = %typeassert.ok, %entry ret i1 %0 typeassert.ok: ; preds = %entry br label %typeassert.next } declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(ptr) #3 ; Function Attrs: nounwind define hidden i1 @main.isStringer(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #2 { entry: %0 = call i1 @"interface:{String:func:{}{basic:string}}.$typeassert"(ptr %itf.typecode) #7 br i1 %0, label %typeassert.ok, label %typeassert.next typeassert.next: ; preds = %typeassert.ok, %entry ret i1 %0 typeassert.ok: ; preds = %entry br label %typeassert.next } declare i1 @"interface:{String:func:{}{basic:string}}.$typeassert"(ptr) #4 ; Function Attrs: nounwind define hidden i8 @main.callFooMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #2 { entry: %0 = call i8 @"interface:{String:func:{}{basic:string},main.foo:func:{basic:int}{basic:uint8}}.foo$invoke"(ptr %itf.value, i32 3, ptr %itf.typecode, ptr undef) #7 ret i8 %0 } declare i8 @"interface:{String:func:{}{basic:string},main.foo:func:{basic:int}{basic:uint8}}.foo$invoke"(ptr, i32, ptr, ptr) #5 ; Function Attrs: nounwind define hidden %runtime._string @main.callErrorMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %0 = call %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(ptr %itf.value, ptr %itf.typecode, ptr undef) #7 %1 = extractvalue %runtime._string %0, 0 call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #7 ret %runtime._string %0 } declare %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(ptr, ptr, ptr) #6 attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #1 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.Error() string" } attributes #4 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.String() string" } attributes #5 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="main.$methods.foo(int) uint8" "tinygo-methods"="reflect/methods.String() string; main.$methods.foo(int) uint8" } attributes #6 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Error() string" "tinygo-methods"="reflect/methods.Error() string" } attributes #7 = { nounwind } ================================================ FILE: compiler/testdata/pointer.go ================================================ package main // This file tests various operations on pointers, such as pointer arithmetic // and dereferencing pointers. import "unsafe" // Dereference pointers. func pointerDerefZero(x *[0]int) [0]int { return *x // This is a no-op, there is nothing to load. } // Unsafe pointer casts, they are sometimes a no-op. func pointerCastFromUnsafe(x unsafe.Pointer) *int { return (*int)(x) } func pointerCastToUnsafe(x *int) unsafe.Pointer { return unsafe.Pointer(x) } func pointerCastToUnsafeNoop(x *byte) unsafe.Pointer { return unsafe.Pointer(x) } ================================================ FILE: compiler/testdata/pointer.ll ================================================ ; ModuleID = 'pointer.go' source_filename = "pointer.go" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20" target triple = "wasm32-unknown-wasi" ; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1 ; Function Attrs: nounwind define hidden void @main.init(ptr %context) unnamed_addr #2 { entry: ret void } ; Function Attrs: nounwind define hidden [0 x i32] @main.pointerDerefZero(ptr %x, ptr %context) unnamed_addr #2 { entry: ret [0 x i32] zeroinitializer } ; Function Attrs: nounwind define hidden ptr @main.pointerCastFromUnsafe(ptr %x, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 call void @runtime.trackPointer(ptr %x, ptr nonnull %stackalloc, ptr undef) #3 ret ptr %x } ; Function Attrs: nounwind define hidden ptr @main.pointerCastToUnsafe(ptr dereferenceable_or_null(4) %x, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 call void @runtime.trackPointer(ptr %x, ptr nonnull %stackalloc, ptr undef) #3 ret ptr %x } ; Function Attrs: nounwind define hidden ptr @main.pointerCastToUnsafeNoop(ptr dereferenceable_or_null(1) %x, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 call void @runtime.trackPointer(ptr %x, ptr nonnull %stackalloc, ptr undef) #3 ret ptr %x } attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #1 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind } ================================================ FILE: compiler/testdata/pragma.go ================================================ package main import _ "unsafe" // Creates an external global with name extern_global. // //go:extern extern_global var externGlobal [0]byte // Creates a // //go:align 32 var alignedGlobal [4]uint32 // Test conflicting pragmas (the last one counts). // //go:align 64 //go:align 16 var alignedGlobal16 [4]uint32 // Test exported functions. // //export extern_func func externFunc() { } // Define a function in a different package using go:linkname. // //go:linkname withLinkageName1 somepkg.someFunction1 func withLinkageName1() { } // Import a function from a different package using go:linkname. // //go:linkname withLinkageName2 somepkg.someFunction2 func withLinkageName2() // Function has an 'inline hint', similar to the inline keyword in C. // //go:inline func inlineFunc() { } // Function should never be inlined, equivalent to GCC // __attribute__((noinline)). // //go:noinline func noinlineFunc() { } type Int interface { int8 | int16 } // Same for generic functions (but the compiler may miss the pragma due to it // being generic). // //go:noinline func noinlineGenericFunc[T Int]() { } func useGeneric() { // Make sure the generic function above is instantiated. noinlineGenericFunc[int8]() } // This function should have the specified section. // //go:section .special_function_section func functionInSection() { } //export exportedFunctionInSection //go:section .special_function_section func exportedFunctionInSection() { } //go:wasmimport modulename import1 func declaredImport() // Legacy way of importing a function. // //go:wasm-module foobar //export imported func foobarImport() // The wasm-module pragma is not functional here, but it should be safe. // //go:wasm-module foobar //export exported func foobarExportModule() { } // This function should not: it's only a declaration and not a definition. // //go:section .special_function_section func undefinedFunctionNotInSection() //go:section .special_global_section var globalInSection uint32 //go:section .special_global_section //go:extern undefinedGlobalNotInSection var undefinedGlobalNotInSection uint32 //go:align 1024 //go:section .global_section var multipleGlobalPragmas uint32 //go:noescape func doesNotEscapeParam(a *int, b []int, c chan int, d *[0]byte) // The //go:noescape pragma only works on declarations, not definitions. // //go:noescape func stillEscapes(a *int, b []int, c chan int, d *[0]byte) { } ================================================ FILE: compiler/testdata/pragma.ll ================================================ ; ModuleID = 'pragma.go' source_filename = "pragma.go" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20" target triple = "wasm32-unknown-wasi" @extern_global = external global [0 x i8], align 1 @main.alignedGlobal = hidden global [4 x i32] zeroinitializer, align 32 @main.alignedGlobal16 = hidden global [4 x i32] zeroinitializer, align 16 @llvm.used = appending global [3 x ptr] [ptr @extern_func, ptr @exportedFunctionInSection, ptr @exported] @main.globalInSection = hidden global i32 0, section ".special_global_section", align 4 @undefinedGlobalNotInSection = external global i32, align 4 @main.multipleGlobalPragmas = hidden global i32 0, section ".global_section", align 1024 ; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1 ; Function Attrs: nounwind define hidden void @main.init(ptr %context) unnamed_addr #2 { entry: ret void } ; Function Attrs: nounwind define void @extern_func() #3 { entry: ret void } ; Function Attrs: nounwind define hidden void @somepkg.someFunction1(ptr %context) unnamed_addr #2 { entry: ret void } declare void @somepkg.someFunction2(ptr) #1 ; Function Attrs: inlinehint nounwind define hidden void @main.inlineFunc(ptr %context) unnamed_addr #4 { entry: ret void } ; Function Attrs: noinline nounwind define hidden void @main.noinlineFunc(ptr %context) unnamed_addr #5 { entry: ret void } ; Function Attrs: nounwind define hidden void @main.useGeneric(ptr %context) unnamed_addr #2 { entry: call void @"main.noinlineGenericFunc[int8]"(ptr undef) ret void } ; Function Attrs: noinline nounwind define linkonce_odr hidden void @"main.noinlineGenericFunc[int8]"(ptr %context) unnamed_addr #5 { entry: ret void } ; Function Attrs: noinline nounwind define hidden void @main.functionInSection(ptr %context) unnamed_addr #5 section ".special_function_section" { entry: ret void } ; Function Attrs: noinline nounwind define void @exportedFunctionInSection() #6 section ".special_function_section" { entry: ret void } declare void @main.declaredImport() #7 declare void @imported() #8 ; Function Attrs: nounwind define void @exported() #9 { entry: ret void } declare void @main.undefinedFunctionNotInSection(ptr) #1 declare void @main.doesNotEscapeParam(ptr nocapture dereferenceable_or_null(4), ptr nocapture, i32, i32, ptr nocapture dereferenceable_or_null(36), ptr nocapture, ptr) #1 ; Function Attrs: nounwind define hidden void @main.stillEscapes(ptr dereferenceable_or_null(4) %a, ptr %b.data, i32 %b.len, i32 %b.cap, ptr dereferenceable_or_null(36) %c, ptr %d, ptr %context) unnamed_addr #2 { entry: ret void } attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #1 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-export-name"="extern_func" } attributes #4 = { inlinehint nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #5 = { noinline nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #6 = { noinline nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-export-name"="exportedFunctionInSection" } attributes #7 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-import-module"="modulename" "wasm-import-name"="import1" } attributes #8 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-import-module"="foobar" "wasm-import-name"="imported" } attributes #9 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-export-name"="exported" } ================================================ FILE: compiler/testdata/slice.go ================================================ package main import "unsafe" func sliceLen(ints []int) int { return len(ints) } func sliceCap(ints []int) int { return cap(ints) } func sliceElement(ints []int, index int) int { return ints[index] } func sliceAppendValues(ints []int) []int { return append(ints, 1, 2, 3) } func sliceAppendSlice(ints, added []int) []int { return append(ints, added...) } func sliceCopy(dst, src []int) int { return copy(dst, src) } // Test bounds checking in *ssa.MakeSlice instruction. func makeByteSlice(len int) []byte { return make([]byte, len) } func makeInt16Slice(len int) []int16 { return make([]int16, len) } func makeArraySlice(len int) [][3]byte { return make([][3]byte, len) // slice with element size of 3 } func makeInt32Slice(len int) []int32 { return make([]int32, len) } func Add32(p unsafe.Pointer, len int) unsafe.Pointer { return unsafe.Add(p, len) } func Add64(p unsafe.Pointer, len int64) unsafe.Pointer { return unsafe.Add(p, len) } func SliceToArray(s []int) *[4]int { return (*[4]int)(s) } func SliceToArrayConst() *[4]int { s := make([]int, 6) return (*[4]int)(s) } func SliceInt(ptr *int, len int) []int { return unsafe.Slice(ptr, len) } func SliceUint16(ptr *byte, len uint16) []byte { return unsafe.Slice(ptr, len) } func SliceUint64(ptr *int, len uint64) []int { return unsafe.Slice(ptr, len) } func SliceInt64(ptr *int, len int64) []int { return unsafe.Slice(ptr, len) } ================================================ FILE: compiler/testdata/slice.ll ================================================ ; ModuleID = 'slice.go' source_filename = "slice.go" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20" target triple = "wasm32-unknown-wasi" ; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1 ; Function Attrs: nounwind define hidden void @main.init(ptr %context) unnamed_addr #2 { entry: ret void } ; Function Attrs: nounwind define hidden i32 @main.sliceLen(ptr %ints.data, i32 %ints.len, i32 %ints.cap, ptr %context) unnamed_addr #2 { entry: ret i32 %ints.len } ; Function Attrs: nounwind define hidden i32 @main.sliceCap(ptr %ints.data, i32 %ints.len, i32 %ints.cap, ptr %context) unnamed_addr #2 { entry: ret i32 %ints.cap } ; Function Attrs: nounwind define hidden i32 @main.sliceElement(ptr %ints.data, i32 %ints.len, i32 %ints.cap, i32 %index, ptr %context) unnamed_addr #2 { entry: %.not = icmp ult i32 %index, %ints.len br i1 %.not, label %lookup.next, label %lookup.throw lookup.next: ; preds = %entry %0 = getelementptr inbounds i32, ptr %ints.data, i32 %index %1 = load i32, ptr %0, align 4 ret i32 %1 lookup.throw: ; preds = %entry call void @runtime.lookupPanic(ptr undef) #3 unreachable } declare void @runtime.lookupPanic(ptr) #1 ; Function Attrs: nounwind define hidden { ptr, i32, i32 } @main.sliceAppendValues(ptr %ints.data, i32 %ints.len, i32 %ints.cap, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %varargs = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %varargs, ptr nonnull %stackalloc, ptr undef) #3 store i32 1, ptr %varargs, align 4 %0 = getelementptr inbounds nuw i8, ptr %varargs, i32 4 store i32 2, ptr %0, align 4 %1 = getelementptr inbounds nuw i8, ptr %varargs, i32 8 store i32 3, ptr %1, align 4 %append.new = call { ptr, i32, i32 } @runtime.sliceAppend(ptr %ints.data, ptr nonnull %varargs, i32 %ints.len, i32 %ints.cap, i32 3, i32 4, ptr undef) #3 %append.newPtr = extractvalue { ptr, i32, i32 } %append.new, 0 %append.newLen = extractvalue { ptr, i32, i32 } %append.new, 1 %append.newCap = extractvalue { ptr, i32, i32 } %append.new, 2 %2 = insertvalue { ptr, i32, i32 } undef, ptr %append.newPtr, 0 %3 = insertvalue { ptr, i32, i32 } %2, i32 %append.newLen, 1 %4 = insertvalue { ptr, i32, i32 } %3, i32 %append.newCap, 2 call void @runtime.trackPointer(ptr %append.newPtr, ptr nonnull %stackalloc, ptr undef) #3 ret { ptr, i32, i32 } %4 } declare { ptr, i32, i32 } @runtime.sliceAppend(ptr, ptr nocapture readonly, i32, i32, i32, i32, ptr) #1 ; Function Attrs: nounwind define hidden { ptr, i32, i32 } @main.sliceAppendSlice(ptr %ints.data, i32 %ints.len, i32 %ints.cap, ptr %added.data, i32 %added.len, i32 %added.cap, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %append.new = call { ptr, i32, i32 } @runtime.sliceAppend(ptr %ints.data, ptr %added.data, i32 %ints.len, i32 %ints.cap, i32 %added.len, i32 4, ptr undef) #3 %append.newPtr = extractvalue { ptr, i32, i32 } %append.new, 0 %append.newLen = extractvalue { ptr, i32, i32 } %append.new, 1 %append.newCap = extractvalue { ptr, i32, i32 } %append.new, 2 %0 = insertvalue { ptr, i32, i32 } undef, ptr %append.newPtr, 0 %1 = insertvalue { ptr, i32, i32 } %0, i32 %append.newLen, 1 %2 = insertvalue { ptr, i32, i32 } %1, i32 %append.newCap, 2 call void @runtime.trackPointer(ptr %append.newPtr, ptr nonnull %stackalloc, ptr undef) #3 ret { ptr, i32, i32 } %2 } ; Function Attrs: nounwind define hidden i32 @main.sliceCopy(ptr %dst.data, i32 %dst.len, i32 %dst.cap, ptr %src.data, i32 %src.len, i32 %src.cap, ptr %context) unnamed_addr #2 { entry: %copy.n = call i32 @runtime.sliceCopy(ptr %dst.data, ptr %src.data, i32 %dst.len, i32 %src.len, i32 4, ptr undef) #3 ret i32 %copy.n } declare i32 @runtime.sliceCopy(ptr nocapture writeonly, ptr nocapture readonly, i32, i32, i32, ptr) #1 ; Function Attrs: nounwind define hidden { ptr, i32, i32 } @main.makeByteSlice(i32 %len, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %slice.maxcap = icmp slt i32 %len, 0 br i1 %slice.maxcap, label %slice.throw, label %slice.next slice.next: ; preds = %entry %makeslice.buf = call align 1 ptr @runtime.alloc(i32 %len, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %0 = insertvalue { ptr, i32, i32 } undef, ptr %makeslice.buf, 0 %1 = insertvalue { ptr, i32, i32 } %0, i32 %len, 1 %2 = insertvalue { ptr, i32, i32 } %1, i32 %len, 2 call void @runtime.trackPointer(ptr nonnull %makeslice.buf, ptr nonnull %stackalloc, ptr undef) #3 ret { ptr, i32, i32 } %2 slice.throw: ; preds = %entry call void @runtime.slicePanic(ptr undef) #3 unreachable } declare void @runtime.slicePanic(ptr) #1 ; Function Attrs: nounwind define hidden { ptr, i32, i32 } @main.makeInt16Slice(i32 %len, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %slice.maxcap = icmp slt i32 %len, 0 br i1 %slice.maxcap, label %slice.throw, label %slice.next slice.next: ; preds = %entry %makeslice.cap = shl nuw i32 %len, 1 %makeslice.buf = call align 2 ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %0 = insertvalue { ptr, i32, i32 } undef, ptr %makeslice.buf, 0 %1 = insertvalue { ptr, i32, i32 } %0, i32 %len, 1 %2 = insertvalue { ptr, i32, i32 } %1, i32 %len, 2 call void @runtime.trackPointer(ptr nonnull %makeslice.buf, ptr nonnull %stackalloc, ptr undef) #3 ret { ptr, i32, i32 } %2 slice.throw: ; preds = %entry call void @runtime.slicePanic(ptr undef) #3 unreachable } ; Function Attrs: nounwind define hidden { ptr, i32, i32 } @main.makeArraySlice(i32 %len, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %slice.maxcap = icmp ugt i32 %len, 1431655765 br i1 %slice.maxcap, label %slice.throw, label %slice.next slice.next: ; preds = %entry %makeslice.cap = mul i32 %len, 3 %makeslice.buf = call align 1 ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %0 = insertvalue { ptr, i32, i32 } undef, ptr %makeslice.buf, 0 %1 = insertvalue { ptr, i32, i32 } %0, i32 %len, 1 %2 = insertvalue { ptr, i32, i32 } %1, i32 %len, 2 call void @runtime.trackPointer(ptr nonnull %makeslice.buf, ptr nonnull %stackalloc, ptr undef) #3 ret { ptr, i32, i32 } %2 slice.throw: ; preds = %entry call void @runtime.slicePanic(ptr undef) #3 unreachable } ; Function Attrs: nounwind define hidden { ptr, i32, i32 } @main.makeInt32Slice(i32 %len, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %slice.maxcap = icmp ugt i32 %len, 1073741823 br i1 %slice.maxcap, label %slice.throw, label %slice.next slice.next: ; preds = %entry %makeslice.cap = shl nuw i32 %len, 2 %makeslice.buf = call align 4 ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %0 = insertvalue { ptr, i32, i32 } undef, ptr %makeslice.buf, 0 %1 = insertvalue { ptr, i32, i32 } %0, i32 %len, 1 %2 = insertvalue { ptr, i32, i32 } %1, i32 %len, 2 call void @runtime.trackPointer(ptr nonnull %makeslice.buf, ptr nonnull %stackalloc, ptr undef) #3 ret { ptr, i32, i32 } %2 slice.throw: ; preds = %entry call void @runtime.slicePanic(ptr undef) #3 unreachable } ; Function Attrs: nounwind define hidden ptr @main.Add32(ptr %p, i32 %len, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %0 = getelementptr i8, ptr %p, i32 %len call void @runtime.trackPointer(ptr %0, ptr nonnull %stackalloc, ptr undef) #3 ret ptr %0 } ; Function Attrs: nounwind define hidden ptr @main.Add64(ptr %p, i64 %len, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %0 = trunc i64 %len to i32 %1 = getelementptr i8, ptr %p, i32 %0 call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #3 ret ptr %1 } ; Function Attrs: nounwind define hidden ptr @main.SliceToArray(ptr %s.data, i32 %s.len, i32 %s.cap, ptr %context) unnamed_addr #2 { entry: %0 = icmp ult i32 %s.len, 4 br i1 %0, label %slicetoarray.throw, label %slicetoarray.next slicetoarray.next: ; preds = %entry ret ptr %s.data slicetoarray.throw: ; preds = %entry call void @runtime.sliceToArrayPointerPanic(ptr undef) #3 unreachable } declare void @runtime.sliceToArrayPointerPanic(ptr) #1 ; Function Attrs: nounwind define hidden ptr @main.SliceToArrayConst(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %makeslice = call align 4 dereferenceable(24) ptr @runtime.alloc(i32 24, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %makeslice, ptr nonnull %stackalloc, ptr undef) #3 br i1 false, label %slicetoarray.throw, label %slicetoarray.next slicetoarray.next: ; preds = %entry ret ptr %makeslice slicetoarray.throw: ; preds = %entry unreachable } ; Function Attrs: nounwind define hidden { ptr, i32, i32 } @main.SliceInt(ptr dereferenceable_or_null(4) %ptr, i32 %len, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %0 = icmp ugt i32 %len, 1073741823 %1 = icmp eq ptr %ptr, null %2 = icmp ne i32 %len, 0 %3 = and i1 %1, %2 %4 = or i1 %3, %0 br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next unsafe.Slice.next: ; preds = %entry %5 = insertvalue { ptr, i32, i32 } undef, ptr %ptr, 0 %6 = insertvalue { ptr, i32, i32 } %5, i32 %len, 1 %7 = insertvalue { ptr, i32, i32 } %6, i32 %len, 2 call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #3 ret { ptr, i32, i32 } %7 unsafe.Slice.throw: ; preds = %entry call void @runtime.unsafeSlicePanic(ptr undef) #3 unreachable } declare void @runtime.unsafeSlicePanic(ptr) #1 ; Function Attrs: nounwind define hidden { ptr, i32, i32 } @main.SliceUint16(ptr dereferenceable_or_null(1) %ptr, i16 %len, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %0 = icmp eq ptr %ptr, null %1 = icmp ne i16 %len, 0 %2 = and i1 %0, %1 br i1 %2, label %unsafe.Slice.throw, label %unsafe.Slice.next unsafe.Slice.next: ; preds = %entry %3 = zext i16 %len to i32 %4 = insertvalue { ptr, i32, i32 } undef, ptr %ptr, 0 %5 = insertvalue { ptr, i32, i32 } %4, i32 %3, 1 %6 = insertvalue { ptr, i32, i32 } %5, i32 %3, 2 call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #3 ret { ptr, i32, i32 } %6 unsafe.Slice.throw: ; preds = %entry call void @runtime.unsafeSlicePanic(ptr undef) #3 unreachable } ; Function Attrs: nounwind define hidden { ptr, i32, i32 } @main.SliceUint64(ptr dereferenceable_or_null(4) %ptr, i64 %len, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %0 = icmp ugt i64 %len, 1073741823 %1 = icmp eq ptr %ptr, null %2 = icmp ne i64 %len, 0 %3 = and i1 %1, %2 %4 = or i1 %3, %0 br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next unsafe.Slice.next: ; preds = %entry %5 = trunc nuw i64 %len to i32 %6 = insertvalue { ptr, i32, i32 } undef, ptr %ptr, 0 %7 = insertvalue { ptr, i32, i32 } %6, i32 %5, 1 %8 = insertvalue { ptr, i32, i32 } %7, i32 %5, 2 call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #3 ret { ptr, i32, i32 } %8 unsafe.Slice.throw: ; preds = %entry call void @runtime.unsafeSlicePanic(ptr undef) #3 unreachable } ; Function Attrs: nounwind define hidden { ptr, i32, i32 } @main.SliceInt64(ptr dereferenceable_or_null(4) %ptr, i64 %len, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %0 = icmp ugt i64 %len, 1073741823 %1 = icmp eq ptr %ptr, null %2 = icmp ne i64 %len, 0 %3 = and i1 %1, %2 %4 = or i1 %3, %0 br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next unsafe.Slice.next: ; preds = %entry %5 = trunc nuw i64 %len to i32 %6 = insertvalue { ptr, i32, i32 } undef, ptr %ptr, 0 %7 = insertvalue { ptr, i32, i32 } %6, i32 %5, 1 %8 = insertvalue { ptr, i32, i32 } %7, i32 %5, 2 call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #3 ret { ptr, i32, i32 } %8 unsafe.Slice.throw: ; preds = %entry call void @runtime.unsafeSlicePanic(ptr undef) #3 unreachable } attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #1 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind } ================================================ FILE: compiler/testdata/string.go ================================================ package main func someString() string { return "foo" } func zeroLengthString() string { return "" } func stringLen(s string) int { return len(s) } func stringIndex(s string, index int) byte { return s[index] } func stringCompareEqual(s1, s2 string) bool { return s1 == s2 } func stringCompareUnequal(s1, s2 string) bool { return s1 != s2 } func stringCompareLarger(s1, s2 string) bool { return s1 > s2 } func stringLookup(s string, x uint8) byte { // Test that x is correctly extended to an uint before comparison. return s[x] } ================================================ FILE: compiler/testdata/string.ll ================================================ ; ModuleID = 'string.go' source_filename = "string.go" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20" target triple = "wasm32-unknown-wasi" %runtime._string = type { ptr, i32 } @"main$string" = internal unnamed_addr constant [3 x i8] c"foo", align 1 ; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1 ; Function Attrs: nounwind define hidden void @main.init(ptr %context) unnamed_addr #2 { entry: ret void } ; Function Attrs: nounwind define hidden %runtime._string @main.someString(ptr %context) unnamed_addr #2 { entry: ret %runtime._string { ptr @"main$string", i32 3 } } ; Function Attrs: nounwind define hidden %runtime._string @main.zeroLengthString(ptr %context) unnamed_addr #2 { entry: ret %runtime._string zeroinitializer } ; Function Attrs: nounwind define hidden i32 @main.stringLen(ptr readonly %s.data, i32 %s.len, ptr %context) unnamed_addr #2 { entry: ret i32 %s.len } ; Function Attrs: nounwind define hidden i8 @main.stringIndex(ptr readonly %s.data, i32 %s.len, i32 %index, ptr %context) unnamed_addr #2 { entry: %.not = icmp ult i32 %index, %s.len br i1 %.not, label %lookup.next, label %lookup.throw lookup.next: ; preds = %entry %0 = getelementptr inbounds i8, ptr %s.data, i32 %index %1 = load i8, ptr %0, align 1 ret i8 %1 lookup.throw: ; preds = %entry call void @runtime.lookupPanic(ptr undef) #3 unreachable } declare void @runtime.lookupPanic(ptr) #1 ; Function Attrs: nounwind define hidden i1 @main.stringCompareEqual(ptr readonly %s1.data, i32 %s1.len, ptr readonly %s2.data, i32 %s2.len, ptr %context) unnamed_addr #2 { entry: %0 = call i1 @runtime.stringEqual(ptr %s1.data, i32 %s1.len, ptr %s2.data, i32 %s2.len, ptr undef) #3 ret i1 %0 } declare i1 @runtime.stringEqual(ptr readonly, i32, ptr readonly, i32, ptr) #1 ; Function Attrs: nounwind define hidden i1 @main.stringCompareUnequal(ptr readonly %s1.data, i32 %s1.len, ptr readonly %s2.data, i32 %s2.len, ptr %context) unnamed_addr #2 { entry: %0 = call i1 @runtime.stringEqual(ptr %s1.data, i32 %s1.len, ptr %s2.data, i32 %s2.len, ptr undef) #3 %1 = xor i1 %0, true ret i1 %1 } ; Function Attrs: nounwind define hidden i1 @main.stringCompareLarger(ptr readonly %s1.data, i32 %s1.len, ptr readonly %s2.data, i32 %s2.len, ptr %context) unnamed_addr #2 { entry: %0 = call i1 @runtime.stringLess(ptr %s2.data, i32 %s2.len, ptr %s1.data, i32 %s1.len, ptr undef) #3 ret i1 %0 } declare i1 @runtime.stringLess(ptr readonly, i32, ptr readonly, i32, ptr) #1 ; Function Attrs: nounwind define hidden i8 @main.stringLookup(ptr readonly %s.data, i32 %s.len, i8 %x, ptr %context) unnamed_addr #2 { entry: %0 = zext i8 %x to i32 %.not = icmp ugt i32 %s.len, %0 br i1 %.not, label %lookup.next, label %lookup.throw lookup.next: ; preds = %entry %1 = getelementptr inbounds nuw i8, ptr %s.data, i32 %0 %2 = load i8, ptr %1, align 1 ret i8 %2 lookup.throw: ; preds = %entry call void @runtime.lookupPanic(ptr undef) #3 unreachable } attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #1 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind } ================================================ FILE: compiler/testdata/zeromap.go ================================================ package main type hasPadding struct { b1 bool i int b2 bool } type nestedPadding struct { b bool hasPadding i int } //go:noinline func testZeroGet(m map[hasPadding]int, s hasPadding) int { return m[s] } //go:noinline func testZeroSet(m map[hasPadding]int, s hasPadding) { m[s] = 5 } //go:noinline func testZeroArrayGet(m map[[2]hasPadding]int, s [2]hasPadding) int { return m[s] } //go:noinline func testZeroArraySet(m map[[2]hasPadding]int, s [2]hasPadding) { m[s] = 5 } func main() { } ================================================ FILE: compiler/testdata/zeromap.ll ================================================ ; ModuleID = 'zeromap.go' source_filename = "zeromap.go" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20" target triple = "wasm32-unknown-wasi" %main.hasPadding = type { i1, i32, i1 } ; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1 ; Function Attrs: nounwind define hidden void @main.init(ptr %context) unnamed_addr #2 { entry: ret void } ; Function Attrs: noinline nounwind define hidden i32 @main.testZeroGet(ptr dereferenceable_or_null(40) %m, i1 %s.b1, i32 %s.i, i1 %s.b2, ptr %context) unnamed_addr #3 { entry: %hashmap.key = alloca %main.hasPadding, align 8 %hashmap.value = alloca i32, align 4 %0 = insertvalue %main.hasPadding zeroinitializer, i1 %s.b1, 0 %1 = insertvalue %main.hasPadding %0, i32 %s.i, 1 %2 = insertvalue %main.hasPadding %1, i1 %s.b2, 2 call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value) call void @llvm.lifetime.start.p0(i64 12, ptr nonnull %hashmap.key) store %main.hasPadding %2, ptr %hashmap.key, align 4 %3 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 1 call void @runtime.memzero(ptr nonnull %3, i32 3, ptr undef) #5 %4 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 9 call void @runtime.memzero(ptr nonnull %4, i32 3, ptr undef) #5 %5 = call i1 @runtime.hashmapBinaryGet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, i32 4, ptr undef) #5 call void @llvm.lifetime.end.p0(i64 12, ptr nonnull %hashmap.key) %6 = load i32, ptr %hashmap.value, align 4 call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value) ret i32 %6 } ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #4 declare void @runtime.memzero(ptr, i32, ptr) #1 declare i1 @runtime.hashmapBinaryGet(ptr dereferenceable_or_null(40), ptr, ptr, i32, ptr) #1 ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #4 ; Function Attrs: noinline nounwind define hidden void @main.testZeroSet(ptr dereferenceable_or_null(40) %m, i1 %s.b1, i32 %s.i, i1 %s.b2, ptr %context) unnamed_addr #3 { entry: %hashmap.key = alloca %main.hasPadding, align 8 %hashmap.value = alloca i32, align 4 %0 = insertvalue %main.hasPadding zeroinitializer, i1 %s.b1, 0 %1 = insertvalue %main.hasPadding %0, i32 %s.i, 1 %2 = insertvalue %main.hasPadding %1, i1 %s.b2, 2 call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value) store i32 5, ptr %hashmap.value, align 4 call void @llvm.lifetime.start.p0(i64 12, ptr nonnull %hashmap.key) store %main.hasPadding %2, ptr %hashmap.key, align 4 %3 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 1 call void @runtime.memzero(ptr nonnull %3, i32 3, ptr undef) #5 %4 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 9 call void @runtime.memzero(ptr nonnull %4, i32 3, ptr undef) #5 call void @runtime.hashmapBinarySet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, ptr undef) #5 call void @llvm.lifetime.end.p0(i64 12, ptr nonnull %hashmap.key) call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value) ret void } declare void @runtime.hashmapBinarySet(ptr dereferenceable_or_null(40), ptr, ptr, ptr) #1 ; Function Attrs: noinline nounwind define hidden i32 @main.testZeroArrayGet(ptr dereferenceable_or_null(40) %m, [2 x %main.hasPadding] %s, ptr %context) unnamed_addr #3 { entry: %hashmap.key = alloca [2 x %main.hasPadding], align 8 %hashmap.value = alloca i32, align 4 call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value) call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %hashmap.key) %s.elt = extractvalue [2 x %main.hasPadding] %s, 0 store %main.hasPadding %s.elt, ptr %hashmap.key, align 4 %hashmap.key.repack1 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 12 %s.elt2 = extractvalue [2 x %main.hasPadding] %s, 1 store %main.hasPadding %s.elt2, ptr %hashmap.key.repack1, align 4 %0 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 1 call void @runtime.memzero(ptr nonnull %0, i32 3, ptr undef) #5 %1 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 9 call void @runtime.memzero(ptr nonnull %1, i32 3, ptr undef) #5 %2 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 13 call void @runtime.memzero(ptr nonnull %2, i32 3, ptr undef) #5 %3 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 21 call void @runtime.memzero(ptr nonnull %3, i32 3, ptr undef) #5 %4 = call i1 @runtime.hashmapBinaryGet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, i32 4, ptr undef) #5 call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %hashmap.key) %5 = load i32, ptr %hashmap.value, align 4 call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value) ret i32 %5 } ; Function Attrs: noinline nounwind define hidden void @main.testZeroArraySet(ptr dereferenceable_or_null(40) %m, [2 x %main.hasPadding] %s, ptr %context) unnamed_addr #3 { entry: %hashmap.key = alloca [2 x %main.hasPadding], align 8 %hashmap.value = alloca i32, align 4 call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value) store i32 5, ptr %hashmap.value, align 4 call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %hashmap.key) %s.elt = extractvalue [2 x %main.hasPadding] %s, 0 store %main.hasPadding %s.elt, ptr %hashmap.key, align 4 %hashmap.key.repack1 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 12 %s.elt2 = extractvalue [2 x %main.hasPadding] %s, 1 store %main.hasPadding %s.elt2, ptr %hashmap.key.repack1, align 4 %0 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 1 call void @runtime.memzero(ptr nonnull %0, i32 3, ptr undef) #5 %1 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 9 call void @runtime.memzero(ptr nonnull %1, i32 3, ptr undef) #5 %2 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 13 call void @runtime.memzero(ptr nonnull %2, i32 3, ptr undef) #5 %3 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 21 call void @runtime.memzero(ptr nonnull %3, i32 3, ptr undef) #5 call void @runtime.hashmapBinarySet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, ptr undef) #5 call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %hashmap.key) call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value) ret void } ; Function Attrs: nounwind define hidden void @main.main(ptr %context) unnamed_addr #2 { entry: ret void } attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #1 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { noinline nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #4 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } attributes #5 = { nounwind } ================================================ FILE: compiler/volatile.go ================================================ package compiler import "go/types" // This file implements volatile loads/stores in runtime/volatile.LoadT and // runtime/volatile.StoreT as compiler builtins. // createVolatileLoad is the implementation of the intrinsic function // runtime/volatile.LoadT(). func (b *builder) createVolatileLoad() { b.createFunctionStart(true) addr := b.getValue(b.fn.Params[0], getPos(b.fn)) b.createNilCheck(b.fn.Params[0], addr, "deref") valType := b.getLLVMType(b.fn.Params[0].Type().(*types.Pointer).Elem()) val := b.CreateLoad(valType, addr, "") val.SetVolatile(true) b.CreateRet(val) } // createVolatileStore is the implementation of the intrinsic function // runtime/volatile.StoreT(). func (b *builder) createVolatileStore() { b.createFunctionStart(true) addr := b.getValue(b.fn.Params[0], getPos(b.fn)) val := b.getValue(b.fn.Params[1], getPos(b.fn)) b.createNilCheck(b.fn.Params[0], addr, "deref") store := b.CreateStore(val, addr) store.SetVolatile(true) b.CreateRetVoid() } ================================================ FILE: corpus_test.go ================================================ package main import ( "flag" "os" "os/exec" "strings" "sync" "testing" "golang.org/x/tools/go/buildutil" yaml "gopkg.in/yaml.v2" ) /* This contains code from https://github.com/dgryski/tinygo-test-corpus The MIT License (MIT) Copyright (c) 2020 Damian Gryski Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ var corpus = flag.String("corpus", "", "path to test corpus") func TestCorpus(t *testing.T) { t.Parallel() if *corpus == "" { t.Skip() } var target string if *testTarget != "" { target = *testTarget } isWASI := strings.HasPrefix(target, "wasi") repos, err := loadRepos(*corpus) if err != nil { t.Fatalf("loading corpus: %v", err) } for _, repo := range repos { repo := repo name := repo.Repo if repo.Tags != "" { name += "(" + strings.ReplaceAll(repo.Tags, " ", "-") + ")" } if repo.Version != "" { name += "@" + repo.Version } t.Run(name, func(t *testing.T) { t.Parallel() if isWASI && repo.SkipWASI { t.Skip("skip wasi") } if repo.Slow && testing.Short() { t.Skip("slow test") } var wg sync.WaitGroup defer wg.Wait() out := ioLogger(t, &wg) defer out.Close() dir := t.TempDir() cmd := exec.Command("go", "mod", "init", "github.com/tinygo/tinygo-corpus-test") cmd.Dir = dir cmd.Stdout, cmd.Stderr = out, out err := cmd.Run() if err != nil { t.Errorf("failed to init: %s", err.Error()) return } var ver string if repo.Version != "" { ver = "@" + repo.Version } cmd = exec.Command("go", "get", "-t", "-d", repo.Repo+"/..."+ver) cmd.Dir = dir cmd.Stdout, cmd.Stderr = out, out err = cmd.Run() if err != nil { t.Errorf("failed to get: %s", err.Error()) return } doTest := func(t *testing.T, path string) { var wg sync.WaitGroup defer wg.Wait() out := ioLogger(t, &wg) defer out.Close() opts := optionsFromTarget(target, sema) opts.Directory = dir var tags buildutil.TagsFlag tags.Set(repo.Tags) opts.Tags = []string(tags) opts.TestConfig.Verbose = testing.Verbose() passed, err := Test(path, out, out, &opts, "") if err != nil { t.Errorf("test error: %v", err) } if !passed { t.Error("test failed") } } if len(repo.Subdirs) == 0 { doTest(t, repo.Repo) return } for _, dir := range repo.Subdirs { dir := dir t.Run(dir.Pkg, func(t *testing.T) { t.Parallel() if isWASI && dir.SkipWASI { t.Skip("skip wasi") } if dir.Slow && testing.Short() { t.Skip("slow test") } doTest(t, repo.Repo+"/"+dir.Pkg) }) } }) } } type T struct { Repo string Tags string Subdirs []Subdir SkipWASI bool Slow bool Version string } type Subdir struct { Pkg string SkipWASI bool Slow bool } func loadRepos(f string) ([]T, error) { yf, err := os.ReadFile(f) if err != nil { return nil, err } var repos []T err = yaml.Unmarshal(yf, &repos) if err != nil { return nil, err } return repos, nil } ================================================ FILE: diagnostics/diagnostics.go ================================================ // Package diagnostics formats compiler errors and prints them in a consistent // way. package diagnostics import ( "bytes" "fmt" "go/scanner" "go/token" "go/types" "io" "path/filepath" "reflect" "sort" "strings" "github.com/tinygo-org/tinygo/builder" "github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/interp" "github.com/tinygo-org/tinygo/loader" ) // A single diagnostic. type Diagnostic struct { Pos token.Position Msg string // Start and end position, if available. For many errors these positions are // not available, but for some they are. StartPos token.Position EndPos token.Position } // One or multiple errors of a particular package. // It can also represent whole-program errors (like linker errors) that can't // easily be connected to a single package. type PackageDiagnostic struct { ImportPath string // the same ImportPath as in `go list -json` Diagnostics []Diagnostic } // Diagnostics of a whole program. This can include errors belonging to multiple // packages, or just a single package. type ProgramDiagnostic []PackageDiagnostic // CreateDiagnostics reads the underlying errors in the error object and creates // a set of diagnostics that's sorted and can be readily printed. func CreateDiagnostics(err error) ProgramDiagnostic { if err == nil { return nil } // Right now, the compiler will only show errors for the first package that // fails to build. This is likely to change in the future. return ProgramDiagnostic{ createPackageDiagnostic(err), } } // Create diagnostics for a single package (though, in practice, it may also be // used for whole-program diagnostics in some cases). func createPackageDiagnostic(err error) PackageDiagnostic { // Extract diagnostics for this package. var pkgDiag PackageDiagnostic switch err := err.(type) { case *builder.MultiError: if err.ImportPath != "" { pkgDiag.ImportPath = err.ImportPath } for _, err := range err.Errs { diags := createDiagnostics(err) pkgDiag.Diagnostics = append(pkgDiag.Diagnostics, diags...) } case loader.Errors: if err.Pkg != nil { pkgDiag.ImportPath = err.Pkg.ImportPath } for _, err := range err.Errs { diags := createDiagnostics(err) pkgDiag.Diagnostics = append(pkgDiag.Diagnostics, diags...) } case *interp.Error: pkgDiag.ImportPath = err.ImportPath w := &bytes.Buffer{} fmt.Fprintln(w, err.Error()) if len(err.Inst) != 0 { fmt.Fprintln(w, err.Inst) } if len(err.Traceback) > 0 { fmt.Fprintln(w, "\ntraceback:") for _, line := range err.Traceback { fmt.Fprintln(w, line.Pos.String()+":") fmt.Fprintln(w, line.Inst) } } pkgDiag.Diagnostics = append(pkgDiag.Diagnostics, Diagnostic{ Msg: w.String(), }) default: pkgDiag.Diagnostics = createDiagnostics(err) } // Sort these diagnostics by file/line/column. sort.SliceStable(pkgDiag.Diagnostics, func(i, j int) bool { posI := pkgDiag.Diagnostics[i].Pos posJ := pkgDiag.Diagnostics[j].Pos if posI.Filename != posJ.Filename { return posI.Filename < posJ.Filename } if posI.Line != posJ.Line { return posI.Line < posJ.Line } return posI.Column < posJ.Column }) return pkgDiag } // Extract diagnostics from the given error message and return them as a slice // of errors (which in many cases will just be a single diagnostic). func createDiagnostics(err error) []Diagnostic { switch err := err.(type) { case types.Error: diag := Diagnostic{ Pos: err.Fset.Position(err.Pos), Msg: err.Msg, } // There is a special unexported API since Go 1.16 that provides the // range (start and end position) where the type error exists. // There is no promise of backwards compatibility in future Go versions // so we have to be extra careful here to be resilient. v := reflect.ValueOf(err) start := v.FieldByName("go116start") end := v.FieldByName("go116end") if start.IsValid() && end.IsValid() && start.Int() != end.Int() { diag.StartPos = err.Fset.Position(token.Pos(start.Int())) diag.EndPos = err.Fset.Position(token.Pos(end.Int())) } return []Diagnostic{diag} case scanner.Error: return []Diagnostic{ { Pos: err.Pos, Msg: err.Msg, }, } case scanner.ErrorList: var diags []Diagnostic for _, err := range err { diags = append(diags, createDiagnostics(*err)...) } return diags case loader.Error: if err.Err.Pos.Filename != "" { // Probably a syntax error in a dependency. return createDiagnostics(err.Err) } else { // Probably an "import cycle not allowed" error. buf := &bytes.Buffer{} fmt.Fprintln(buf, "package", err.ImportStack[0]) for i := 1; i < len(err.ImportStack); i++ { pkgPath := err.ImportStack[i] if i == len(err.ImportStack)-1 { // last package fmt.Fprintln(buf, "\timports", pkgPath+": "+err.Err.Error()) } else { // not the last package fmt.Fprintln(buf, "\timports", pkgPath) } } return []Diagnostic{ {Msg: buf.String()}, } } default: return []Diagnostic{ {Msg: err.Error()}, } } } // Write program diagnostics to the given writer with 'wd' as the relative // working directory. func (progDiag ProgramDiagnostic) WriteTo(w io.Writer, wd string) { for _, pkgDiag := range progDiag { pkgDiag.WriteTo(w, wd) } } // Write package diagnostics to the given writer with 'wd' as the relative // working directory. func (pkgDiag PackageDiagnostic) WriteTo(w io.Writer, wd string) { if pkgDiag.ImportPath != "" { fmt.Fprintln(w, "#", pkgDiag.ImportPath) } for _, diag := range pkgDiag.Diagnostics { diag.WriteTo(w, wd) } } // Write this diagnostic to the given writer with 'wd' as the relative working // directory. func (diag Diagnostic) WriteTo(w io.Writer, wd string) { if diag.Pos == (token.Position{}) { fmt.Fprintln(w, diag.Msg) return } pos := RelativePosition(diag.Pos, wd) fmt.Fprintf(w, "%s: %s\n", pos, diag.Msg) } // Convert the position in pos (assumed to have an absolute path) into a // relative path if possible. Paths inside GOROOT/TINYGOROOT will remain // absolute. func RelativePosition(pos token.Position, wd string) token.Position { // Check whether we even have a working directory. if wd == "" { return pos } // Paths inside GOROOT should be printed in full. if strings.HasPrefix(pos.Filename, filepath.Join(goenv.Get("GOROOT"), "src")) || strings.HasPrefix(pos.Filename, filepath.Join(goenv.Get("TINYGOROOT"), "src")) { return pos } // Make the path relative, for easier reading. Ignore any errors in the // process (falling back to the absolute path). relpath, err := filepath.Rel(wd, pos.Filename) if err == nil { pos.Filename = relpath } return pos } ================================================ FILE: diff.go ================================================ // Copyright 2022 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "bytes" "fmt" "sort" "strings" ) // A pair is a pair of values tracked for both the x and y side of a diff. // It is typically a pair of line indexes. type pair struct{ x, y int } // Diff returns an anchored diff of the two texts old and new // in the “unified diff” format. If old and new are identical, // Diff returns a nil slice (no output). // // Unix diff implementations typically look for a diff with // the smallest number of lines inserted and removed, // which can in the worst case take time quadratic in the // number of lines in the texts. As a result, many implementations // either can be made to run for a long time or cut off the search // after a predetermined amount of work. // // In contrast, this implementation looks for a diff with the // smallest number of “unique” lines inserted and removed, // where unique means a line that appears just once in both old and new. // We call this an “anchored diff” because the unique lines anchor // the chosen matching regions. An anchored diff is usually clearer // than a standard diff, because the algorithm does not try to // reuse unrelated blank lines or closing braces. // The algorithm also guarantees to run in O(n log n) time // instead of the standard O(n²) time. // // Some systems call this approach a “patience diff,” named for // the “patience sorting” algorithm, itself named for a solitaire card game. // We avoid that name for two reasons. First, the name has been used // for a few different variants of the algorithm, so it is imprecise. // Second, the name is frequently interpreted as meaning that you have // to wait longer (to be patient) for the diff, meaning that it is a slower algorithm, // when in fact the algorithm is faster than the standard one. func Diff(oldName string, old []byte, newName string, new []byte) []byte { if bytes.Equal(old, new) { return nil } x := lines(old) y := lines(new) // Print diff header. var out bytes.Buffer fmt.Fprintf(&out, "diff %s %s\n", oldName, newName) fmt.Fprintf(&out, "--- %s\n", oldName) fmt.Fprintf(&out, "+++ %s\n", newName) // Loop over matches to consider, // expanding each match to include surrounding lines, // and then printing diff chunks. // To avoid setup/teardown cases outside the loop, // tgs returns a leading {0,0} and trailing {len(x), len(y)} pair // in the sequence of matches. var ( done pair // printed up to x[:done.x] and y[:done.y] chunk pair // start lines of current chunk count pair // number of lines from each side in current chunk ctext []string // lines for current chunk ) for _, m := range tgs(x, y) { if m.x < done.x { // Already handled scanning forward from earlier match. continue } // Expand matching lines as far as possible, // establishing that x[start.x:end.x] == y[start.y:end.y]. // Note that on the first (or last) iteration we may (or definitely do) // have an empty match: start.x==end.x and start.y==end.y. start := m for start.x > done.x && start.y > done.y && x[start.x-1] == y[start.y-1] { start.x-- start.y-- } end := m for end.x < len(x) && end.y < len(y) && x[end.x] == y[end.y] { end.x++ end.y++ } // Emit the mismatched lines before start into this chunk. // (No effect on first sentinel iteration, when start = {0,0}.) for _, s := range x[done.x:start.x] { ctext = append(ctext, "-"+s) count.x++ } for _, s := range y[done.y:start.y] { ctext = append(ctext, "+"+s) count.y++ } // If we're not at EOF and have too few common lines, // the chunk includes all the common lines and continues. const C = 3 // number of context lines if (end.x < len(x) || end.y < len(y)) && (end.x-start.x < C || (len(ctext) > 0 && end.x-start.x < 2*C)) { for _, s := range x[start.x:end.x] { ctext = append(ctext, " "+s) count.x++ count.y++ } done = end continue } // End chunk with common lines for context. if len(ctext) > 0 { n := end.x - start.x if n > C { n = C } for _, s := range x[start.x : start.x+n] { ctext = append(ctext, " "+s) count.x++ count.y++ } done = pair{start.x + n, start.y + n} // Format and emit chunk. // Convert line numbers to 1-indexed. // Special case: empty file shows up as 0,0 not 1,0. if count.x > 0 { chunk.x++ } if count.y > 0 { chunk.y++ } fmt.Fprintf(&out, "@@ -%d,%d +%d,%d @@\n", chunk.x, count.x, chunk.y, count.y) for _, s := range ctext { out.WriteString(s) } count.x = 0 count.y = 0 ctext = ctext[:0] } // If we reached EOF, we're done. if end.x >= len(x) && end.y >= len(y) { break } // Otherwise start a new chunk. chunk = pair{end.x - C, end.y - C} for _, s := range x[chunk.x:end.x] { ctext = append(ctext, " "+s) count.x++ count.y++ } done = end } return out.Bytes() } // lines returns the lines in the file x, including newlines. // If the file does not end in a newline, one is supplied // along with a warning about the missing newline. func lines(x []byte) []string { l := strings.SplitAfter(string(x), "\n") if l[len(l)-1] == "" { l = l[:len(l)-1] } else { // Treat last line as having a message about the missing newline attached, // using the same text as BSD/GNU diff (including the leading backslash). l[len(l)-1] += "\n\\ No newline at end of file\n" } return l } // tgs returns the pairs of indexes of the longest common subsequence // of unique lines in x and y, where a unique line is one that appears // once in x and once in y. // // The longest common subsequence algorithm is as described in // Thomas G. Szymanski, “A Special Case of the Maximal Common // Subsequence Problem,” Princeton TR #170 (January 1975), // available at https://research.swtch.com/tgs170.pdf. func tgs(x, y []string) []pair { // Count the number of times each string appears in a and b. // We only care about 0, 1, many, counted as 0, -1, -2 // for the x side and 0, -4, -8 for the y side. // Using negative numbers now lets us distinguish positive line numbers later. m := make(map[string]int) for _, s := range x { if c := m[s]; c > -2 { m[s] = c - 1 } } for _, s := range y { if c := m[s]; c > -8 { m[s] = c - 4 } } // Now unique strings can be identified by m[s] = -1+-4. // // Gather the indexes of those strings in x and y, building: // xi[i] = increasing indexes of unique strings in x. // yi[i] = increasing indexes of unique strings in y. // inv[i] = index j such that x[xi[i]] = y[yi[j]]. var xi, yi, inv []int for i, s := range y { if m[s] == -1+-4 { m[s] = len(yi) yi = append(yi, i) } } for i, s := range x { if j, ok := m[s]; ok && j >= 0 { xi = append(xi, i) inv = append(inv, j) } } // Apply Algorithm A from Szymanski's paper. // In those terms, A = J = inv and B = [0, n). // We add sentinel pairs {0,0}, and {len(x),len(y)} // to the returned sequence, to help the processing loop. J := inv n := len(xi) T := make([]int, n) L := make([]int, n) for i := range T { T[i] = n + 1 } for i := 0; i < n; i++ { k := sort.Search(n, func(k int) bool { return T[k] >= J[i] }) T[k] = J[i] L[i] = k + 1 } k := 0 for _, v := range L { if k < v { k = v } } seq := make([]pair, 2+k) seq[1+k] = pair{len(x), len(y)} // sentinel at end lastj := n for i := n - 1; i >= 0; i-- { if L[i] == k && J[i] < lastj { seq[k] = pair{xi[i], yi[J[i]]} k-- } } seq[0] = pair{0, 0} // sentinel at start return seq } ================================================ FILE: docs/GNUmakefile ================================================ # Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help help: @echo "Please use \`$(MAKE) ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " epub3 to make an epub3" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" @echo " dummy to check syntax errors of document sources" .PHONY: clean clean: rm -rf $(BUILDDIR)/* .PHONY: html html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." .PHONY: dirhtml dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." .PHONY: singlehtml singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." .PHONY: pickle pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." .PHONY: json json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." .PHONY: htmlhelp htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." .PHONY: qthelp qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/TinyGo.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/TinyGo.qhc" .PHONY: applehelp applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." .PHONY: devhelp devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/TinyGo" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/TinyGo" @echo "# devhelp" .PHONY: epub epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." .PHONY: epub3 epub3: $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 @echo @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." .PHONY: latex latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." .PHONY: latexpdf latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: latexpdfja latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: text text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." .PHONY: man man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." .PHONY: texinfo texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." .PHONY: info info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." .PHONY: gettext gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." .PHONY: changes changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." .PHONY: linkcheck linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." .PHONY: doctest doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." .PHONY: coverage coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." .PHONY: xml xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." .PHONY: pseudoxml pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." .PHONY: dummy dummy: $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy @echo @echo "Build finished. Dummy builder generates no files." ================================================ FILE: docs/conf.py ================================================ # -*- coding: utf-8 -*- # # TinyGo documentation build configuration file, created by # sphinx-quickstart on Sat Sep 22 15:05:19 2018. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [] # Add any paths that contain templates here, relative to this directory. templates_path = ['templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'TinyGo' copyright = u'2018, Ayke van Laethem' author = u'Ayke van Laethem' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = u'master' # The full version, including alpha/beta/rc tags. release = u'master' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # # today = '' # # Else, today_fmt is used as the format for a strftime call. # # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The reST default role (used for this markup: `text`) to use for all # documents. # # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # try: import sphinx_rtd_theme html_theme = 'sphinx_rtd_theme' except: html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. # " v documentation" by default. # # html_title = u'TinyGo vmaster' # A shorter title for the navigation bar. Default is the same as html_title. # # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # # html_logo = None # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # # html_extra_path = [] # If not None, a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # The empty string is equivalent to '%b %d, %Y'. # # html_last_updated_fmt = None # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # # html_additional_pages = {} # If false, no module index is generated. # # html_domain_indices = True # If false, no index is generated. # # html_use_index = True # If true, the index is split into individual pages for each letter. # # html_split_index = False # If true, links to the reST sources are added to the pages. # # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' # # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # 'ja' uses this config value. # 'zh' user can custom change `jieba` dictionary path. # # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'TinyGodoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'TinyGo.tex', u'TinyGo Documentation', u'Ayke van Laethem', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # # latex_use_parts = False # If true, show page references after internal links. # # latex_show_pagerefs = False # If true, show URL addresses after external links. # # latex_show_urls = False # Documents to append as an appendix to all manuals. # # latex_appendices = [] # It false, will not define \strong, \code, itleref, \crossref ... but only # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added # packages. # # latex_keep_old_macro_names = True # If false, no module index is generated. # # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'tinygo', u'TinyGo Documentation', [author], 1) ] # If true, show URL addresses after external links. # # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'TinyGo', u'TinyGo Documentation', author, 'TinyGo', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # # texinfo_appendices = [] # If false, no module index is generated. # # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # # texinfo_no_detailmenu = False ================================================ FILE: docs/index.rst ================================================ .. TinyGo documentation master file, created by sphinx-quickstart on Sat Sep 22 15:05:19 2018. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. The TinyGo site has moved! ================================== Contents: .. toctree:: :maxdepth: 2 moved ================================================ FILE: docs/make.bat ================================================ @ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. epub3 to make an epub3 echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled echo. coverage to run coverage check of the documentation if enabled echo. dummy to check syntax errors of document sources goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) REM Check if sphinx-build is available and fallback to Python version if any %SPHINXBUILD% 1>NUL 2>NUL if errorlevel 9009 goto sphinx_python goto sphinx_ok :sphinx_python set SPHINXBUILD=python -m sphinx.__init__ %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) :sphinx_ok if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\TinyGo.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\TinyGo.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "epub3" ( %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %~dp0 echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %~dp0 echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "coverage" ( %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage if errorlevel 1 exit /b 1 echo. echo.Testing of coverage in the sources finished, look at the ^ results in %BUILDDIR%/coverage/python.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) if "%1" == "dummy" ( %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy if errorlevel 1 exit /b 1 echo. echo.Build finished. Dummy builder generates no files. goto end ) :end ================================================ FILE: docs/moved.rst ================================================ .. _moved: .. highlight:: none The TinyGo Docs Site Has Moved ========================= The documentation web site for TinyGo has moved. You can find the new web site at `tinygo.org `_ Thank you! ================================================ FILE: errors_test.go ================================================ package main import ( "bytes" "os" "path/filepath" "regexp" "strings" "testing" "github.com/tinygo-org/tinygo/builder" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/diagnostics" ) // Test the error messages of the TinyGo compiler. func TestErrors(t *testing.T) { // TODO: nicely formatted error messages for: // - duplicate symbols in ld.lld (currently only prints bitcode file) type errorTest struct { name string target string } for _, tc := range []errorTest{ {name: "cgo"}, {name: "compiler"}, {name: "interp"}, {name: "invalidmain"}, {name: "invalidname"}, {name: "linker-flashoverflow", target: "cortex-m-qemu"}, {name: "linker-ramoverflow", target: "cortex-m-qemu"}, {name: "linker-undefined", target: "darwin/arm64"}, {name: "linker-undefined", target: "linux/amd64"}, //{name: "linker-undefined", target: "windows/amd64"}, // TODO: no source location {name: "linker-undefined", target: "cortex-m-qemu"}, //{name: "linker-undefined", target: "wasip1"}, // TODO: no source location {name: "loader-importcycle"}, {name: "loader-invaliddep"}, {name: "loader-invalidpackage"}, {name: "loader-nopackage"}, {name: "optimizer"}, {name: "syntax"}, {name: "types"}, } { name := tc.name if tc.target != "" { name += "#" + tc.target } target := tc.target if target == "" { target = "wasip1" } t.Run(name, func(t *testing.T) { options := optionsFromTarget(target, sema) testErrorMessages(t, "./testdata/errors/"+tc.name+".go", &options) }) } } func testErrorMessages(t *testing.T, filename string, options *compileopts.Options) { t.Parallel() // Parse expected error messages. expected := readErrorMessages(t, filename) // Try to build a binary (this should fail with an error). tmpdir := t.TempDir() config, err := builder.NewConfig(options) if err != nil { t.Fatal("expected to get a compiler error") } err = Build(filename, tmpdir+"/out", config) if err == nil { t.Fatal("expected to get a compiler error") } // Get the full ./testdata/errors directory. wd, absErr := filepath.Abs("testdata/errors") if absErr != nil { t.Fatal(absErr) } // Write error message out as plain text. var buf bytes.Buffer diagnostics.CreateDiagnostics(err).WriteTo(&buf, wd) actual := strings.TrimRight(buf.String(), "\n") // Check whether the error is as expected. if !matchErrors(t, expected, actual) { t.Errorf("expected error:\n%s\ngot:\n%s", indentText(expected, "> "), indentText(actual, "> ")) } } func matchErrors(t *testing.T, pattern, actual string) bool { patternLines := strings.Split(pattern, "\n") actualLines := strings.Split(actual, "\n") if len(patternLines) != len(actualLines) { return false } for i, patternLine := range patternLines { indices := regexp.MustCompile(`\{\{.*?\}\}`).FindAllStringIndex(patternLine, -1) patternParts := []string{"^"} lastStop := 0 for _, startstop := range indices { start := startstop[0] stop := startstop[1] patternParts = append(patternParts, regexp.QuoteMeta(patternLine[lastStop:start]), patternLine[start+2:stop-2]) lastStop = stop } patternParts = append(patternParts, regexp.QuoteMeta(patternLine[lastStop:]), "$") pattern := strings.Join(patternParts, "") re, err := regexp.Compile(pattern) if err != nil { t.Fatalf("could not compile regexp for %#v: %v", patternLine, err) } if !re.MatchString(actualLines[i]) { return false } } return true } // Indent the given text with a given indentation string. func indentText(text, indent string) string { return indent + strings.ReplaceAll(text, "\n", "\n"+indent) } // Read "// ERROR:" prefixed messages from the given file. func readErrorMessages(t *testing.T, file string) string { data, err := os.ReadFile(file) if err != nil { t.Fatal("could not read input file:", err) } var errors []string for _, line := range strings.Split(string(data), "\n") { if strings.HasPrefix(line, "// ERROR: ") { errors = append(errors, strings.TrimRight(line[len("// ERROR: "):], "\r\n")) } } return strings.Join(errors, "\n") } ================================================ FILE: flake.nix ================================================ # A Nix flake file, mainly intended for developing TinyGo. # You can download Nix here, for use on your Linux or macOS system: # https://nixos.org/download.html # After you have installed Nix, you can enter the development environment as # follows: # # nix develop # # This drops you into a bash shell, where you can install TinyGo simply using # the following command: # # go install # # That's all! Assuming you've set up your $PATH correctly, you can now use the # tinygo command as usual: # # tinygo version # # But you'll need a bit more to make TinyGo actually able to compile code: # # make llvm-source # fetch compiler-rt # git submodule update --init # fetch lots of other libraries and SVD files # make gen-device -j4 # build src/device/*/*.go files # # With this, you should have an environment that can compile anything - except # for the Xtensa architecture (ESP8266/ESP32) because support for that lives in # a separate LLVM fork. # # You can also do many other things from this environment. Building and flashing # should work as you're used to: it's not a VM or container so there are no # access restrictions and you're running in the same host environment - just # with a slightly different set of tools available. { inputs = { # Use a recent stable release, but fix the version to make it reproducible. # This version should be updated from time to time. nixpkgs.url = "nixpkgs/nixos-25.05"; flake-utils.url = "github:numtide/flake-utils"; }; outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; in with pkgs; { devShells.default = mkShell { buildInputs = [ # These dependencies are required for building tinygo (go install). go llvmPackages_20.llvm llvmPackages_20.libclang # Additional dependencies needed at runtime, for building and/or # flashing. llvmPackages_20.lld avrdude binaryen # Additional dependencies needed for on-chip debugging. # These tools are rather big (especially GDB) and not frequently # used, so are commented out. On-chip debugging is still possible if # these tools are available in the host environment. #gdb #openocd ]; shellHook= '' # Make `make smoketest` work (the default is `md5`, while Nix only # has `md5sum`). export MD5SUM=md5sum # Ugly hack to make the Clang resources directory available. export GOFLAGS="\"-ldflags=-X github.com/tinygo-org/tinygo/goenv.clangResourceDir=${llvmPackages_20.clang.cc.lib}/lib/clang/20\" -tags=llvm20" ''; }; } ); } ================================================ FILE: go.mod ================================================ module github.com/tinygo-org/tinygo go 1.22.0 require ( github.com/aykevl/go-wasm v0.0.2-0.20250317121156-42b86c494139 github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 github.com/gofrs/flock v0.8.1 github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892 github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-tty v0.0.4 github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 github.com/tetratelabs/wazero v1.6.0 go.bug.st/serial v1.6.0 golang.org/x/net v0.35.0 golang.org/x/sys v0.30.0 golang.org/x/tools v0.30.0 gopkg.in/yaml.v2 v2.4.0 tinygo.org/x/go-llvm v0.0.0-20250422114502-b8f170971e74 ) require ( github.com/creack/goselect v0.1.2 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/stretchr/testify v1.8.4 // indirect golang.org/x/text v0.22.0 // indirect ) ================================================ FILE: go.sum ================================================ github.com/aykevl/go-wasm v0.0.2-0.20250317121156-42b86c494139 h1:2O/WuAt8J5id3khcAtVB90czG80m+v0sfkLE07GrCVg= github.com/aykevl/go-wasm v0.0.2-0.20250317121156-42b86c494139/go.mod h1:7sXyiaA0WtSogCu67R2252fQpVmJMh9JWJ9ddtGkpWw= github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI= github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf h1:FtEj8sfIcaaBfAKrE1Cwb61YDtYq9JxChK1c7AKce7s= github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf/go.mod h1:yrqSXGoD/4EKfF26AOGzscPOgTTJcyAwM2rpixWT+t4= github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892 h1:6J+qramlHVLmiBOgRiBOnQkno8uprqG6YFFQTt6uYIw= github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-tty v0.0.4 h1:NVikla9X8MN0SQAqCYzpGyXv0jY7MNl3HOWD2dkle7E= github.com/mattn/go-tty v0.0.4/go.mod h1:u5GGXBtZU6RQoKV8gY5W6UhMudbR5vXnUe7j3pxse28= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs= github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tetratelabs/wazero v1.6.0 h1:z0H1iikCdP8t+q341xqepY4EWvHEw8Es7tlqiVzlP3g= github.com/tetratelabs/wazero v1.6.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A= go.bug.st/serial v1.6.0 h1:mAbRGN4cKE2J5gMwsMHC2KQisdLRQssO9WSM+rbZJ8A= go.bug.st/serial v1.6.0/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE= golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= tinygo.org/x/go-llvm v0.0.0-20250422114502-b8f170971e74 h1:ovavgTdIBWCH8YWlcfq9gkpoyT1+IxMKSn+Df27QwE8= tinygo.org/x/go-llvm v0.0.0-20250422114502-b8f170971e74/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0= ================================================ FILE: goenv/goenv.go ================================================ // Package goenv returns environment variables that are used in various parts of // the compiler. You can query it manually with the `tinygo env` subcommand. package goenv import ( "bytes" "encoding/json" "errors" "fmt" "io/fs" "os" "os/exec" "path/filepath" "runtime" "strings" "sync" "tinygo.org/x/go-llvm" ) // Keys is a slice of all available environment variable keys. var Keys = []string{ "GOOS", "GOARCH", "GOROOT", "GOPATH", "GOCACHE", "CGO_ENABLED", "TINYGOROOT", } func init() { switch Get("GOARCH") { case "arm": Keys = append(Keys, "GOARM") case "mips", "mipsle": Keys = append(Keys, "GOMIPS") } } // Set to true if we're linking statically against LLVM. var hasBuiltinTools = false // TINYGOROOT is the path to the final location for checking tinygo files. If // unset (by a -X ldflag), then sourceDir() will fallback to the original build // directory. var TINYGOROOT string // If a particular Clang resource dir must always be used and TinyGo can't // figure out the directory using heuristics, this global can be set using a // linker flag. // This is needed for Nix. var clangResourceDir string // Variables read from a `go env` command invocation. var goEnvVars struct { GOPATH string GOROOT string GOVERSION string } var goEnvVarsOnce sync.Once var goEnvVarsErr error // error returned from cmd.Run // Make sure goEnvVars is fresh. This can be called multiple times, the first // time will update all environment variables in goEnvVars. func readGoEnvVars() error { goEnvVarsOnce.Do(func() { cmd := exec.Command("go", "env", "-json", "GOPATH", "GOROOT", "GOVERSION") output, err := cmd.Output() if err != nil { // Check for "command not found" error. if execErr, ok := err.(*exec.Error); ok { goEnvVarsErr = fmt.Errorf("could not find '%s' command: %w", execErr.Name, execErr.Err) return } // It's perhaps a bit ugly to handle this error here, but I couldn't // think of a better place further up in the call chain. if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() != 0 { if len(exitErr.Stderr) != 0 { // The 'go' command exited with an error message. Print that // message and exit, so we behave in a similar way. os.Stderr.Write(exitErr.Stderr) os.Exit(exitErr.ExitCode()) } } // Other errors. Not sure whether there are any, but just in case. goEnvVarsErr = err return } err = json.Unmarshal(output, &goEnvVars) if err != nil { // This should never happen if we have a sane Go toolchain // installed. goEnvVarsErr = fmt.Errorf("unexpected error while unmarshalling `go env` output: %w", err) } }) return goEnvVarsErr } // Get returns a single environment variable, possibly calculating it on-demand. // The empty string is returned for unknown environment variables. func Get(name string) string { switch name { case "GOOS": goos := os.Getenv("GOOS") if goos == "" { goos = runtime.GOOS } if goos == "android" { goos = "linux" } return goos case "GOARCH": if dir := os.Getenv("GOARCH"); dir != "" { return dir } return runtime.GOARCH case "GOARM": if goarm := os.Getenv("GOARM"); goarm != "" { return goarm } if goos := Get("GOOS"); goos == "windows" || goos == "android" { // Assume Windows and Android are running on modern CPU cores. // This matches upstream Go. return "7" } // Default to ARMv6 on other devices. // The difference between ARMv5 and ARMv6 is big, much bigger than the // difference between ARMv6 and ARMv7. ARMv6 binaries are much smaller, // especially when floating point instructions are involved. return "6" case "GOMIPS": gomips := os.Getenv("GOMIPS") if gomips == "" { // Default to hardfloat (this matches the Go toolchain). gomips = "hardfloat" } return gomips case "GOROOT": readGoEnvVars() return goEnvVars.GOROOT case "GOPATH": readGoEnvVars() return goEnvVars.GOPATH case "GOCACHE": // Get the cache directory, usually ~/.cache/tinygo dir, err := os.UserCacheDir() if err != nil { panic("could not find cache dir: " + err.Error()) } return filepath.Join(dir, "tinygo") case "CGO_ENABLED": // Always enable CGo. It is required by a number of targets, including // macOS and the rp2040. return "1" case "TINYGOROOT": return sourceDir() case "WASMOPT": if path := os.Getenv("WASMOPT"); path != "" { err := wasmOptCheckVersion(path) if err != nil { fmt.Fprintf(os.Stderr, "cannot use %q as wasm-opt (from WASMOPT environment variable): %s", path, err.Error()) os.Exit(1) } return path } return findWasmOpt() case "WASMTOOLS": if path := os.Getenv("WASMTOOLS"); path != "" { return path } return "wasm-tools" default: return "" } } // Find wasm-opt, or exit with an error. func findWasmOpt() string { tinygoroot := sourceDir() searchPaths := []string{ tinygoroot + "/bin/wasm-opt", tinygoroot + "/build/wasm-opt", } var paths []string for _, path := range searchPaths { if runtime.GOOS == "windows" { path += ".exe" } _, err := os.Stat(path) if err != nil && errors.Is(err, fs.ErrNotExist) { continue } paths = append(paths, path) } if path, err := exec.LookPath("wasm-opt"); err == nil { paths = append(paths, path) } if len(paths) == 0 { fmt.Fprintln(os.Stderr, "error: could not find wasm-opt, set the WASMOPT environment variable to override") os.Exit(1) } errs := make([]error, len(paths)) for i, path := range paths { err := wasmOptCheckVersion(path) if err == nil { return path } errs[i] = err } fmt.Fprintln(os.Stderr, "no usable wasm-opt found, update or run \"make binaryen\"") for i, path := range paths { fmt.Fprintf(os.Stderr, "\t%s: %s\n", path, errs[i].Error()) } os.Exit(1) panic("unreachable") } // wasmOptCheckVersion checks if a copy of wasm-opt is usable. func wasmOptCheckVersion(path string) error { cmd := exec.Command(path, "--version") var buf bytes.Buffer cmd.Stdout = &buf cmd.Stderr = os.Stderr err := cmd.Run() if err != nil { return err } str := buf.String() if strings.Contains(str, "(") { // The git tag may be placed in parentheses after the main version string. str = strings.Split(str, "(")[0] } str = strings.TrimSpace(str) var ver uint _, err = fmt.Sscanf(str, "wasm-opt version %d", &ver) if err != nil || ver < 102 { return errors.New("incompatible wasm-opt (need 102 or newer)") } return nil } // Return the TINYGOROOT, or exit with an error. func sourceDir() string { // Use $TINYGOROOT as root, if available. root := os.Getenv("TINYGOROOT") if root != "" { if !isSourceDir(root) { fmt.Fprintln(os.Stderr, "error: $TINYGOROOT was not set to the correct root") os.Exit(1) } return root } if TINYGOROOT != "" { if !isSourceDir(TINYGOROOT) { fmt.Fprintln(os.Stderr, "error: TINYGOROOT was not set to the correct root") os.Exit(1) } return TINYGOROOT } // Find root from executable path. path, err := os.Executable() if err != nil { // Very unlikely. Bail out if it happens. panic("could not get executable path: " + err.Error()) } root = filepath.Dir(filepath.Dir(path)) if isSourceDir(root) { return root } // Fallback: use the original directory from where it was built // https://stackoverflow.com/a/32163888/559350 _, path, _, _ = runtime.Caller(0) root = filepath.Dir(filepath.Dir(path)) if isSourceDir(root) { return root } fmt.Fprintln(os.Stderr, "error: could not autodetect root directory, set the TINYGOROOT environment variable to override") os.Exit(1) panic("unreachable") } // isSourceDir returns true if the directory looks like a TinyGo source directory. func isSourceDir(root string) bool { _, err := os.Stat(filepath.Join(root, "src/runtime/internal/sys/zversion.go")) if err != nil { return false } _, err = os.Stat(filepath.Join(root, "src/device/arm/arm.go")) return err == nil } // ClangResourceDir returns the clang resource dir if available. This is the // -resource-dir flag. If it isn't available, an empty string is returned and // -resource-dir should be left unset. // The libclang flag must be set if the resource dir is read for use by // libclang. // In that case, the resource dir is always returned (even when linking // dynamically against LLVM) because libclang always needs this directory. func ClangResourceDir(libclang bool) string { if clangResourceDir != "" { // The resource dir is forced to a particular value at build time. // This is needed on Nix for example, where Clang and libclang don't // know their own resource dir. // Also see: // https://discourse.nixos.org/t/why-is-the-clang-resource-dir-split-in-a-separate-package/34114 return clangResourceDir } if !hasBuiltinTools && !libclang { // Using external tools, so the resource dir doesn't need to be // specified. Clang knows where to find it. return "" } // Check whether we're running from a TinyGo release directory. // This is the case for release binaries on GitHub. root := Get("TINYGOROOT") releaseHeaderDir := filepath.Join(root, "lib", "clang") if _, err := os.Stat(releaseHeaderDir); !errors.Is(err, fs.ErrNotExist) { return releaseHeaderDir } if hasBuiltinTools { // We are statically linked to LLVM. // Check whether we're running from the source directory. // This typically happens when TinyGo was built using `make` as part of // development. llvmMajor := strings.Split(llvm.Version, ".")[0] buildResourceDir := filepath.Join(root, "llvm-build", "lib", "clang", llvmMajor) if _, err := os.Stat(buildResourceDir); !errors.Is(err, fs.ErrNotExist) { return buildResourceDir } } else { // We use external tools, either when installed using `go install` or // when packaged in a Linux distribution (Linux distros typically prefer // dynamic linking). // Try to detect the system clang resources directory. resourceDir := findSystemClangResources(root) if resourceDir != "" { return resourceDir } } // Resource directory not found. return "" } // Find the Clang resource dir on this particular system. // Return the empty string when they aren't found. func findSystemClangResources(TINYGOROOT string) string { llvmMajor := strings.Split(llvm.Version, ".")[0] switch runtime.GOOS { case "linux", "android": // Header files are typically stored in /usr/lib/clang//include. // Tested on Fedora 39, Debian 12, and Arch Linux. path := filepath.Join("/usr/lib/clang", llvmMajor) _, err := os.Stat(filepath.Join(path, "include", "stdint.h")) if err == nil { return path } case "darwin": // This assumes a Homebrew installation, like in builder/commands.go. var prefix string switch runtime.GOARCH { case "amd64": prefix = "/usr/local/opt/llvm@" + llvmMajor case "arm64": prefix = "/opt/homebrew/opt/llvm@" + llvmMajor default: return "" // very unlikely for now } path := fmt.Sprintf("%s/lib/clang/%s", prefix, llvmMajor) _, err := os.Stat(path + "/include/stdint.h") if err == nil { return path } } // Could not find it. return "" } ================================================ FILE: goenv/tools-builtin.go ================================================ //go:build byollvm package goenv func init() { hasBuiltinTools = true } ================================================ FILE: goenv/version.go ================================================ package goenv import ( "errors" "fmt" "io" "runtime/debug" "strings" ) // Version of TinyGo. // Update this value before release of new version of software. const version = "0.40.1" // Return TinyGo version, either in the form 0.30.0 or as a development version // (like 0.30.0-dev-abcd012). func Version() string { v := version if strings.HasSuffix(version, "-dev") { if hash := readGitHash(); hash != "" { v += "-" + hash } } return v } func readGitHash() string { if info, ok := debug.ReadBuildInfo(); ok { for _, setting := range info.Settings { if setting.Key == "vcs.revision" { return setting.Value[:8] } } } return "" } // GetGorootVersion returns the major and minor version for a given GOROOT path. // If the goroot cannot be determined, (0, 0) is returned. func GetGorootVersion() (major, minor int, err error) { s, err := GorootVersionString() if err != nil { return 0, 0, err } major, minor, _, err = Parse(s) return major, minor, err } // Parse parses the Go version (like "go1.3.2") in the parameter and return the // major, minor, and patch version: 1, 3, and 2 in this example. // If there is an error, (0, 0, 0) and an error will be returned. func Parse(version string) (major, minor, patch int, err error) { if strings.HasPrefix(version, "devel ") { version = strings.Split(strings.TrimPrefix(version, "devel "), version)[0] } if version == "" || version[:2] != "go" { return 0, 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix") } parts := strings.Split(version[2:], ".") if len(parts) < 2 { return 0, 0, 0, errors.New("could not parse Go version: version has less than two parts") } // Ignore the errors, we don't really handle errors here anyway. var trailing string n, err := fmt.Sscanf(version, "go%d.%d.%d%s", &major, &minor, &patch, &trailing) if n == 2 { n, err = fmt.Sscanf(version, "go%d.%d%s", &major, &minor, &trailing) } if n >= 2 && err == io.EOF { // Means there were no trailing characters (i.e., not an alpha/beta) err = nil } if err != nil { return 0, 0, 0, fmt.Errorf("failed to parse version: %s", err) } return major, minor, patch, nil } // Compare compares two Go version strings. // The result will be 0 if a == b, -1 if a < b, and +1 if a > b. // If either a or b is not a valid Go version, it is treated as "go0.0" // and compared lexicographically. // See [Parse] for more information. func Compare(a, b string) int { aMajor, aMinor, aPatch, _ := Parse(a) bMajor, bMinor, bPatch, _ := Parse(b) switch { case aMajor < bMajor: return -1 case aMajor > bMajor: return +1 case aMinor < bMinor: return -1 case aMinor > bMinor: return +1 case aPatch < bPatch: return -1 case aPatch > bPatch: return +1 default: return strings.Compare(a, b) } } // GorootVersionString returns the version string as reported by the Go // toolchain. It is usually of the form `go1.x.y` but can have some variations // (for beta releases, for example). func GorootVersionString() (string, error) { err := readGoEnvVars() return goEnvVars.GOVERSION, err } ================================================ FILE: goenv/version_test.go ================================================ package goenv import "testing" func TestParse(t *testing.T) { tests := []struct { v string major int minor int patch int wantErr bool }{ {"", 0, 0, 0, true}, {"go", 0, 0, 0, true}, {"go1", 0, 0, 0, true}, {"go.0", 0, 0, 0, true}, {"go1.0", 1, 0, 0, false}, {"go1.1", 1, 1, 0, false}, {"go1.23", 1, 23, 0, false}, {"go1.23.5", 1, 23, 5, false}, {"go1.23.5-rc6", 1, 23, 5, false}, {"go2.0", 2, 0, 0, false}, {"go2.0.15", 2, 0, 15, false}, {"devel go1.24-f99f5da18f Thu Nov 14 22:29:26 2024 +0000 darwin/arm64", 1, 24, 0, false}, } for _, tt := range tests { t.Run(tt.v, func(t *testing.T) { major, minor, patch, err := Parse(tt.v) if err == nil && tt.wantErr { t.Errorf("Parse(%q): expected err != nil", tt.v) } if err != nil && !tt.wantErr { t.Errorf("Parse(%q): expected err == nil", tt.v) } if major != tt.major || minor != tt.minor || patch != tt.patch { t.Errorf("Parse(%q): expected %d, %d, %d, nil; got %d, %d, %d, %v", tt.v, tt.major, tt.minor, tt.patch, major, minor, patch, err) } }) } } func TestCompare(t *testing.T) { tests := []struct { a string b string want int }{ {"", "", 0}, {"go0", "go0", 0}, {"go0", "go1", -1}, {"go1", "go0", 1}, {"go1", "go2", -1}, {"go2", "go1", 1}, {"go1.1", "go1.2", -1}, {"go1.2", "go1.1", 1}, {"go1.1.0", "go1.2.0", -1}, {"go1.2.0", "go1.1.0", 1}, {"go1.2.0", "go2.3.0", -1}, {"go1.23.2", "go1.23.10", -1}, {"go0.1.22", "go1.23.101", -1}, } for _, tt := range tests { t.Run(tt.a+" "+tt.b, func(t *testing.T) { got := Compare(tt.a, tt.b) if got != tt.want { t.Errorf("Compare(%q, %q): expected %d; got %d", tt.a, tt.b, tt.want, got) } }) } } ================================================ FILE: hooks/README.md ================================================ # Hooks for Docker Hub Files in this directory are custom commands to be run during the different Docker Hub build phases. See https://docs.docker.com/docker-hub/builds/advanced/#custom-build-phase-hooks ================================================ FILE: hooks/post_checkout ================================================ #!/bin/bash # Docker hub does a recursive clone, then checks the branch out, # so when a PR adds a submodule (or updates it), it fails. git submodule update --init ================================================ FILE: internal/tools/go.mod ================================================ // TODO: remove this (by merging it into the top-level go.mod) // once the top level go.mod specifies a go new enough to make our version of misspell happy. module tools go 1.21 require ( github.com/golangci/misspell v0.6.0 github.com/mgechev/revive v1.3.9 ) require ( github.com/BurntSushi/toml v1.4.0 // indirect github.com/chavacava/garif v0.1.0 // indirect github.com/fatih/color v1.17.0 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/spf13/afero v1.11.0 // indirect golang.org/x/sys v0.22.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.23.0 // indirect ) ================================================ FILE: internal/tools/go.sum ================================================ github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 h1:zpIH83+oKzcpryru8ceC6BxnoG8TBrhgAvRg8obzup0= github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= github.com/mgechev/revive v1.3.9 h1:18Y3R4a2USSBF+QZKFQwVkBROUda7uoBlkEuBD+YD1A= github.com/mgechev/revive v1.3.9/go.mod h1:+uxEIr5UH0TjXWHTno3xh4u7eg6jDpXKzQccA9UGhHU= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: internal/tools/tools.go ================================================ //go:build tools // Install tools specified in go.mod. // See https://marcofranssen.nl/manage-go-tools-via-go-modules for idiom. package tools import ( _ "github.com/golangci/misspell" _ "github.com/mgechev/revive" ) //go:generate go install github.com/golangci/misspell/cmd/misspell //go:generate go install github.com/mgechev/revive ================================================ FILE: internal/wasm-tools/README.md ================================================ # wasm-tools directory This directory has a separate `go.mod` file because the `wasm-tools-go` module requires Go 1.22, while TinyGo itself supports Go 1.19. When the minimum Go version for TinyGo is 1.22, this directory can be folded into `internal/tools` and the `go.mod` and `go.sum` files deleted. ================================================ FILE: internal/wasm-tools/go.mod ================================================ module github.com/tinygo-org/tinygo/internal/wasm-tools go 1.23.0 require ( go.bytecodealliance.org v0.6.2 go.bytecodealliance.org/cm v0.2.2 ) require ( github.com/coreos/go-semver v0.3.1 // indirect github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/regclient/regclient v0.8.2 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/tetratelabs/wazero v1.9.0 // indirect github.com/ulikunitz/xz v0.5.12 // indirect github.com/urfave/cli/v3 v3.0.0-beta1 // indirect golang.org/x/mod v0.24.0 // indirect golang.org/x/sys v0.31.0 // indirect ) ================================================ FILE: internal/wasm-tools/go.sum ================================================ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/olareg/olareg v0.1.1 h1:Ui7q93zjcoF+U9U71sgqgZWByDoZOpqHitUXEu2xV+g= github.com/olareg/olareg v0.1.1/go.mod h1:w8NP4SWrHHtxsFaUiv1lnCnYPm4sN1seCd2h7FK/dc0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/regclient/regclient v0.8.2 h1:23BQ3jWgKYHHIXUhp/S9laVJDHDoOQaQCzXMJ4undVE= github.com/regclient/regclient v0.8.2/go.mod h1:uGyetv0o6VLyRDjtfeBqp/QBwRLJ3Hcn07/+8QbhNcM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg= github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= go.bytecodealliance.org v0.6.2 h1:Jy4u5DVmSkXgsnwojBhJ+AD/YsJsR3VzVnxF0xRCqTQ= go.bytecodealliance.org v0.6.2/go.mod h1:gqjTJm0y9NSksG4py/lSjIQ/SNuIlOQ+hCIEPQwtJgA= go.bytecodealliance.org/cm v0.2.2 h1:M9iHS6qs884mbQbIjtLX1OifgyPG9DuMs2iwz8G4WQA= go.bytecodealliance.org/cm v0.2.2/go.mod h1:JD5vtVNZv7sBoQQkvBvAAVKJPhR/bqBH7yYXTItMfZI= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: internal/wasm-tools/tools.go ================================================ //go:build tools // Install tools specified in go.mod. // See https://marcofranssen.nl/manage-go-tools-via-go-modules for idiom. package tools import ( _ "go.bytecodealliance.org/cm" _ "go.bytecodealliance.org/cmd/wit-bindgen-go" ) //go:generate go install go.bytecodealliance.org/cmd/wit-bindgen-go ================================================ FILE: interp/README.md ================================================ # Partial evaluation of initialization code in Go For several reasons related to code size and memory consumption (see below), it is best to try to evaluate as much initialization code at compile time as possible and only run unknown expressions (e.g. external calls) at runtime. This is in practice a partial evaluator of the `runtime.initAll` function, which calls each package initializer. This package is a rewrite of a previous partial evaluator that worked directly on LLVM IR and used the module and LLVM constants as intermediate values. This newer version instead uses a mostly Go intermediate form. It compiles functions and extracts relevant data first (compiler.go), then executes those functions (interpreter.go) in a memory space that can be rolled back per function (memory.go). This means that it is not necessary to scan functions to see whether they can be run at compile time, which was very error prone. Instead it just tries to execute everything and if it hits something it cannot interpret (such as a store to memory-mapped I/O) it rolls back the execution of that function and runs the function at runtime instead. All in all, this design provides several benefits: * Much better error handling. By being able to revert to runtime execution without the need for scanning functions, this version is able to automatically work around many bugs in the previous implementation. * More correct memory model. This is not inherent to the new design, but the new design also made the memory model easier to reason about. * Faster execution of initialization code. While it is not much faster for normal interpretation (maybe 25% or so) due to the compilation overhead, it should be a whole lot faster for loops as it doesn't have to call into LLVM (via CGo) for every operation. As mentioned, this partial evaluator comes in three parts: a compiler, an interpreter, and a memory manager. ## Compiler The main task of the compiler is that it extracts all necessary data from every instruction in a function so that when this instruction is interpreted, no additional CGo calls are necessary. This is not currently done for all instructions (`runtime.alloc` is a notable exception), but at least it does so for the vast majority of instructions. ## Interpreter The interpreter runs an instruction just like it would if it were executed 'for real'. The vast majority of instructions can be executed at compile time. As indicated above, some instructions need to be executed at runtime instead. ## Memory Memory is represented as objects (the `object` type) that contains data that will eventually be stored in a global and values (the `value` interface) that can be worked with while running the interpreter. Values therefore are only used locally and are always passed by value (just like most LLVM constants) while objects represent the backing storage (like LLVM globals). Some values are pointer values, and point to an object. Importantly, this partial evaluator can roll back the execution of a function. This is implemented by creating a new memory view per function activation, which makes sure that any change to a global (such as a store instruction) is stored in the memory view. It creates a copy of the object and stores that in the memory view to be modified. Once the function has executed successfully, all these modified objects are then copied into the parent function, up to the root function invocation which (on successful execution) writes the values back into the LLVM module. This way, function invocations can be rolled back without leaving a trace. Pointer values point to memory objects, but not to a particular memory object. Every memory object is given an index, and pointers use that index to look up the current active object for the pointer to load from or to copy when storing to it. Rolling back a function should roll back everything, including the few instructions emitted at runtime. This is done by treating instructions much like memory objects and removing the created instructions when necessary. ## Why is this necessary? A partial evaluator is hard to get right, so why go through all the trouble of writing one? The answer is that globals with initializers are much easier to optimize by LLVM than initialization code. Also, there are a few other benefits: * Dead globals are trivial to optimize away. * Constant globals are easier to detect. Remember that Go does not have global constants in the same sense as that C has them. Constants are useful because they can be propagated and provide some opportunities for other optimizations (like dead code elimination when branching on the contents of a global). * Constants are much more efficient on microcontrollers, as they can be allocated in flash instead of RAM. The Go SSA package does not create constant initializers for globals. Instead, it emits initialization functions, so if you write the following: ```go var foo = []byte{1, 2, 3, 4} ``` It would generate something like this: ```go var foo []byte func init() { foo = make([]byte, 4) foo[0] = 1 foo[1] = 2 foo[2] = 3 foo[3] = 4 } ``` This is of course hugely wasteful, it's much better to create `foo` as a global array instead of initializing it at runtime. For more details, see [this section of the documentation](https://tinygo.org/compiler-internals/differences-from-go/). ================================================ FILE: interp/compiler.go ================================================ package interp // This file compiles the LLVM IR to a form that's easy to efficiently // interpret. import ( "strings" "tinygo.org/x/go-llvm" ) // A function is a compiled LLVM function, which means that interpreting it // avoids most CGo calls necessary. This is done in a separate step so the // result can be cached. // Functions are in SSA form, just like the LLVM version if it. The first block // (blocks[0]) is the entry block. type function struct { llvmFn llvm.Value name string // precalculated llvmFn.Name() params []llvm.Value // precalculated llvmFn.Params() blocks []*basicBlock locals map[llvm.Value]int } // basicBlock represents a LLVM basic block and contains a slice of // instructions. The last instruction must be a terminator instruction. type basicBlock struct { phiNodes []instruction instructions []instruction } // instruction is a precompiled LLVM IR instruction. The operands can be either // an already known value (such as literalValue or pointerValue) but can also be // the special localValue, which means that the value is a function parameter or // is produced by another instruction in the function. In that case, the // interpreter will replace the operand with that local value. type instruction struct { opcode llvm.Opcode localIndex int operands []value llvmInst llvm.Value name string } // String returns a nice human-readable version of this instruction. func (inst *instruction) String() string { operands := make([]string, len(inst.operands)) for i, op := range inst.operands { operands[i] = op.String() } name := "" if int(inst.opcode) < len(instructionNameMap) { name = instructionNameMap[inst.opcode] } if name == "" { name = "" } return name + " " + strings.Join(operands, " ") } // compileFunction compiles a given LLVM function to an easier to interpret // version of the function. As far as possible, all operands are preprocessed so // that the interpreter doesn't have to call into LLVM. func (r *runner) compileFunction(llvmFn llvm.Value) *function { fn := &function{ llvmFn: llvmFn, name: llvmFn.Name(), params: llvmFn.Params(), locals: make(map[llvm.Value]int), } if llvmFn.IsDeclaration() { // Nothing to do. return fn } for i, param := range fn.params { fn.locals[param] = i } // Make a map of all the blocks, to quickly find the block number for a // given branch instruction. blockIndices := make(map[llvm.Value]int) for llvmBB := llvmFn.FirstBasicBlock(); !llvmBB.IsNil(); llvmBB = llvm.NextBasicBlock(llvmBB) { index := len(blockIndices) blockIndices[llvmBB.AsValue()] = index } // Compile every block. for llvmBB := llvmFn.FirstBasicBlock(); !llvmBB.IsNil(); llvmBB = llvm.NextBasicBlock(llvmBB) { bb := &basicBlock{} fn.blocks = append(fn.blocks, bb) // Compile every instruction in the block. for llvmInst := llvmBB.FirstInstruction(); !llvmInst.IsNil(); llvmInst = llvm.NextInstruction(llvmInst) { // Create instruction skeleton. opcode := llvmInst.InstructionOpcode() inst := instruction{ opcode: opcode, localIndex: len(fn.locals), llvmInst: llvmInst, } fn.locals[llvmInst] = len(fn.locals) // Add operands specific for this instruction. switch opcode { case llvm.Ret: // Return instruction, which can either be a `ret void` (no // return value) or return a value. numOperands := llvmInst.OperandsCount() if numOperands != 0 { inst.operands = []value{ r.getValue(llvmInst.Operand(0)), } } case llvm.Br: // Branch instruction. Can be either a conditional branch (with // 3 operands) or unconditional branch (with just one basic // block operand). numOperands := llvmInst.OperandsCount() switch numOperands { case 3: // Conditional jump to one of two blocks. Comparable to an // if/else in procedural languages. thenBB := llvmInst.Operand(2) elseBB := llvmInst.Operand(1) inst.operands = []value{ r.getValue(llvmInst.Operand(0)), literalValue{uint32(blockIndices[thenBB])}, literalValue{uint32(blockIndices[elseBB])}, } case 1: // Unconditional jump to a target basic block. Comparable to // a jump in C and Go. jumpBB := llvmInst.Operand(0) inst.operands = []value{ literalValue{uint32(blockIndices[jumpBB])}, } default: panic("unknown number of operands") } case llvm.Switch: // A switch is an array of (value, label) pairs, of which the // first one indicates the to-switch value and the default // label. numOperands := llvmInst.OperandsCount() for i := 0; i < numOperands; i += 2 { inst.operands = append(inst.operands, r.getValue(llvmInst.Operand(i))) inst.operands = append(inst.operands, literalValue{uint32(blockIndices[llvmInst.Operand(i+1)])}) } case llvm.PHI: inst.name = llvmInst.Name() incomingCount := inst.llvmInst.IncomingCount() for i := 0; i < incomingCount; i++ { incomingBB := inst.llvmInst.IncomingBlock(i) incomingValue := inst.llvmInst.IncomingValue(i) inst.operands = append(inst.operands, literalValue{uint32(blockIndices[incomingBB.AsValue()])}, r.getValue(incomingValue), ) } case llvm.Select: // Select is a special instruction that is much like a ternary // operator. It produces operand 1 or 2 based on the boolean // that is operand 0. inst.name = llvmInst.Name() inst.operands = []value{ r.getValue(llvmInst.Operand(0)), r.getValue(llvmInst.Operand(1)), r.getValue(llvmInst.Operand(2)), } case llvm.Call: // Call is a regular function call but could also be a runtime // intrinsic. Some runtime intrinsics are treated specially by // the interpreter, such as runtime.alloc. We don't // differentiate between them here because these calls may also // need to be run at runtime, in which case they should all be // created in the same way. llvmCalledValue := llvmInst.CalledValue() if !llvmCalledValue.IsAFunction().IsNil() { name := llvmCalledValue.Name() if name == "llvm.dbg.value" || strings.HasPrefix(name, "llvm.lifetime.") { // These intrinsics should not be interpreted, they are not // relevant to the execution of this function. continue } } inst.name = llvmInst.Name() numOperands := llvmInst.OperandsCount() inst.operands = append(inst.operands, r.getValue(llvmCalledValue)) for i := 0; i < numOperands-1; i++ { inst.operands = append(inst.operands, r.getValue(llvmInst.Operand(i))) } case llvm.Load: // Load instruction. The interpreter will load from the // appropriate memory view. // Also provide the memory size to be loaded, which is necessary // with a lack of type information. inst.name = llvmInst.Name() inst.operands = []value{ r.getValue(llvmInst.Operand(0)), literalValue{r.targetData.TypeAllocSize(llvmInst.Type())}, } case llvm.Store: // Store instruction. The interpreter will create a new object // in the memory view of the function invocation and store to // that, to make it possible to roll back this store. inst.operands = []value{ r.getValue(llvmInst.Operand(0)), r.getValue(llvmInst.Operand(1)), } case llvm.Alloca: // Alloca allocates stack space for local variables. numElements := r.getValue(inst.llvmInst.Operand(0)).(literalValue).value.(uint32) elementSize := r.targetData.TypeAllocSize(inst.llvmInst.AllocatedType()) inst.operands = []value{ literalValue{elementSize * uint64(numElements)}, } case llvm.GetElementPtr: // GetElementPtr does pointer arithmetic. inst.name = llvmInst.Name() ptr := llvmInst.Operand(0) n := llvmInst.OperandsCount() elementType := llvmInst.GEPSourceElementType() // gep: [source ptr, dest value size, pairs of indices...] inst.operands = []value{ r.getValue(ptr), r.getValue(llvmInst.Operand(1)), literalValue{r.targetData.TypeAllocSize(elementType)}, } for i := 2; i < n; i++ { operand := r.getValue(llvmInst.Operand(i)) switch elementType.TypeKind() { case llvm.StructTypeKind: index := operand.(literalValue).value.(uint32) elementOffset := r.targetData.ElementOffset(elementType, int(index)) // Encode operands in a special way. The elementOffset // is just the offset in bytes. The elementSize is a // negative number (when cast to a int64) by flipping // all the bits. This allows the interpreter to detect // this is a struct field and that it should not // multiply it with the elementOffset to get the offset. // It is important for the interpreter to know the // struct field index for when the GEP must be done at // runtime. inst.operands = append(inst.operands, literalValue{elementOffset}, literalValue{^uint64(index)}) elementType = elementType.StructElementTypes()[index] case llvm.ArrayTypeKind: elementType = elementType.ElementType() elementSize := r.targetData.TypeAllocSize(elementType) elementSizeOperand := literalValue{elementSize} // Add operand * elementSizeOperand bytes to the pointer. inst.operands = append(inst.operands, operand, elementSizeOperand) default: // This should be unreachable. panic("unknown type: " + elementType.String()) } } case llvm.BitCast, llvm.IntToPtr, llvm.PtrToInt: // Bitcasts are usually used to cast a pointer from one type to // another leaving the pointer itself intact. inst.name = llvmInst.Name() inst.operands = []value{ r.getValue(llvmInst.Operand(0)), } case llvm.ExtractValue: inst.name = llvmInst.Name() agg := llvmInst.Operand(0) var offset uint64 indexingType := agg.Type() for _, index := range inst.llvmInst.Indices() { switch indexingType.TypeKind() { case llvm.StructTypeKind: offset += r.targetData.ElementOffset(indexingType, int(index)) indexingType = indexingType.StructElementTypes()[index] case llvm.ArrayTypeKind: indexingType = indexingType.ElementType() elementSize := r.targetData.TypeAllocSize(indexingType) offset += elementSize * uint64(index) default: panic("unknown type kind") // unreachable } } size := r.targetData.TypeAllocSize(inst.llvmInst.Type()) // extractvalue [agg, byteOffset, byteSize] inst.operands = []value{ r.getValue(agg), literalValue{offset}, literalValue{size}, } case llvm.InsertValue: inst.name = llvmInst.Name() agg := llvmInst.Operand(0) var offset uint64 indexingType := agg.Type() for _, index := range inst.llvmInst.Indices() { switch indexingType.TypeKind() { case llvm.StructTypeKind: offset += r.targetData.ElementOffset(indexingType, int(index)) indexingType = indexingType.StructElementTypes()[index] case llvm.ArrayTypeKind: indexingType = indexingType.ElementType() elementSize := r.targetData.TypeAllocSize(indexingType) offset += elementSize * uint64(index) default: panic("unknown type kind") // unreachable } } // insertvalue [agg, elt, byteOffset] inst.operands = []value{ r.getValue(agg), r.getValue(llvmInst.Operand(1)), literalValue{offset}, } case llvm.ICmp: inst.name = llvmInst.Name() inst.operands = []value{ r.getValue(llvmInst.Operand(0)), r.getValue(llvmInst.Operand(1)), literalValue{uint8(llvmInst.IntPredicate())}, } case llvm.FCmp: inst.name = llvmInst.Name() inst.operands = []value{ r.getValue(llvmInst.Operand(0)), r.getValue(llvmInst.Operand(1)), literalValue{uint8(llvmInst.FloatPredicate())}, } case llvm.Add, llvm.Sub, llvm.Mul, llvm.UDiv, llvm.SDiv, llvm.URem, llvm.SRem, llvm.Shl, llvm.LShr, llvm.AShr, llvm.And, llvm.Or, llvm.Xor: // Integer binary operations. inst.name = llvmInst.Name() inst.operands = []value{ r.getValue(llvmInst.Operand(0)), r.getValue(llvmInst.Operand(1)), } case llvm.SExt, llvm.ZExt, llvm.Trunc: // Extend or shrink an integer size. // No sign extension going on so easy to do. // zext: [value, bitwidth] // trunc: [value, bitwidth] inst.name = llvmInst.Name() inst.operands = []value{ r.getValue(llvmInst.Operand(0)), literalValue{uint64(llvmInst.Type().IntTypeWidth())}, } case llvm.SIToFP, llvm.UIToFP: // Convert an integer to a floating point instruction. // opcode: [value, bitwidth] inst.name = llvmInst.Name() inst.operands = []value{ r.getValue(llvmInst.Operand(0)), literalValue{uint64(r.targetData.TypeAllocSize(llvmInst.Type()) * 8)}, } default: // Unknown instruction, which is already set in inst.opcode so // is detectable. // This error is handled when actually trying to interpret this // instruction (to not trigger on code that won't be executed). } if inst.opcode == llvm.PHI { // PHI nodes need to be treated specially, see the comment in // interpreter.go for an explanation. bb.phiNodes = append(bb.phiNodes, inst) } else { bb.instructions = append(bb.instructions, inst) } } } return fn } // instructionNameMap maps from instruction opcodes to instruction names. This // can be useful for debug logging. var instructionNameMap = [...]string{ llvm.Ret: "ret", llvm.Br: "br", llvm.Switch: "switch", llvm.IndirectBr: "indirectbr", llvm.Invoke: "invoke", llvm.Unreachable: "unreachable", // Standard Binary Operators llvm.Add: "add", llvm.FAdd: "fadd", llvm.Sub: "sub", llvm.FSub: "fsub", llvm.Mul: "mul", llvm.FMul: "fmul", llvm.UDiv: "udiv", llvm.SDiv: "sdiv", llvm.FDiv: "fdiv", llvm.URem: "urem", llvm.SRem: "srem", llvm.FRem: "frem", // Logical Operators llvm.Shl: "shl", llvm.LShr: "lshr", llvm.AShr: "ashr", llvm.And: "and", llvm.Or: "or", llvm.Xor: "xor", // Memory Operators llvm.Alloca: "alloca", llvm.Load: "load", llvm.Store: "store", llvm.GetElementPtr: "getelementptr", // Cast Operators llvm.Trunc: "trunc", llvm.ZExt: "zext", llvm.SExt: "sext", llvm.FPToUI: "fptoui", llvm.FPToSI: "fptosi", llvm.UIToFP: "uitofp", llvm.SIToFP: "sitofp", llvm.FPTrunc: "fptrunc", llvm.FPExt: "fpext", llvm.PtrToInt: "ptrtoint", llvm.IntToPtr: "inttoptr", llvm.BitCast: "bitcast", // Other Operators llvm.ICmp: "icmp", llvm.FCmp: "fcmp", llvm.PHI: "phi", llvm.Call: "call", llvm.Select: "select", llvm.VAArg: "vaarg", llvm.ExtractElement: "extractelement", llvm.InsertElement: "insertelement", llvm.ShuffleVector: "shufflevector", llvm.ExtractValue: "extractvalue", llvm.InsertValue: "insertvalue", } ================================================ FILE: interp/errors.go ================================================ package interp // This file provides useful types for errors encountered during IR evaluation. import ( "errors" "go/scanner" "go/token" "path/filepath" "tinygo.org/x/go-llvm" ) // These errors are expected during normal execution and can be recovered from // by running the affected function at runtime instead of compile time. var ( errIntegerAsPointer = errors.New("interp: trying to use an integer as a pointer (memory-mapped I/O?)") errUnsupportedInst = errors.New("interp: unsupported instruction") errUnsupportedRuntimeInst = errors.New("interp: unsupported instruction (to be emitted at runtime)") errMapAlreadyCreated = errors.New("interp: map already created") errLoopUnrolled = errors.New("interp: loop unrolled") ) // This is one of the errors that can be returned from toLLVMValue when the // passed type does not fit the data to serialize. It is recoverable by // serializing without a type (using rawValue.rawLLVMValue). var errInvalidPtrToIntSize = errors.New("interp: ptrtoint integer size does not equal pointer size") func isRecoverableError(err error) bool { return err == errIntegerAsPointer || err == errUnsupportedInst || err == errUnsupportedRuntimeInst || err == errMapAlreadyCreated || err == errLoopUnrolled } // ErrorLine is one line in a traceback. The position may be missing. type ErrorLine struct { Pos token.Position Inst string } // Error encapsulates compile-time interpretation errors with an associated // import path. The errors may not have a precise location attached. type Error struct { ImportPath string Inst string Pos token.Position Err error Traceback []ErrorLine } // Error returns the string of the first error in the list of errors. func (e *Error) Error() string { return e.Pos.String() + ": " + e.Err.Error() } // errorAt returns an error value for the currently interpreted package at the // location of the instruction. The location information may not be complete as // it depends on debug information in the IR. func (r *runner) errorAt(inst instruction, err error) *Error { pos := getPosition(inst.llvmInst) return &Error{ ImportPath: r.pkgName, Inst: inst.llvmInst.String(), Pos: pos, Err: err, Traceback: []ErrorLine{{pos, inst.llvmInst.String()}}, } } // errorAt returns an error value at the location of the instruction. // The location information may not be complete as it depends on debug // information in the IR. func errorAt(inst llvm.Value, msg string) scanner.Error { return scanner.Error{ Pos: getPosition(inst), Msg: msg, } } // getPosition returns the position information for the given instruction, as // far as it is available. func getPosition(inst llvm.Value) token.Position { if inst.IsAInstruction().IsNil() { return token.Position{} } loc := inst.InstructionDebugLoc() if loc.IsNil() { return token.Position{} } file := loc.LocationScope().ScopeFile() return token.Position{ Filename: filepath.Join(file.FileDirectory(), file.FileFilename()), Line: int(loc.LocationLine()), Column: int(loc.LocationColumn()), } } ================================================ FILE: interp/interp.go ================================================ // Package interp is a partial evaluator of code run at package init time. See // the README in this package for details. package interp import ( "encoding/binary" "fmt" "os" "strings" "time" "github.com/tinygo-org/tinygo/compiler/llvmutil" "tinygo.org/x/go-llvm" ) // Enable extra checks, which should be disabled by default. // This may help track down bugs by adding a few more sanity checks. const checks = true // runner contains all state related to one interp run. type runner struct { mod llvm.Module targetData llvm.TargetData builder llvm.Builder pointerSize uint32 // cached pointer size from the TargetData dataPtrType llvm.Type // often used type so created in advance uintptrType llvm.Type // equivalent to uintptr in Go maxAlign int // maximum alignment of an object, alignment of runtime.alloc() result byteOrder binary.ByteOrder // big-endian or little-endian debug bool // log debug messages pkgName string // package name of the currently executing package functionCache map[llvm.Value]*function // cache of compiled functions objects []object // slice of objects in memory globals map[llvm.Value]int // map from global to index in objects slice start time.Time timeout time.Duration callsExecuted uint64 } func newRunner(mod llvm.Module, timeout time.Duration, debug bool) *runner { r := runner{ mod: mod, targetData: llvm.NewTargetData(mod.DataLayout()), byteOrder: llvmutil.ByteOrder(mod.Target()), debug: debug, functionCache: make(map[llvm.Value]*function), objects: []object{{}}, globals: make(map[llvm.Value]int), start: time.Now(), timeout: timeout, } r.pointerSize = uint32(r.targetData.PointerSize()) r.dataPtrType = llvm.PointerType(mod.Context().Int8Type(), 0) r.uintptrType = mod.Context().IntType(r.targetData.PointerSize() * 8) r.maxAlign = r.targetData.PrefTypeAlignment(r.dataPtrType) // assume pointers are maximally aligned (this is not always the case) return &r } // Dispose deallocates all allocated LLVM resources. func (r *runner) dispose() { r.targetData.Dispose() r.targetData = llvm.TargetData{} } // Run evaluates runtime.initAll function as much as possible at compile time. // Set debug to true if it should print output while running. func Run(mod llvm.Module, timeout time.Duration, debug bool) error { r := newRunner(mod, timeout, debug) defer r.dispose() initAll := mod.NamedFunction("runtime.initAll") bb := initAll.EntryBasicBlock() // Create a builder, to insert instructions that could not be evaluated at // compile time. r.builder = mod.Context().NewBuilder() defer r.builder.Dispose() // Create a dummy alloca in the entry block that we can set the insert point // to. This is necessary because otherwise we might be removing the // instruction (init call) that we are removing after successful // interpretation. r.builder.SetInsertPointBefore(bb.FirstInstruction()) dummy := r.builder.CreateAlloca(r.mod.Context().Int8Type(), "dummy") r.builder.SetInsertPointBefore(dummy) defer dummy.EraseFromParentAsInstruction() // Get a list if init calls. A runtime.initAll might look something like this: // func initAll() { // unsafe.init() // machine.init() // runtime.init() // } // This function gets a list of these call instructions. var initCalls []llvm.Value for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) { if inst == dummy { continue } if !inst.IsAReturnInst().IsNil() { break // ret void } if inst.IsACallInst().IsNil() || inst.CalledValue().IsAFunction().IsNil() { return errorAt(inst, "interp: expected all instructions in "+initAll.Name()+" to be direct calls") } initCalls = append(initCalls, inst) } // Run initializers for each package. Once the package initializer is // finished, the call to the package initializer can be removed. for _, call := range initCalls { initName := call.CalledValue().Name() if !strings.HasSuffix(initName, ".init") { return errorAt(call, "interp: expected all instructions in "+initAll.Name()+" to be *.init() calls") } r.pkgName = initName[:len(initName)-len(".init")] fn := call.CalledValue() if r.debug { fmt.Fprintln(os.Stderr, "call:", fn.Name()) } _, mem, callErr := r.run(r.getFunction(fn), nil, nil, " ") call.EraseFromParentAsInstruction() if callErr != nil { if isRecoverableError(callErr.Err) { if r.debug { fmt.Fprintln(os.Stderr, "not interpreting", r.pkgName, "because of error:", callErr.Error()) } // Remove instructions that were created as part of interpreting // the package. mem.revert() // Create a call to the package initializer (which was // previously deleted). i8undef := llvm.Undef(r.dataPtrType) r.builder.CreateCall(fn.GlobalValueType(), fn, []llvm.Value{i8undef}, "") // Make sure that any globals touched by the package // initializer, won't be accessed by later package initializers. err := r.markExternalLoad(fn) if err != nil { return fmt.Errorf("failed to interpret package %s: %w", r.pkgName, err) } continue } return callErr } for index, obj := range mem.objects { r.objects[index] = obj } } r.pkgName = "" // Update all global variables in the LLVM module. mem := memoryView{r: r} for i, obj := range r.objects { if obj.llvmGlobal.IsNil() { continue } if obj.buffer == nil { continue } if obj.constant { continue // constant buffers can't have been modified } initializer, err := obj.buffer.toLLVMValue(obj.llvmGlobal.GlobalValueType(), &mem) if err == errInvalidPtrToIntSize { // This can happen when a previous interp run did not have the // correct LLVM type for a global and made something up. In that // case, some fields could be written out as a series of (null) // bytes even though they actually contain a pointer value. // As a fallback, use asRawValue to get something of the correct // memory layout. initializer, err := obj.buffer.asRawValue(r).rawLLVMValue(&mem) if err != nil { return err } initializerType := initializer.Type() newGlobal := llvm.AddGlobal(mod, initializerType, obj.llvmGlobal.Name()+".tmp") newGlobal.SetInitializer(initializer) newGlobal.SetLinkage(obj.llvmGlobal.Linkage()) newGlobal.SetAlignment(obj.llvmGlobal.Alignment()) // TODO: copy debug info, unnamed_addr, ... obj.llvmGlobal.ReplaceAllUsesWith(newGlobal) name := obj.llvmGlobal.Name() obj.llvmGlobal.EraseFromParentAsGlobal() newGlobal.SetName(name) // Update interp-internal references. delete(r.globals, obj.llvmGlobal) obj.llvmGlobal = newGlobal r.globals[newGlobal] = i r.objects[i] = obj continue } if err != nil { return err } if checks && initializer.Type() != obj.llvmGlobal.GlobalValueType() { panic("initializer type mismatch") } obj.llvmGlobal.SetInitializer(initializer) } return nil } // RunFunc evaluates a single package initializer at compile time. // Set debug to true if it should print output while running. func RunFunc(fn llvm.Value, timeout time.Duration, debug bool) error { // Create and initialize *runner object. mod := fn.GlobalParent() r := newRunner(mod, timeout, debug) defer r.dispose() initName := fn.Name() if !strings.HasSuffix(initName, ".init") { return errorAt(fn, "interp: unexpected function name (expected *.init)") } r.pkgName = initName[:len(initName)-len(".init")] // Create new function with the interp result. newFn := llvm.AddFunction(mod, fn.Name()+".tmp", fn.GlobalValueType()) newFn.SetLinkage(fn.Linkage()) newFn.SetVisibility(fn.Visibility()) entry := mod.Context().AddBasicBlock(newFn, "entry") // Create a builder, to insert instructions that could not be evaluated at // compile time. r.builder = mod.Context().NewBuilder() defer r.builder.Dispose() r.builder.SetInsertPointAtEnd(entry) // Copy debug information. subprogram := fn.Subprogram() if !subprogram.IsNil() { newFn.SetSubprogram(subprogram) r.builder.SetCurrentDebugLocation(subprogram.SubprogramLine(), 0, subprogram, llvm.Metadata{}) } // Run the initializer, filling the .init.tmp function. if r.debug { fmt.Fprintln(os.Stderr, "interp:", fn.Name()) } _, pkgMem, callErr := r.run(r.getFunction(fn), nil, nil, " ") if callErr != nil { if isRecoverableError(callErr.Err) { // Could not finish, but could recover from it. if r.debug { fmt.Fprintln(os.Stderr, "not interpreting", r.pkgName, "because of error:", callErr.Error()) } newFn.EraseFromParentAsFunction() return nil } return callErr } for index, obj := range pkgMem.objects { r.objects[index] = obj } // Update globals with values determined while running the initializer above. mem := memoryView{r: r} for _, obj := range r.objects { if obj.llvmGlobal.IsNil() { continue } if obj.buffer == nil { continue } if obj.constant { continue // constant, so can't have been modified } initializer, err := obj.buffer.toLLVMValue(obj.llvmGlobal.GlobalValueType(), &mem) if err != nil { return err } if checks && initializer.Type() != obj.llvmGlobal.GlobalValueType() { panic("initializer type mismatch") } obj.llvmGlobal.SetInitializer(initializer) } // Finalize: remove the old init function and replace it with the new // (.init.tmp) function. r.builder.CreateRetVoid() fnName := fn.Name() fn.ReplaceAllUsesWith(newFn) fn.EraseFromParentAsFunction() newFn.SetName(fnName) return nil } // getFunction returns the compiled version of the given LLVM function. It // compiles the function if necessary and caches the result. func (r *runner) getFunction(llvmFn llvm.Value) *function { if fn, ok := r.functionCache[llvmFn]; ok { return fn } fn := r.compileFunction(llvmFn) r.functionCache[llvmFn] = fn return fn } // markExternalLoad marks the given llvmValue as being loaded externally. This // is primarily used to mark package initializers that could not be run at // compile time. As an example, a package initialize might store to a global // variable. Another package initializer might read from the same global // variable. By marking this function as being run at runtime, that load // instruction will need to be run at runtime instead of at compile time. func (r *runner) markExternalLoad(llvmValue llvm.Value) error { mem := memoryView{r: r} err := mem.markExternalLoad(llvmValue) if err != nil { return err } for index, obj := range mem.objects { if obj.marked > r.objects[index].marked { r.objects[index].marked = obj.marked } } return nil } ================================================ FILE: interp/interp_test.go ================================================ package interp import ( "os" "strconv" "strings" "testing" "time" "tinygo.org/x/go-llvm" ) func TestInterp(t *testing.T) { llvmVersion, err := strconv.Atoi(strings.Split(llvm.Version, ".")[0]) if err != nil { // Note: this should never happen and if it does, it will always happen // for a particular build because llvm.Version is a constant. panic(err) } for _, name := range []string{ "basic", "phi", "slice-copy", "consteval", "interface", "revert", "alloc", } { name := name // make local to this closure if name == "slice-copy" && llvmVersion < 14 { continue } t.Run(name, func(t *testing.T) { t.Parallel() runTest(t, "testdata/"+name) }) } } func runTest(t *testing.T, pathPrefix string) { // Read the input IR. ctx := llvm.NewContext() defer ctx.Dispose() buf, err := llvm.NewMemoryBufferFromFile(pathPrefix + ".ll") os.Stat(pathPrefix + ".ll") // make sure this file is tracked by `go test` caching if err != nil { t.Fatalf("could not read file %s: %v", pathPrefix+".ll", err) } mod, err := ctx.ParseIR(buf) if err != nil { t.Fatalf("could not load module:\n%v", err) } defer mod.Dispose() // Perform the transform. err = Run(mod, 10*time.Minute, false) if err != nil { if err, match := err.(*Error); match { println(err.Error()) if len(err.Inst) != 0 { println(err.Inst) } if len(err.Traceback) > 0 { println("\ntraceback:") for _, line := range err.Traceback { println(line.Pos.String() + ":") println(line.Inst) } } } t.Fatal(err) } // To be sure, verify that the module is still valid. if llvm.VerifyModule(mod, llvm.PrintMessageAction) != nil { t.FailNow() } // Run some cleanup passes to get easy-to-read outputs. to := llvm.NewPassBuilderOptions() defer to.Dispose() mod.RunPasses("globalopt,dse,adce", llvm.TargetMachine{}, to) // Read the expected output IR. out, err := os.ReadFile(pathPrefix + ".out.ll") if err != nil { t.Fatalf("could not read output file %s: %v", pathPrefix+".out.ll", err) } // See whether the transform output matches with the expected output IR. expected := string(out) actual := mod.String() if !fuzzyEqualIR(expected, actual) { t.Logf("output does not match expected output:\n%s", actual) t.Fail() } } // fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly // equal. That means, only relevant lines are compared (excluding comments // etc.). func fuzzyEqualIR(s1, s2 string) bool { lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n")) lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n")) if len(lines1) != len(lines2) { return false } for i, line1 := range lines1 { line2 := lines2[i] if line1 != line2 { return false } } return true } // filterIrrelevantIRLines removes lines from the input slice of strings that // are not relevant in comparing IR. For example, empty lines and comments are // stripped out. func filterIrrelevantIRLines(lines []string) []string { var out []string for _, line := range lines { line = strings.TrimSpace(line) // drop '\r' on Windows if line == "" || line[0] == ';' { continue } if strings.HasPrefix(line, "source_filename = ") { continue } out = append(out, line) } return out } ================================================ FILE: interp/interpreter.go ================================================ package interp import ( "errors" "fmt" "math" "os" "strconv" "strings" "time" "tinygo.org/x/go-llvm" ) func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent string) (value, memoryView, *Error) { mem := memoryView{r: r, parent: parentMem} locals := make([]value, len(fn.locals)) r.callsExecuted++ // Parameters are considered a kind of local values. for i, param := range params { locals[i] = param } // Track what blocks have run instructions at runtime. // This is used to prevent unrolling. var runtimeBlocks map[int]struct{} // Start with the first basic block and the first instruction. // Branch instructions may modify both bb and instIndex when branching. bb := fn.blocks[0] currentBB := 0 lastBB := -1 // last basic block is undefined, only defined after a branch var operands []value startRTInsts := len(mem.instructions) for instIndex := 0; instIndex < len(bb.instructions); instIndex++ { if instIndex == 0 { // This is the start of a new basic block. if len(mem.instructions) != startRTInsts { if _, ok := runtimeBlocks[lastBB]; ok { // This loop has been unrolled. // Avoid doing this, as it can result in a large amount of extra machine code. // This currently uses the branch from the last block, as there is no available information to give a better location. lastBBInsts := fn.blocks[lastBB].instructions return nil, mem, r.errorAt(lastBBInsts[len(lastBBInsts)-1], errLoopUnrolled) } // Flag the last block as having run stuff at runtime. if runtimeBlocks == nil { runtimeBlocks = make(map[int]struct{}) } runtimeBlocks[lastBB] = struct{}{} // Reset the block-start runtime instructions counter. startRTInsts = len(mem.instructions) } // There may be PHI nodes that need to be resolved. Resolve all PHI // nodes before continuing with regular instructions. // PHI nodes need to be treated specially because they can have a // mutual dependency: // for.loop: // %a = phi i8 [ 1, %entry ], [ %b, %for.loop ] // %b = phi i8 [ 3, %entry ], [ %a, %for.loop ] // If these PHI nodes are processed like a regular instruction, %a // and %b are both 3 on the second iteration of the loop because %b // loads the value of %a from the second iteration, while it should // load the value from the previous iteration. The correct behavior // is that these two values swap each others place on each // iteration. var phiValues []value var phiIndices []int for _, inst := range bb.phiNodes { var result value for i := 0; i < len(inst.operands); i += 2 { if int(inst.operands[i].(literalValue).value.(uint32)) == lastBB { incoming := inst.operands[i+1] if local, ok := incoming.(localValue); ok { result = locals[fn.locals[local.value]] } else { result = incoming } break } } if r.debug { fmt.Fprintln(os.Stderr, indent+"phi", inst.operands, "->", result) } if result == nil { panic("could not find PHI input") } phiValues = append(phiValues, result) phiIndices = append(phiIndices, inst.localIndex) } for i, value := range phiValues { locals[phiIndices[i]] = value } } inst := bb.instructions[instIndex] operands = operands[:0] isRuntimeInst := false if inst.opcode != llvm.PHI { for _, v := range inst.operands { if v, ok := v.(localValue); ok { index, ok := fn.locals[v.value] if !ok { // This is a localValue that is not local to the // function. An example would be an inline assembly call // operand. isRuntimeInst = true break } localVal := locals[index] if localVal == nil { // Trying to read a function-local value before it is // set. return nil, mem, r.errorAt(inst, errors.New("interp: local not defined")) } else { operands = append(operands, localVal) if _, ok := localVal.(localValue); ok { // The function-local value is still just a // localValue (which can't be interpreted at compile // time). Not sure whether this ever happens in // practice. isRuntimeInst = true break } continue } } operands = append(operands, v) } } if isRuntimeInst { err := r.runAtRuntime(fn, inst, locals, &mem, indent) if err != nil { return nil, mem, err } continue } switch inst.opcode { case llvm.Ret: if time.Since(r.start) > r.timeout { // Running for more than the allowed timeout; This shouldn't happen, but it does. // See github.com/tinygo-org/tinygo/issues/2124 return nil, mem, r.errorAt(fn.blocks[0].instructions[0], fmt.Errorf("interp: running for more than %s, timing out (executed calls: %d)", r.timeout, r.callsExecuted)) } if len(operands) != 0 { if r.debug { fmt.Fprintln(os.Stderr, indent+"ret", operands[0]) } // Return instruction has a value to return. return operands[0], mem, nil } if r.debug { fmt.Fprintln(os.Stderr, indent+"ret") } // Return instruction doesn't return anything, it's just 'ret void'. return nil, mem, nil case llvm.Br: switch len(operands) { case 1: // Unconditional branch: [nextBB] lastBB = currentBB currentBB = int(operands[0].(literalValue).value.(uint32)) bb = fn.blocks[currentBB] instIndex = -1 // start at 0 the next cycle if r.debug { fmt.Fprintln(os.Stderr, indent+"br", operands, "->", currentBB) } case 3: // Conditional branch: [cond, thenBB, elseBB] lastBB = currentBB switch operands[0].Uint(r) { case 1: // true -> thenBB currentBB = int(operands[1].(literalValue).value.(uint32)) case 0: // false -> elseBB currentBB = int(operands[2].(literalValue).value.(uint32)) default: panic("bool should be 0 or 1") } if r.debug { fmt.Fprintln(os.Stderr, indent+"br", operands, "->", currentBB) } bb = fn.blocks[currentBB] instIndex = -1 // start at 0 the next cycle default: panic("unknown operands length") } case llvm.Switch: // Switch statement: [value, defaultLabel, case0, label0, case1, label1, ...] value := operands[0].Uint(r) targetLabel := operands[1].Uint(r) // default label // Do a lazy switch by iterating over all cases. for i := 2; i < len(operands); i += 2 { if value == operands[i].Uint(r) { targetLabel = operands[i+1].Uint(r) break } } lastBB = currentBB currentBB = int(targetLabel) bb = fn.blocks[currentBB] instIndex = -1 // start at 0 the next cycle if r.debug { fmt.Fprintln(os.Stderr, indent+"switch", operands, "->", currentBB) } case llvm.Select: // Select is much like a ternary operator: it picks a result from // the second and third operand based on the boolean first operand. var result value switch operands[0].Uint(r) { case 1: result = operands[1] case 0: result = operands[2] default: panic("boolean must be 0 or 1") } locals[inst.localIndex] = result if r.debug { fmt.Fprintln(os.Stderr, indent+"select", operands, "->", result) } case llvm.Call: // A call instruction can either be a regular call or a runtime intrinsic. fnPtr, err := operands[0].asPointer(r) if err != nil { return nil, mem, r.errorAt(inst, err) } callFn := r.getFunction(fnPtr.llvmValue(&mem)) switch { case callFn.name == "runtime.trackPointer": // Allocas and such are created as globals, so don't need a // runtime.trackPointer. // Unless the object is allocated at runtime for example, in // which case this call won't even get to this point but will // already be emitted in initAll. continue case strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet" || callFn.name == "runtime.hashmapInterfaceHash" || callFn.name == "os.runtime_args" || callFn.name == "internal/task.start" || callFn.name == "internal/task.Current" || callFn.name == "time.startTimer" || callFn.name == "time.stopTimer" || callFn.name == "time.resetTimer": // These functions should be run at runtime. Specifically: // * Print and panic functions are best emitted directly without // interpreting them, otherwise we get a ton of putchar (etc.) // calls. // * runtime.hashmapGet tries to access the map value directly. // This is not possible as the map value is treated as a special // kind of object in this package. // * os.runtime_args reads globals that are initialized outside // the view of the interp package so it always needs to be run // at runtime. // * internal/task.start, internal/task.Current: start and read shcheduler state, // which is modified elsewhere. // * Timer functions access runtime internal state which may // not be initialized. err := r.runAtRuntime(fn, inst, locals, &mem, indent) if err != nil { return nil, mem, err } case callFn.name == "internal/task.Pause": // Task scheduling isn't possible at compile time. return nil, mem, r.errorAt(inst, errUnsupportedRuntimeInst) case callFn.name == "runtime.nanotime" && r.pkgName == "time": // The time package contains a call to runtime.nanotime. // This appears to be to work around a limitation in Windows // Server 2008: // > Monotonic times are reported as offsets from startNano. // > We initialize startNano to runtimeNano() - 1 so that on systems where // > monotonic time resolution is fairly low (e.g. Windows 2008 // > which appears to have a default resolution of 15ms), // > we avoid ever reporting a monotonic time of 0. // > (Callers may want to use 0 as "time not set".) // Simply let runtime.nanotime return 0 in this case, which // should be fine and avoids a call to runtime.nanotime. It // means that monotonic time in the time package is counted from // time.Time{}.Sub(1), which should be fine. locals[inst.localIndex] = literalValue{uint64(0)} case callFn.name == "runtime.alloc": // Allocate heap memory. At compile time, this is instead done // by creating a global variable. // Get the requested memory size to be allocated. size := operands[1].Uint(r) // Get the object layout, if it is available. llvmLayoutType := r.getLLVMTypeFromLayout(operands[2]) // Get the alignment of the memory to be allocated. alignment := 0 // use default alignment if unset alignAttr := inst.llvmInst.GetCallSiteEnumAttribute(0, llvm.AttributeKindID("align")) if !alignAttr.IsNil() { alignment = int(alignAttr.GetEnumValue()) } // Create the object. alloc := object{ globalName: r.pkgName + "$alloc", align: alignment, llvmLayoutType: llvmLayoutType, buffer: newRawValue(uint32(size)), size: uint32(size), } index := len(r.objects) r.objects = append(r.objects, alloc) // And create a pointer to this object, for working with it (so // that stores to it copy it, etc). ptr := newPointerValue(r, index, 0) if r.debug { fmt.Fprintln(os.Stderr, indent+"runtime.alloc:", size, "->", ptr) } locals[inst.localIndex] = ptr case callFn.name == "runtime.sliceCopy": // sliceCopy implements the built-in copy function for slices. // It is implemented here so that it can be used even if the // runtime implementation is not available. Doing it this way // may also be faster. // Code: // func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr) int { // n := srcLen // if n > dstLen { // n = dstLen // } // memmove(dst, src, n*elemSize) // return int(n) // } dstLen := operands[3].Uint(r) srcLen := operands[4].Uint(r) elemSize := operands[5].Uint(r) n := srcLen if n > dstLen { n = dstLen } if r.debug { fmt.Fprintln(os.Stderr, indent+"copy:", operands[1], operands[2], n) } if n != 0 { // Only try to copy bytes when there are any bytes to copy. // This is not just an optimization. If one of the slices // (or both) are nil, the asPointer method call will fail // even though copying a nil slice is allowed. dst, err := operands[1].asPointer(r) if err != nil { return nil, mem, r.errorAt(inst, err) } src, err := operands[2].asPointer(r) if err != nil { return nil, mem, r.errorAt(inst, err) } if mem.hasExternalStore(src) || mem.hasExternalLoadOrStore(dst) { // These are the same checks as there are on llvm.Load // and llvm.Store in the interpreter. Copying is // essentially loading from the source array and storing // to the destination array, hence why we need to do the // same checks here. // This fixes the following bug: // https://github.com/tinygo-org/tinygo/issues/3890 err := r.runAtRuntime(fn, inst, locals, &mem, indent) if err != nil { return nil, mem, err } continue } nBytes := uint32(n * elemSize) srcObj := mem.get(src.index()) dstObj := mem.getWritable(dst.index()) if srcObj.buffer == nil || dstObj.buffer == nil { // If the buffer is nil, it means the slice is external. // This can happen for example when copying data out of // a //go:embed slice, which is not available at interp // time. // See: https://github.com/tinygo-org/tinygo/issues/4895 err := r.runAtRuntime(fn, inst, locals, &mem, indent) if err != nil { return nil, mem, err } continue } dstBuf := dstObj.buffer.asRawValue(r) srcBuf := srcObj.buffer.asRawValue(r) copy(dstBuf.buf[dst.offset():dst.offset()+nBytes], srcBuf.buf[src.offset():]) dstObj.buffer = dstBuf mem.put(dst.index(), dstObj) } locals[inst.localIndex] = makeLiteralInt(n, inst.llvmInst.Type().IntTypeWidth()) case strings.HasPrefix(callFn.name, "llvm.memcpy.p0") || strings.HasPrefix(callFn.name, "llvm.memmove.p0"): // Copy a block of memory from one pointer to another. dst, err := operands[1].asPointer(r) if err != nil { return nil, mem, r.errorAt(inst, err) } src, err := operands[2].asPointer(r) if err != nil { return nil, mem, r.errorAt(inst, err) } nBytes := uint32(operands[3].Uint(r)) dstObj := mem.getWritable(dst.index()) dstBuf := dstObj.buffer.asRawValue(r) if mem.get(src.index()).buffer == nil { // Looks like the source buffer is not defined. // This can happen with //extern or //go:embed. return nil, mem, r.errorAt(inst, errUnsupportedRuntimeInst) } srcBuf := mem.get(src.index()).buffer.asRawValue(r) copy(dstBuf.buf[dst.offset():dst.offset()+nBytes], srcBuf.buf[src.offset():]) dstObj.buffer = dstBuf mem.put(dst.index(), dstObj) case callFn.name == "runtime.typeAssert": // This function must be implemented manually as it is normally // implemented by the interface lowering pass. if r.debug { fmt.Fprintln(os.Stderr, indent+"typeassert:", operands[1:]) } assertedType, err := operands[2].toLLVMValue(inst.llvmInst.Operand(1).Type(), &mem) if err != nil { return nil, mem, r.errorAt(inst, err) } actualType, err := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem) if err != nil { return nil, mem, r.errorAt(inst, err) } if !actualType.IsAConstantInt().IsNil() && actualType.ZExtValue() == 0 { locals[inst.localIndex] = literalValue{uint8(0)} break } // Strip pointer casts (bitcast, getelementptr). for !actualType.IsAConstantExpr().IsNil() { opcode := actualType.Opcode() if opcode != llvm.GetElementPtr && opcode != llvm.BitCast { break } actualType = actualType.Operand(0) } if strings.TrimPrefix(actualType.Name(), "reflect/types.type:") == strings.TrimPrefix(assertedType.Name(), "reflect/types.typeid:") { locals[inst.localIndex] = literalValue{uint8(1)} } else { locals[inst.localIndex] = literalValue{uint8(0)} } case callFn.name == "__tinygo_interp_raise_test_error": // Special function that will trigger an error. // This is used to test error reporting. return nil, mem, r.errorAt(inst, errors.New("test error")) case strings.HasSuffix(callFn.name, ".$typeassert"): if r.debug { fmt.Fprintln(os.Stderr, indent+"interface assert:", operands[1:]) } // Load various values for the interface implements check below. typecodePtr, err := operands[1].asPointer(r) if err != nil { return nil, mem, r.errorAt(inst, err) } // typecodePtr always point to the numMethod field in the type // description struct. The methodSet, when present, comes right // before the numMethod field (the compiler doesn't generate // method sets for concrete types without methods). // Considering that the compiler doesn't emit interface type // asserts for interfaces with no methods (as the always succeed) // then if the offset is zero, this assert must always fail. if typecodePtr.offset() == 0 { locals[inst.localIndex] = literalValue{uint8(0)} break } typecodePtrOffset, err := typecodePtr.addOffset(-int64(r.pointerSize)) if err != nil { return nil, mem, r.errorAt(inst, err) } methodSetPtr, err := mem.load(typecodePtrOffset, r.pointerSize).asPointer(r) if err != nil { return nil, mem, r.errorAt(inst, err) } methodSet := mem.get(methodSetPtr.index()).llvmGlobal.Initializer() numMethods := int(r.builder.CreateExtractValue(methodSet, 0, "").ZExtValue()) llvmFn := inst.llvmInst.CalledValue() methodSetAttr := llvmFn.GetStringAttributeAtIndex(-1, "tinygo-methods") methodSetString := methodSetAttr.GetStringValue() // Make a set of all the methods on the concrete type, for // easier checking in the next step. concreteTypeMethods := map[string]struct{}{} for i := 0; i < numMethods; i++ { methodInfo := r.builder.CreateExtractValue(methodSet, 1, "") name := r.builder.CreateExtractValue(methodInfo, i, "").Name() concreteTypeMethods[name] = struct{}{} } // Check whether all interface methods are also in the list // of defined methods calculated above. This is the interface // assert itself. assertOk := uint8(1) // i1 true for _, name := range strings.Split(methodSetString, "; ") { if _, ok := concreteTypeMethods[name]; !ok { // There is a method on the interface that is not // implemented by the type. The assertion will fail. assertOk = 0 // i1 false break } } // If assertOk is still 1, the assertion succeeded. locals[inst.localIndex] = literalValue{assertOk} case strings.HasSuffix(callFn.name, "$invoke"): // This thunk is the interface method dispatcher: it is called // with all regular parameters and a type code. It will then // call the concrete method for it. if r.debug { fmt.Fprintln(os.Stderr, indent+"invoke method:", operands[1:]) } // Load the type code and method set of the interface value. typecodePtr, err := operands[len(operands)-2].asPointer(r) if err != nil { return nil, mem, r.errorAt(inst, err) } typecodePtrOffset, err := typecodePtr.addOffset(-int64(r.pointerSize)) if err != nil { return nil, mem, r.errorAt(inst, err) } methodSetPtr, err := mem.load(typecodePtrOffset, r.pointerSize).asPointer(r) if err != nil { return nil, mem, r.errorAt(inst, err) } methodSet := mem.get(methodSetPtr.index()).llvmGlobal.Initializer() // We don't need to load the interface method set. // Load the signature of the to-be-called function. llvmFn := inst.llvmInst.CalledValue() invokeAttr := llvmFn.GetStringAttributeAtIndex(-1, "tinygo-invoke") invokeName := invokeAttr.GetStringValue() signature := r.mod.NamedGlobal(invokeName) // Iterate through all methods, looking for the one method that // should be returned. numMethods := int(r.builder.CreateExtractValue(methodSet, 0, "").ZExtValue()) var method llvm.Value for i := 0; i < numMethods; i++ { methodSignatureAgg := r.builder.CreateExtractValue(methodSet, 1, "") methodSignature := r.builder.CreateExtractValue(methodSignatureAgg, i, "") if methodSignature == signature { methodAgg := r.builder.CreateExtractValue(methodSet, 2, "") method = r.builder.CreateExtractValue(methodAgg, i, "") } } if method.IsNil() { return nil, mem, r.errorAt(inst, errors.New("could not find method: "+invokeName)) } // Change the to-be-called function to the underlying method to // be called and fall through to the default case. callFn = r.getFunction(method) fallthrough default: if len(callFn.blocks) == 0 { // Call to a function declaration without a definition // available. err := r.runAtRuntime(fn, inst, locals, &mem, indent) if err != nil { return nil, mem, err } continue } // Call a function with a definition available. Run it as usual, // possibly trying to recover from it if it failed to execute. if r.debug { argStrings := make([]string, len(operands)-1) for i, v := range operands[1:] { argStrings[i] = v.String() } fmt.Fprintln(os.Stderr, indent+"call:", callFn.name+"("+strings.Join(argStrings, ", ")+")") } retval, callMem, callErr := r.run(callFn, operands[1:], &mem, indent+" ") if callErr != nil { if isRecoverableError(callErr.Err) { // This error can be recovered by doing the call at // runtime instead of at compile time. But we need to // revert any changes made by the call first. if r.debug { fmt.Fprintln(os.Stderr, indent+"!! revert because of error:", callErr.Error()) } callMem.revert() err := r.runAtRuntime(fn, inst, locals, &mem, indent) if err != nil { return nil, mem, err } continue } // Add to the traceback, so that error handling code can see // how this function got called. callErr.Traceback = append(callErr.Traceback, ErrorLine{ Pos: getPosition(inst.llvmInst), Inst: inst.llvmInst.String(), }) return nil, mem, callErr } locals[inst.localIndex] = retval mem.extend(callMem) } case llvm.Load: // Load instruction, loading some data from the topmost memory view. ptr, err := operands[0].asPointer(r) if err != nil { return nil, mem, r.errorAt(inst, err) } size := operands[1].(literalValue).value.(uint64) if inst.llvmInst.IsVolatile() || inst.llvmInst.Ordering() != llvm.AtomicOrderingNotAtomic || mem.hasExternalStore(ptr) { // If there could be an external store (for example, because a // pointer to the object was passed to a function that could not // be interpreted at compile time) then the load must be done at // runtime. err := r.runAtRuntime(fn, inst, locals, &mem, indent) if err != nil { return nil, mem, err } continue } result := mem.load(ptr, uint32(size)) if result == nil { err := r.runAtRuntime(fn, inst, locals, &mem, indent) if err != nil { return nil, mem, err } continue } if r.debug { fmt.Fprintln(os.Stderr, indent+"load:", ptr, "->", result) } locals[inst.localIndex] = result case llvm.Store: // Store instruction. Create a new object in the memory view and // store to that, to make it possible to roll back this store. ptr, err := operands[1].asPointer(r) if err != nil { return nil, mem, r.errorAt(inst, err) } if inst.llvmInst.IsVolatile() || inst.llvmInst.Ordering() != llvm.AtomicOrderingNotAtomic || mem.hasExternalLoadOrStore(ptr) { err := r.runAtRuntime(fn, inst, locals, &mem, indent) if err != nil { return nil, mem, err } continue } val := operands[0] if r.debug { fmt.Fprintln(os.Stderr, indent+"store:", val, ptr) } ok := mem.store(val, ptr) if !ok { // Could not store the value, do it at runtime. err := r.runAtRuntime(fn, inst, locals, &mem, indent) if err != nil { return nil, mem, err } } case llvm.Alloca: // Alloca normally allocates some stack memory. In the interpreter, // it allocates a global instead. // This can likely be optimized, as all it really needs is an alloca // in the initAll function and creating a global is wasteful for // this purpose. // Create the new object. size := operands[0].(literalValue).value.(uint64) alloca := object{ llvmType: inst.llvmInst.AllocatedType(), globalName: r.pkgName + "$alloca", buffer: newRawValue(uint32(size)), size: uint32(size), align: inst.llvmInst.Alignment(), } index := len(r.objects) r.objects = append(r.objects, alloca) // Create a pointer to this object (an alloca produces a pointer). ptr := newPointerValue(r, index, 0) if r.debug { fmt.Fprintln(os.Stderr, indent+"alloca:", operands, "->", ptr) } locals[inst.localIndex] = ptr case llvm.GetElementPtr: // GetElementPtr does pointer arithmetic, changing the offset of the // pointer into the underlying object. var offset int64 for i := 1; i < len(operands); i += 2 { index := operands[i].Int(r) elementSize := operands[i+1].Int(r) if elementSize < 0 { // This is a struct field. offset += index } else { // This is a normal GEP, probably an array index. offset += elementSize * index } } ptr, err := operands[0].asPointer(r) if err != nil { if err != errIntegerAsPointer { return nil, mem, r.errorAt(inst, err) } // GEP on fixed pointer value (for example, memory-mapped I/O). ptrValue := operands[0].Uint(r) + uint64(offset) locals[inst.localIndex] = makeLiteralInt(ptrValue, int(operands[0].len(r)*8)) continue } ptr, err = ptr.addOffset(int64(offset)) if err != nil { return nil, mem, r.errorAt(inst, err) } locals[inst.localIndex] = ptr if r.debug { fmt.Fprintln(os.Stderr, indent+"gep:", operands, "->", ptr) } case llvm.BitCast, llvm.IntToPtr, llvm.PtrToInt: // Various bitcast-like instructions that all keep the same bits // while changing the LLVM type. // Because interp doesn't preserve the type, these operations are // identity operations. if r.debug { fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", operands[0]) } locals[inst.localIndex] = operands[0] case llvm.ExtractValue: agg := operands[0].asRawValue(r) offset := operands[1].(literalValue).value.(uint64) size := operands[2].(literalValue).value.(uint64) elt := rawValue{ buf: agg.buf[offset : offset+size], } if r.debug { fmt.Fprintln(os.Stderr, indent+"extractvalue:", operands, "->", elt) } locals[inst.localIndex] = elt case llvm.InsertValue: agg := operands[0].asRawValue(r) elt := operands[1].asRawValue(r) offset := int(operands[2].(literalValue).value.(uint64)) newagg := newRawValue(uint32(len(agg.buf))) copy(newagg.buf, agg.buf) copy(newagg.buf[offset:], elt.buf) if r.debug { fmt.Fprintln(os.Stderr, indent+"insertvalue:", operands, "->", newagg) } locals[inst.localIndex] = newagg case llvm.ICmp: predicate := llvm.IntPredicate(operands[2].(literalValue).value.(uint8)) lhs := operands[0] rhs := operands[1] result := r.interpretICmp(lhs, rhs, predicate) if result { locals[inst.localIndex] = literalValue{uint8(1)} } else { locals[inst.localIndex] = literalValue{uint8(0)} } if r.debug { fmt.Fprintln(os.Stderr, indent+"icmp:", operands[0], intPredicateString(predicate), operands[1], "->", result) } case llvm.FCmp: predicate := llvm.FloatPredicate(operands[2].(literalValue).value.(uint8)) var result bool var lhs, rhs float64 switch operands[0].len(r) { case 8: lhs = math.Float64frombits(operands[0].Uint(r)) rhs = math.Float64frombits(operands[1].Uint(r)) case 4: lhs = float64(math.Float32frombits(uint32(operands[0].Uint(r)))) rhs = float64(math.Float32frombits(uint32(operands[1].Uint(r)))) default: panic("unknown float type") } switch predicate { case llvm.FloatOEQ: result = lhs == rhs case llvm.FloatUNE: result = lhs != rhs case llvm.FloatOGT: result = lhs > rhs case llvm.FloatOGE: result = lhs >= rhs case llvm.FloatOLT: result = lhs < rhs case llvm.FloatOLE: result = lhs <= rhs default: return nil, mem, r.errorAt(inst, errors.New("interp: unsupported fcmp")) } if result { locals[inst.localIndex] = literalValue{uint8(1)} } else { locals[inst.localIndex] = literalValue{uint8(0)} } if r.debug { fmt.Fprintln(os.Stderr, indent+"fcmp:", operands[0], predicate, operands[1], "->", result) } case llvm.Add, llvm.Sub, llvm.Mul, llvm.UDiv, llvm.SDiv, llvm.URem, llvm.SRem, llvm.Shl, llvm.LShr, llvm.AShr, llvm.And, llvm.Or, llvm.Xor: // Integer binary operations. lhs := operands[0] rhs := operands[1] lhsPtr, err := lhs.asPointer(r) if err == nil { // The lhs is a pointer. This sometimes happens for particular // pointer tricks. if inst.opcode == llvm.Add { // This likely means this is part of a // unsafe.Pointer(uintptr(ptr) + offset) pattern. lhsPtr, err = lhsPtr.addOffset(int64(rhs.Uint(r))) if err != nil { return nil, mem, r.errorAt(inst, err) } locals[inst.localIndex] = lhsPtr } else if inst.opcode == llvm.Xor && rhs.Uint(r) == 0 { // Special workaround for strings.noescape, see // src/strings/builder.go in the Go source tree. This is // the identity operator, so we can return the input. locals[inst.localIndex] = lhs } else if inst.opcode == llvm.And && rhs.Uint(r) < 8 { // This is probably part of a pattern to get the lower bits // of a pointer for pointer tagging, like this: // uintptr(unsafe.Pointer(t)) & 0b11 // We can actually support this easily by ANDing with the // pointer offset. result := uint64(lhsPtr.offset()) & rhs.Uint(r) locals[inst.localIndex] = makeLiteralInt(result, int(lhs.len(r)*8)) } else { // Catch-all for weird operations that should just be done // at runtime. err := r.runAtRuntime(fn, inst, locals, &mem, indent) if err != nil { return nil, mem, err } } continue } var result uint64 switch inst.opcode { case llvm.Add: result = lhs.Uint(r) + rhs.Uint(r) case llvm.Sub: result = lhs.Uint(r) - rhs.Uint(r) case llvm.Mul: result = lhs.Uint(r) * rhs.Uint(r) case llvm.UDiv: result = lhs.Uint(r) / rhs.Uint(r) case llvm.SDiv: result = uint64(lhs.Int(r) / rhs.Int(r)) case llvm.URem: result = lhs.Uint(r) % rhs.Uint(r) case llvm.SRem: result = uint64(lhs.Int(r) % rhs.Int(r)) case llvm.Shl: result = lhs.Uint(r) << rhs.Uint(r) case llvm.LShr: result = lhs.Uint(r) >> rhs.Uint(r) case llvm.AShr: result = uint64(lhs.Int(r) >> rhs.Uint(r)) case llvm.And: result = lhs.Uint(r) & rhs.Uint(r) case llvm.Or: result = lhs.Uint(r) | rhs.Uint(r) case llvm.Xor: result = lhs.Uint(r) ^ rhs.Uint(r) default: panic("unreachable") } locals[inst.localIndex] = makeLiteralInt(result, int(lhs.len(r)*8)) if r.debug { fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", lhs, rhs, "->", result) } case llvm.SExt, llvm.ZExt, llvm.Trunc: // Change the size of an integer to a larger or smaller bit width. // We make use of the fact that the Uint() function already // zero-extends the value and that Int() already sign-extends the // value, so we only need to truncate it to the appropriate bit // width. This means we can implement sext, zext and trunc in the // same way, by first {zero,sign}extending all the way up to uint64 // and then truncating it as necessary. var value uint64 if inst.opcode == llvm.SExt { value = uint64(operands[0].Int(r)) } else { value = operands[0].Uint(r) } bitwidth := operands[1].Uint(r) if r.debug { fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", value, bitwidth) } locals[inst.localIndex] = makeLiteralInt(value, int(bitwidth)) case llvm.SIToFP, llvm.UIToFP: var value float64 switch inst.opcode { case llvm.SIToFP: value = float64(operands[0].Int(r)) case llvm.UIToFP: value = float64(operands[0].Uint(r)) } bitwidth := operands[1].Uint(r) if r.debug { fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", value, bitwidth) } switch bitwidth { case 64: locals[inst.localIndex] = literalValue{math.Float64bits(value)} case 32: locals[inst.localIndex] = literalValue{math.Float32bits(float32(value))} default: panic("unknown integer size in sitofp/uitofp") } default: if r.debug { fmt.Fprintln(os.Stderr, indent+inst.String()) } return nil, mem, r.errorAt(inst, errUnsupportedInst) } } return nil, mem, r.errorAt(bb.instructions[len(bb.instructions)-1], errors.New("interp: reached end of basic block without terminator")) } // Interpret an icmp instruction. Doesn't have side effects, only returns the // output of the comparison. func (r *runner) interpretICmp(lhs, rhs value, predicate llvm.IntPredicate) bool { switch predicate { case llvm.IntEQ, llvm.IntNE: var result bool lhsPointer, lhsErr := lhs.asPointer(r) rhsPointer, rhsErr := rhs.asPointer(r) if (lhsErr == nil) != (rhsErr == nil) { // Fast path: only one is a pointer, so they can't be equal. result = false } else if lhsErr == nil { // Both must be nil, so both are pointers. // Compare them directly. result = lhsPointer.equal(rhsPointer) } else { // Fall back to generic comparison. result = lhs.asRawValue(r).equal(rhs.asRawValue(r)) } if predicate == llvm.IntNE { result = !result } return result case llvm.IntUGT: return lhs.Uint(r) > rhs.Uint(r) case llvm.IntUGE: return lhs.Uint(r) >= rhs.Uint(r) case llvm.IntULT: return lhs.Uint(r) < rhs.Uint(r) case llvm.IntULE: return lhs.Uint(r) <= rhs.Uint(r) case llvm.IntSGT: return lhs.Int(r) > rhs.Int(r) case llvm.IntSGE: return lhs.Int(r) >= rhs.Int(r) case llvm.IntSLT: return lhs.Int(r) < rhs.Int(r) case llvm.IntSLE: return lhs.Int(r) <= rhs.Int(r) default: // _should_ be unreachable, until LLVM adds new icmp operands (unlikely) panic("interp: unsupported icmp") } } func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, mem *memoryView, indent string) *Error { numOperands := inst.llvmInst.OperandsCount() operands := make([]llvm.Value, numOperands) for i := 0; i < numOperands; i++ { operand := inst.llvmInst.Operand(i) if !operand.IsAInstruction().IsNil() || !operand.IsAArgument().IsNil() { var err error operand, err = locals[fn.locals[operand]].toLLVMValue(operand.Type(), mem) if err != nil { return r.errorAt(inst, err) } } operands[i] = operand } if r.debug { fmt.Fprintln(os.Stderr, indent+inst.String()) } var result llvm.Value switch inst.opcode { case llvm.Call: llvmFn := operands[len(operands)-1] args := operands[:len(operands)-1] for _, op := range operands { if op.Type().TypeKind() == llvm.PointerTypeKind { err := mem.markExternalStore(op) if err != nil { return r.errorAt(inst, err) } } } result = r.builder.CreateCall(inst.llvmInst.CalledFunctionType(), llvmFn, args, inst.name) case llvm.Load: err := mem.markExternalLoad(operands[0]) if err != nil { return r.errorAt(inst, err) } result = r.builder.CreateLoad(inst.llvmInst.Type(), operands[0], inst.name) if inst.llvmInst.IsVolatile() { result.SetVolatile(true) } if ordering := inst.llvmInst.Ordering(); ordering != llvm.AtomicOrderingNotAtomic { result.SetOrdering(ordering) } case llvm.Store: err := mem.markExternalStore(operands[1]) if err != nil { return r.errorAt(inst, err) } result = r.builder.CreateStore(operands[0], operands[1]) if inst.llvmInst.IsVolatile() { result.SetVolatile(true) } if ordering := inst.llvmInst.Ordering(); ordering != llvm.AtomicOrderingNotAtomic { result.SetOrdering(ordering) } case llvm.BitCast: result = r.builder.CreateBitCast(operands[0], inst.llvmInst.Type(), inst.name) case llvm.ExtractValue: indices := inst.llvmInst.Indices() // Note: the Go LLVM API doesn't support multiple indices, so simulate // this operation with some extra extractvalue instructions. Hopefully // this is optimized to a single instruction. agg := operands[0] for i := 0; i < len(indices)-1; i++ { agg = r.builder.CreateExtractValue(agg, int(indices[i]), inst.name+".agg") mem.instructions = append(mem.instructions, agg) } result = r.builder.CreateExtractValue(agg, int(indices[len(indices)-1]), inst.name) case llvm.InsertValue: indices := inst.llvmInst.Indices() // Similar to extractvalue, we're working around a limitation in the Go // LLVM API here by splitting the insertvalue into multiple instructions // if there is more than one operand. agg := operands[0] aggregates := []llvm.Value{agg} for i := 0; i < len(indices)-1; i++ { agg = r.builder.CreateExtractValue(agg, int(indices[i]), inst.name+".agg"+strconv.Itoa(i)) aggregates = append(aggregates, agg) mem.instructions = append(mem.instructions, agg) } result = operands[1] for i := len(indices) - 1; i >= 0; i-- { agg := aggregates[i] result = r.builder.CreateInsertValue(agg, result, int(indices[i]), inst.name+".insertvalue"+strconv.Itoa(i)) if i != 0 { // don't add last result to mem.instructions as it will be done at the end already mem.instructions = append(mem.instructions, result) } } case llvm.Add: result = r.builder.CreateAdd(operands[0], operands[1], inst.name) case llvm.Sub: result = r.builder.CreateSub(operands[0], operands[1], inst.name) case llvm.Mul: result = r.builder.CreateMul(operands[0], operands[1], inst.name) case llvm.UDiv: result = r.builder.CreateUDiv(operands[0], operands[1], inst.name) case llvm.SDiv: result = r.builder.CreateSDiv(operands[0], operands[1], inst.name) case llvm.URem: result = r.builder.CreateURem(operands[0], operands[1], inst.name) case llvm.SRem: result = r.builder.CreateSRem(operands[0], operands[1], inst.name) case llvm.ZExt: result = r.builder.CreateZExt(operands[0], inst.llvmInst.Type(), inst.name) default: return r.errorAt(inst, errUnsupportedRuntimeInst) } locals[inst.localIndex] = localValue{result} mem.instructions = append(mem.instructions, result) return nil } func intPredicateString(predicate llvm.IntPredicate) string { switch predicate { case llvm.IntEQ: return "eq" case llvm.IntNE: return "ne" case llvm.IntUGT: return "ugt" case llvm.IntUGE: return "uge" case llvm.IntULT: return "ult" case llvm.IntULE: return "ule" case llvm.IntSGT: return "sgt" case llvm.IntSGE: return "sge" case llvm.IntSLT: return "slt" case llvm.IntSLE: return "sle" default: return "cmp?" } } ================================================ FILE: interp/memory.go ================================================ package interp // This file implements memory as used by interp in a reversible way. // Each new function call creates a new layer which is merged in the parent on // successful return and is thrown away when the function couldn't complete (in // which case the function call is done at runtime). // Memory is not typed, except that there is a difference between pointer and // non-pointer data. A pointer always points to an object. This implies: // * Nil pointers are zero, and are not considered a pointer. // * Pointers for memory-mapped I/O point to numeric pointer values, and are // thus not considered pointers but regular values. Dereferencing them cannot be // done in interp and results in a revert. // // Right now the memory is assumed to be little endian. This will need an update // for big endian architectures, if TinyGo ever adds support for one. import ( "encoding/binary" "errors" "fmt" "math" "math/big" "strconv" "strings" "tinygo.org/x/go-llvm" ) // An object is a memory buffer that may be an already existing global or a // global created with runtime.alloc or the alloca instruction. If llvmGlobal is // set, that's the global for this object, otherwise it needs to be created (if // it is still reachable when the package initializer returns). The // llvmLayoutType is not necessarily a complete type: it may need to be // repeated (for example, for a slice value). // // Objects are copied in a memory view when they are stored to, to provide the // ability to roll back interpreting a function. type object struct { llvmGlobal llvm.Value llvmType llvm.Type // must match llvmGlobal.GlobalValueType() if both are set, may be unset if llvmGlobal is set llvmLayoutType llvm.Type // LLVM type based on runtime.alloc layout parameter, if available globalName string // name, if not yet created (not guaranteed to be the final name) buffer value // buffer with value as given by interp, nil if external size uint32 // must match buffer.len(), if available align int // alignment of the object (may be 0 if unknown) constant bool // true if this is a constant global marked uint8 // 0 means unmarked, 1 means external read, 2 means external write } // clone() returns a cloned version of this object, for when an object needs to // be written to for example. func (obj object) clone() object { if obj.buffer != nil { obj.buffer = obj.buffer.clone() } return obj } // A memoryView is bound to a function activation. Loads are done from this view // or a parent view (up to the *runner if it isn't included in a view). Stores // copy the object to the current view. // // For details, see the README in the package. type memoryView struct { r *runner parent *memoryView objects map[uint32]object // These instructions were added to runtime.initAll while interpreting a // function. They are stored here in a list so they can be removed if the // execution of the function needs to be rolled back. instructions []llvm.Value } // extend integrates the changes done by the sub-memoryView into this memory // view. This happens when a function is successfully interpreted and returns to // the parent, in which case all changed objects should be included in this // memory view. func (mv *memoryView) extend(sub memoryView) { if mv.objects == nil && len(sub.objects) != 0 { mv.objects = make(map[uint32]object) } for key, value := range sub.objects { mv.objects[key] = value } mv.instructions = append(mv.instructions, sub.instructions...) } // revert undoes changes done in this memory view: it removes all instructions // created in this memoryView. Do not reuse this memoryView. func (mv *memoryView) revert() { // Erase instructions in reverse order. for i := len(mv.instructions) - 1; i >= 0; i-- { llvmInst := mv.instructions[i] if llvmInst.IsAInstruction().IsNil() { // The IR builder will try to create constant versions of // instructions whenever possible. If it does this, it's not an // instruction and thus shouldn't be removed. continue } llvmInst.EraseFromParentAsInstruction() } } // markExternalLoad marks the given LLVM value as having an external read. That // means that the interpreter can still read from it, but cannot write to it as // that would mean the external read (done at runtime) reads from a state that // would not exist had the whole initialization been done at runtime. func (mv *memoryView) markExternalLoad(llvmValue llvm.Value) error { return mv.markExternal(llvmValue, 1) } // markExternalStore marks the given LLVM value as having an external write. // This means that the interpreter can no longer read from it or write to it, as // that would happen in a different order than if all initialization were // happening at runtime. func (mv *memoryView) markExternalStore(llvmValue llvm.Value) error { return mv.markExternal(llvmValue, 2) } // markExternal is a helper for markExternalLoad and markExternalStore, and // should not be called directly. func (mv *memoryView) markExternal(llvmValue llvm.Value, mark uint8) error { if llvmValue.IsUndef() || llvmValue.IsNull() { // Null and undef definitely don't contain (valid) pointers. return nil } if !llvmValue.IsAInstruction().IsNil() || !llvmValue.IsAArgument().IsNil() { // These are considered external by default, there is nothing to mark. return nil } if !llvmValue.IsAGlobalValue().IsNil() { objectIndex := mv.r.getValue(llvmValue).(pointerValue).index() obj := mv.get(objectIndex) if obj.marked < mark { obj = obj.clone() obj.marked = mark if mv.objects == nil { mv.objects = make(map[uint32]object) } mv.objects[objectIndex] = obj if !llvmValue.IsAGlobalVariable().IsNil() { initializer := llvmValue.Initializer() if !initializer.IsNil() { // Using mark '2' (which means read/write access) because // even from an object that is only read from, the resulting // loaded pointer can be written to. err := mv.markExternal(initializer, 2) if err != nil { return err } } } else { // This is a function. Go through all instructions and mark all // objects in there. for bb := llvmValue.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) { for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) { opcode := inst.InstructionOpcode() if opcode == llvm.Call { calledValue := inst.CalledValue() if !calledValue.IsAFunction().IsNil() { functionName := calledValue.Name() if functionName == "llvm.dbg.value" || strings.HasPrefix(functionName, "llvm.lifetime.") { continue } } } if opcode == llvm.Br || opcode == llvm.Switch { // These don't affect memory. Skipped here because // they also have a label as operand. continue } numOperands := inst.OperandsCount() for i := 0; i < numOperands; i++ { // Using mark '2' (which means read/write access) // because this might be a store instruction. err := mv.markExternal(inst.Operand(i), 2) if err != nil { return err } } } } } } } else if !llvmValue.IsAConstantExpr().IsNil() { switch llvmValue.Opcode() { case llvm.IntToPtr, llvm.PtrToInt, llvm.BitCast, llvm.GetElementPtr: err := mv.markExternal(llvmValue.Operand(0), mark) if err != nil { return err } case llvm.Add, llvm.Sub, llvm.Mul, llvm.UDiv, llvm.SDiv, llvm.URem, llvm.SRem, llvm.Shl, llvm.LShr, llvm.AShr, llvm.And, llvm.Or, llvm.Xor: // Integer binary operators. Mark both operands. err := mv.markExternal(llvmValue.Operand(0), mark) if err != nil { return err } err = mv.markExternal(llvmValue.Operand(1), mark) if err != nil { return err } default: return fmt.Errorf("interp: unknown constant expression '%s'", instructionNameMap[llvmValue.Opcode()]) } } else if !llvmValue.IsAInlineAsm().IsNil() { // Inline assembly can modify globals but only exported globals. Let's // hope the author knows what they're doing. } else { llvmType := llvmValue.Type() switch llvmType.TypeKind() { case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind: // Nothing to do here. Integers and floats aren't pointers so don't // need any marking. case llvm.StructTypeKind: numElements := llvmType.StructElementTypesCount() for i := 0; i < numElements; i++ { element := mv.r.builder.CreateExtractValue(llvmValue, i, "") err := mv.markExternal(element, mark) if err != nil { return err } } case llvm.ArrayTypeKind: numElements := llvmType.ArrayLength() for i := 0; i < numElements; i++ { element := mv.r.builder.CreateExtractValue(llvmValue, i, "") err := mv.markExternal(element, mark) if err != nil { return err } } default: return errors.New("interp: unknown type kind in markExternalValue") } } return nil } // hasExternalLoadOrStore returns true if this object has an external load or // store. If this has happened, it is not possible for the interpreter to load // from the object or store to it without affecting the behavior of the program. func (mv *memoryView) hasExternalLoadOrStore(v pointerValue) bool { obj := mv.get(v.index()) return obj.marked >= 1 } // hasExternalStore returns true if this object has an external store. If this // is true, stores to this object are no longer allowed by the interpreter. // It returns false if it only has an external load, in which case it is still // possible for the interpreter to read from the object. func (mv *memoryView) hasExternalStore(v pointerValue) bool { obj := mv.get(v.index()) return obj.marked >= 2 && !obj.constant } // get returns an object that can only be read from, as it may return an object // of a parent view. func (mv *memoryView) get(index uint32) object { if obj, ok := mv.objects[index]; ok { return obj } if mv.parent != nil { return mv.parent.get(index) } return mv.r.objects[index] } // getWritable returns an object that can be written to. func (mv *memoryView) getWritable(index uint32) object { if obj, ok := mv.objects[index]; ok { // Object is already in the current memory view, so can be modified. return obj } // Object is not currently in this view. Get it, and clone it for use. obj := mv.get(index).clone() mv.r.objects[index] = obj return obj } // Replace the object (indicated with index) with the given object. This put is // only done at the current memory view, so that if this memory view is reverted // the object is not changed. func (mv *memoryView) put(index uint32, obj object) { if mv.objects == nil { mv.objects = make(map[uint32]object) } if checks && mv.get(index).buffer == nil { panic("writing to external object") } if checks && mv.get(index).buffer.len(mv.r) != obj.buffer.len(mv.r) { panic("put() with a differently-sized object") } if checks && obj.constant { panic("interp: store to a constant") } mv.objects[index] = obj } // Load the value behind the given pointer. Returns nil if the pointer points to // an external global. func (mv *memoryView) load(p pointerValue, size uint32) value { if checks && mv.hasExternalStore(p) { panic("interp: load from object with external store") } obj := mv.get(p.index()) if obj.buffer == nil { // External global, return nil. return nil } if p.offset() == 0 && size == obj.size { return obj.buffer.clone() } if checks && p.offset()+size > obj.size { panic("interp: load out of bounds") } v := obj.buffer.asRawValue(mv.r) loadedValue := rawValue{ buf: v.buf[p.offset() : p.offset()+size], } return loadedValue } // Store to the value behind the given pointer. This overwrites the value in the // memory view, so that the changed value is discarded when the memory view is // reverted. Returns true on success, false if the object to store to is // external. func (mv *memoryView) store(v value, p pointerValue) bool { if checks && mv.hasExternalLoadOrStore(p) { panic("interp: store to object with external load/store") } obj := mv.get(p.index()) if obj.buffer == nil { // External global, return false (for a failure). return false } if checks && p.offset()+v.len(mv.r) > obj.size { panic("interp: store out of bounds") } if p.offset() == 0 && v.len(mv.r) == obj.buffer.len(mv.r) { obj.buffer = v } else { obj = obj.clone() buffer := obj.buffer.asRawValue(mv.r) obj.buffer = buffer v := v.asRawValue(mv.r) for i := uint32(0); i < v.len(mv.r); i++ { buffer.buf[p.offset()+i] = v.buf[i] } } mv.put(p.index(), obj) return true // success } // value is some sort of value, comparable to a LLVM constant. It can be // implemented in various ways for efficiency, but the fallback value (that all // implementations can be converted to except for localValue) is rawValue. type value interface { // len returns the length in bytes. len(r *runner) uint32 clone() value asPointer(*runner) (pointerValue, error) asRawValue(*runner) rawValue Uint(*runner) uint64 Int(*runner) int64 toLLVMValue(llvm.Type, *memoryView) (llvm.Value, error) String() string } // literalValue contains simple integer values that don't need to be stored in a // buffer. type literalValue struct { value interface{} } // Make a literalValue given the number of bits. func makeLiteralInt(value uint64, bits int) literalValue { switch bits { case 64: return literalValue{value} case 32: return literalValue{uint32(value)} case 16: return literalValue{uint16(value)} case 8: return literalValue{uint8(value)} default: panic("unknown integer size") } } func (v literalValue) len(r *runner) uint32 { switch v.value.(type) { case uint64: return 8 case uint32: return 4 case uint16: return 2 case uint8: return 1 default: panic("unknown value type") } } func (v literalValue) String() string { // Note: passing a nil *runner to v.Int because we know it won't use it. return strconv.FormatInt(v.Int(nil), 10) } func (v literalValue) clone() value { return v } func (v literalValue) asPointer(r *runner) (pointerValue, error) { return pointerValue{}, errIntegerAsPointer } func (v literalValue) asRawValue(r *runner) rawValue { var buf []byte switch value := v.value.(type) { case uint64: buf = make([]byte, 8) r.byteOrder.PutUint64(buf, value) case uint32: buf = make([]byte, 4) r.byteOrder.PutUint32(buf, uint32(value)) case uint16: buf = make([]byte, 2) r.byteOrder.PutUint16(buf, uint16(value)) case uint8: buf = []byte{uint8(value)} default: panic("unknown value type") } raw := newRawValue(uint32(len(buf))) for i, b := range buf { raw.buf[i] = uint64(b) } return raw } func (v literalValue) Uint(r *runner) uint64 { switch value := v.value.(type) { case uint64: return value case uint32: return uint64(value) case uint16: return uint64(value) case uint8: return uint64(value) default: panic("inpterp: unknown literal type") } } func (v literalValue) Int(r *runner) int64 { switch value := v.value.(type) { case uint64: return int64(value) case uint32: return int64(int32(value)) case uint16: return int64(int16(value)) case uint8: return int64(int8(value)) default: panic("inpterp: unknown literal type") } } func (v literalValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, error) { switch llvmType.TypeKind() { case llvm.IntegerTypeKind: switch value := v.value.(type) { case uint64: return llvm.ConstInt(llvmType, value, false), nil case uint32: return llvm.ConstInt(llvmType, uint64(value), false), nil case uint16: return llvm.ConstInt(llvmType, uint64(value), false), nil case uint8: return llvm.ConstInt(llvmType, uint64(value), false), nil default: return llvm.Value{}, errors.New("interp: unknown literal type") } case llvm.DoubleTypeKind: return llvm.ConstFloat(llvmType, math.Float64frombits(v.value.(uint64))), nil case llvm.FloatTypeKind: return llvm.ConstFloat(llvmType, float64(math.Float32frombits(v.value.(uint32)))), nil default: return v.asRawValue(mem.r).toLLVMValue(llvmType, mem) } } // pointerValue contains a single pointer, with an offset into the underlying // object. type pointerValue struct { pointer uint64 // low 32 bits are offset, high 32 bits are index } func newPointerValue(r *runner, index, offset int) pointerValue { return pointerValue{ pointer: uint64(index)<<32 | uint64(offset), } } func (v pointerValue) index() uint32 { return uint32(v.pointer >> 32) } func (v pointerValue) offset() uint32 { return uint32(v.pointer) } // addOffset essentially does a GEP operation (pointer arithmetic): it adds the // offset to the pointer. It also checks that the offset doesn't overflow the // maximum offset size (which is 4GB). func (v pointerValue) addOffset(offset int64) (pointerValue, error) { result := pointerValue{v.pointer + uint64(offset)} if checks && v.index() != result.index() { return result, fmt.Errorf("interp: offset %d out of range for object %v", offset, v) } return result, nil } func (v pointerValue) len(r *runner) uint32 { return r.pointerSize } func (v pointerValue) String() string { name := strconv.Itoa(int(v.index())) if v.offset() == 0 { return "<" + name + ">" } return "<" + name + "+" + strconv.Itoa(int(v.offset())) + ">" } func (v pointerValue) clone() value { return v } func (v pointerValue) asPointer(r *runner) (pointerValue, error) { return v, nil } func (v pointerValue) asRawValue(r *runner) rawValue { rv := newRawValue(r.pointerSize) for i := range rv.buf { rv.buf[i] = v.pointer } return rv } func (v pointerValue) Uint(r *runner) uint64 { panic("cannot convert pointer to integer") } func (v pointerValue) Int(r *runner) int64 { panic("cannot convert pointer to integer") } func (v pointerValue) equal(rhs pointerValue) bool { return v.pointer == rhs.pointer } func (v pointerValue) llvmValue(mem *memoryView) llvm.Value { return mem.get(v.index()).llvmGlobal } // toLLVMValue returns the LLVM value for this pointer, which may be a GEP or // bitcast. The llvm.Type parameter is optional, if omitted the pointer type may // be different than expected. func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, error) { // If a particular LLVM type is requested, cast to it. if !llvmType.IsNil() && llvmType.TypeKind() != llvm.PointerTypeKind { // The LLVM value has (or should have) the same bytes once compiled, but // does not have the right LLVM type. This can happen for example when // storing to a struct with a single pointer field: this pointer may // then become the value even though the pointer should be wrapped in a // struct. // This can be worked around by simply converting to a raw value, // rawValue knows how to create such structs. return v.asRawValue(mem.r).toLLVMValue(llvmType, mem) } // Obtain the llvmValue, creating it if it doesn't exist yet. llvmValue := v.llvmValue(mem) if llvmValue.IsNil() { // The global does not yet exist. Probably this is the result of a // runtime.alloc. // First allocate a new global for this object. obj := mem.get(v.index()) alignment := obj.align if alignment == 0 { // Unknown alignment, perhaps from a direct call to runtime.alloc in // the runtime. Use a conservative default instead. alignment = mem.r.maxAlign } if obj.llvmType.IsNil() && obj.llvmLayoutType.IsNil() { // Create an initializer without knowing the global type. // This is probably the result of a runtime.alloc call. initializer, err := obj.buffer.asRawValue(mem.r).rawLLVMValue(mem) if err != nil { return llvm.Value{}, err } globalType := initializer.Type() llvmValue = llvm.AddGlobal(mem.r.mod, globalType, obj.globalName) llvmValue.SetInitializer(initializer) llvmValue.SetAlignment(alignment) obj.llvmGlobal = llvmValue mem.put(v.index(), obj) } else { // The global type is known, or at least its structure. var globalType llvm.Type if !obj.llvmType.IsNil() { // The exact type is known. globalType = obj.llvmType } else { // !obj.llvmLayoutType.IsNil() // The exact type isn't known, but the object layout is known. globalType = obj.llvmLayoutType // The layout may not span the full size of the global because // of repetition. One example would be make([]string, 5) which // would be 10 words in size but the layout would only be two // words (for the string type). typeSize := mem.r.targetData.TypeAllocSize(globalType) if typeSize != uint64(obj.size) { globalType = llvm.ArrayType(globalType, int(uint64(obj.size)/typeSize)) } } if checks && mem.r.targetData.TypeAllocSize(globalType) != uint64(obj.size) { panic("size of the globalType isn't the same as the object size") } llvmValue = llvm.AddGlobal(mem.r.mod, globalType, obj.globalName) obj.llvmGlobal = llvmValue mem.put(v.index(), obj) // Set the initializer for the global. Do this after creation to avoid // infinite recursion between creating the global and creating the // contents of the global (if the global contains itself). initializer, err := obj.buffer.toLLVMValue(globalType, mem) if err != nil { return llvm.Value{}, err } if checks && initializer.Type() != globalType { return llvm.Value{}, errors.New("interp: allocated value does not match allocated type") } llvmValue.SetInitializer(initializer) llvmValue.SetAlignment(alignment) } // It should be included in r.globals because otherwise markExternal // would consider it a new global (and would fail to mark this global as // having an externa load/store). mem.r.globals[llvmValue] = int(v.index()) llvmValue.SetLinkage(llvm.InternalLinkage) } if v.offset() != 0 { // If there is an offset, make sure to use a GEP to index into the // pointer. llvmValue = llvm.ConstInBoundsGEP(mem.r.mod.Context().Int8Type(), llvmValue, []llvm.Value{ llvm.ConstInt(mem.r.mod.Context().Int32Type(), uint64(v.offset()), false), }) } return llvmValue, nil } // rawValue is a raw memory buffer that can store either pointers or regular // data. This is the fallback data for everything that isn't clearly a // literalValue or pointerValue. type rawValue struct { // An integer in buf contains either pointers or bytes. // If it is a byte, it is smaller than 256. // If it is a pointer, the index is contained in the upper 32 bits and the // offset is contained in the lower 32 bits. buf []uint64 } func newRawValue(size uint32) rawValue { return rawValue{make([]uint64, size)} } func (v rawValue) len(r *runner) uint32 { return uint32(len(v.buf)) } func (v rawValue) String() string { if len(v.buf) == 2 || len(v.buf) == 4 || len(v.buf) == 8 { // Format as a pointer if the entire buf is this pointer. if v.buf[0] > 255 { isPointer := true for _, p := range v.buf { if p != v.buf[0] { isPointer = false break } } if isPointer { return pointerValue{v.buf[0]}.String() } } // Format as number if none of the buf is a pointer. if !v.hasPointer() { // Construct a fake runner, which is little endian. // We only use String() for debugging, so this is is good enough // (the printed value will just be slightly wrong when debugging the // interp package with GOOS=mips for example). r := &runner{byteOrder: binary.LittleEndian} return strconv.FormatInt(v.Int(r), 10) } } return "<[…" + strconv.Itoa(len(v.buf)) + "]>" } func (v rawValue) clone() value { newValue := v newValue.buf = make([]uint64, len(v.buf)) copy(newValue.buf, v.buf) return newValue } func (v rawValue) asPointer(r *runner) (pointerValue, error) { if v.buf[0] <= 255 { // Probably a null pointer or memory-mapped I/O. return pointerValue{}, errIntegerAsPointer } return pointerValue{v.buf[0]}, nil } func (v rawValue) asRawValue(r *runner) rawValue { return v } func (v rawValue) bytes() []byte { buf := make([]byte, len(v.buf)) for i, p := range v.buf { if p > 255 { panic("cannot convert pointer value to byte") } buf[i] = byte(p) } return buf } func (v rawValue) Uint(r *runner) uint64 { buf := v.bytes() switch len(v.buf) { case 1: return uint64(buf[0]) case 2: return uint64(r.byteOrder.Uint16(buf)) case 4: return uint64(r.byteOrder.Uint32(buf)) case 8: return r.byteOrder.Uint64(buf) default: panic("unknown integer size") } } func (v rawValue) Int(r *runner) int64 { switch len(v.buf) { case 1: return int64(int8(v.Uint(r))) case 2: return int64(int16(v.Uint(r))) case 4: return int64(int32(v.Uint(r))) case 8: return int64(int64(v.Uint(r))) default: panic("unknown integer size") } } // equal returns true if (and only if) the value matches rhs. func (v rawValue) equal(rhs rawValue) bool { if len(v.buf) != len(rhs.buf) { panic("comparing values of different size") } for i, p := range v.buf { if rhs.buf[i] != p { return false } } return true } // rawLLVMValue returns a llvm.Value for this rawValue, making up a type as it // goes. The resulting value does not have a specified type, but it will be the // same size and have the same bytes if it was created with a provided LLVM type // (through toLLVMValue). func (v rawValue) rawLLVMValue(mem *memoryView) (llvm.Value, error) { var structFields []llvm.Value ctx := mem.r.mod.Context() int8Type := ctx.Int8Type() var bytesBuf []llvm.Value // addBytes can be called after adding to bytesBuf to flush remaining bytes // to a new array in structFields. addBytes := func() { if len(bytesBuf) == 0 { return } if len(bytesBuf) == 1 { structFields = append(structFields, bytesBuf[0]) } else { structFields = append(structFields, llvm.ConstArray(int8Type, bytesBuf)) } bytesBuf = nil } // Create structFields, converting the rawValue to a LLVM value. for i := uint32(0); i < uint32(len(v.buf)); { if v.buf[i] > 255 { addBytes() field, err := pointerValue{v.buf[i]}.toLLVMValue(llvm.Type{}, mem) if err != nil { return llvm.Value{}, err } structFields = append(structFields, field) i += mem.r.pointerSize continue } val := llvm.ConstInt(int8Type, uint64(v.buf[i]), false) bytesBuf = append(bytesBuf, val) i++ } addBytes() // Return the created data. if len(structFields) == 1 { return structFields[0], nil } return ctx.ConstStruct(structFields, false), nil } func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, error) { isZero := true for _, p := range v.buf { if p != 0 { isZero = false break } } if isZero { return llvm.ConstNull(llvmType), nil } switch llvmType.TypeKind() { case llvm.IntegerTypeKind: if v.buf[0] > 255 { ptr, err := v.asPointer(mem.r) if err != nil { panic(err) } if checks && mem.r.targetData.TypeAllocSize(llvmType) != mem.r.targetData.TypeAllocSize(mem.r.dataPtrType) { // Probably trying to serialize a pointer to a byte array, // perhaps as a result of rawLLVMValue() in a previous interp // run. return llvm.Value{}, errInvalidPtrToIntSize } v, err := ptr.toLLVMValue(llvm.Type{}, mem) if err != nil { return llvm.Value{}, err } return llvm.ConstPtrToInt(v, llvmType), nil } var n uint64 switch llvmType.IntTypeWidth() { case 64: n = rawValue{v.buf[:8]}.Uint(mem.r) case 32: n = rawValue{v.buf[:4]}.Uint(mem.r) case 16: n = rawValue{v.buf[:2]}.Uint(mem.r) case 8: n = uint64(v.buf[0]) case 1: n = uint64(v.buf[0]) if n != 0 && n != 1 { panic("bool must be 0 or 1") } default: panic("unknown integer size") } return llvm.ConstInt(llvmType, n, false), nil case llvm.StructTypeKind: fieldTypes := llvmType.StructElementTypes() fields := make([]llvm.Value, len(fieldTypes)) for i, fieldType := range fieldTypes { offset := mem.r.targetData.ElementOffset(llvmType, i) field := rawValue{ buf: v.buf[offset:], } var err error fields[i], err = field.toLLVMValue(fieldType, mem) if err != nil { return llvm.Value{}, err } } if llvmType.StructName() != "" { return llvm.ConstNamedStruct(llvmType, fields), nil } return llvmType.Context().ConstStruct(fields, false), nil case llvm.ArrayTypeKind: numElements := llvmType.ArrayLength() childType := llvmType.ElementType() childTypeSize := mem.r.targetData.TypeAllocSize(childType) fields := make([]llvm.Value, numElements) for i := range fields { offset := i * int(childTypeSize) field := rawValue{ buf: v.buf[offset:], } var err error fields[i], err = field.toLLVMValue(childType, mem) if err != nil { return llvm.Value{}, err } if checks && fields[i].Type() != childType { panic("child type doesn't match") } } return llvm.ConstArray(childType, fields), nil case llvm.PointerTypeKind: if v.buf[0] > 255 { // This is a regular pointer. llvmValue, err := pointerValue{v.buf[0]}.toLLVMValue(llvm.Type{}, mem) if err != nil { return llvm.Value{}, err } if llvmValue.Type() != llvmType { if llvmValue.Type().PointerAddressSpace() != llvmType.PointerAddressSpace() { // Special case for AVR function pointers. // Because go-llvm doesn't have addrspacecast at the moment, // do it indirectly with a ptrtoint/inttoptr pair. llvmValue = llvm.ConstIntToPtr(llvm.ConstPtrToInt(llvmValue, mem.r.uintptrType), llvmType) } } return llvmValue, nil } // This is either a null pointer or a raw pointer for memory-mapped I/O // (such as 0xe000ed00). ptr := rawValue{v.buf[:mem.r.pointerSize]}.Uint(mem.r) if ptr == 0 { // Null pointer. return llvm.ConstNull(llvmType), nil } var ptrValue llvm.Value // the underlying int switch mem.r.pointerSize { case 8: ptrValue = llvm.ConstInt(llvmType.Context().Int64Type(), ptr, false) case 4: ptrValue = llvm.ConstInt(llvmType.Context().Int32Type(), ptr, false) case 2: ptrValue = llvm.ConstInt(llvmType.Context().Int16Type(), ptr, false) default: return llvm.Value{}, errors.New("interp: unknown pointer size") } return llvm.ConstIntToPtr(ptrValue, llvmType), nil case llvm.DoubleTypeKind: b := rawValue{v.buf[:8]}.Uint(mem.r) f := math.Float64frombits(b) return llvm.ConstFloat(llvmType, f), nil case llvm.FloatTypeKind: b := uint32(rawValue{v.buf[:4]}.Uint(mem.r)) f := math.Float32frombits(b) return llvm.ConstFloat(llvmType, float64(f)), nil default: return llvm.Value{}, errors.New("interp: todo: raw value to LLVM value: " + llvmType.String()) } } func (v *rawValue) set(llvmValue llvm.Value, r *runner) { if llvmValue.IsNull() { // A zero value is common so check that first. return } if !llvmValue.IsAGlobalValue().IsNil() { ptrSize := r.pointerSize ptr, err := r.getValue(llvmValue).asPointer(r) if err != nil { panic(err) } for i := uint32(0); i < ptrSize; i++ { v.buf[i] = ptr.pointer } } else if !llvmValue.IsAConstantExpr().IsNil() { switch llvmValue.Opcode() { case llvm.IntToPtr, llvm.PtrToInt, llvm.BitCast: // All these instructions effectively just reinterprets the bits // (like a bitcast) while no bits change and keeping the same // length, so just read its contents. v.set(llvmValue.Operand(0), r) case llvm.GetElementPtr: ptr := llvmValue.Operand(0) index := llvmValue.Operand(1) numOperands := llvmValue.OperandsCount() elementType := llvmValue.GEPSourceElementType() totalOffset := r.targetData.TypeAllocSize(elementType) * index.ZExtValue() for i := 2; i < numOperands; i++ { indexValue := llvmValue.Operand(i) if checks && indexValue.IsAConstantInt().IsNil() { panic("expected const gep index to be a constant integer") } index := indexValue.ZExtValue() switch elementType.TypeKind() { case llvm.StructTypeKind: // Indexing into a struct field. offsetInBytes := r.targetData.ElementOffset(elementType, int(index)) totalOffset += offsetInBytes elementType = elementType.StructElementTypes()[index] default: // Indexing into an array. elementType = elementType.ElementType() elementSize := r.targetData.TypeAllocSize(elementType) totalOffset += index * elementSize } } ptrSize := r.pointerSize ptrValue, err := r.getValue(ptr).asPointer(r) if err != nil { panic(err) } ptrValue.pointer += totalOffset for i := uint32(0); i < ptrSize; i++ { v.buf[i] = ptrValue.pointer } case llvm.ICmp: // Note: constant icmp isn't supported anymore in LLVM 19. // Once we drop support for LLVM 18, this can be removed. size := r.targetData.TypeAllocSize(llvmValue.Operand(0).Type()) lhs := newRawValue(uint32(size)) rhs := newRawValue(uint32(size)) lhs.set(llvmValue.Operand(0), r) rhs.set(llvmValue.Operand(1), r) if r.interpretICmp(lhs, rhs, llvmValue.IntPredicate()) { v.buf[0] = 1 // result is true } else { v.buf[0] = 0 // result is false } default: llvmValue.Dump() println() panic("unknown constant expr") } } else if llvmValue.IsUndef() { // Let undef be zero, by lack of an explicit 'undef' marker. } else { if checks && llvmValue.IsAConstant().IsNil() { panic("expected a constant") } llvmType := llvmValue.Type() switch llvmType.TypeKind() { case llvm.IntegerTypeKind: n := llvmValue.ZExtValue() switch llvmValue.Type().IntTypeWidth() { case 64: var buf [8]byte r.byteOrder.PutUint64(buf[:], n) for i, b := range buf { v.buf[i] = uint64(b) } case 32: var buf [4]byte r.byteOrder.PutUint32(buf[:], uint32(n)) for i, b := range buf { v.buf[i] = uint64(b) } case 16: var buf [2]byte r.byteOrder.PutUint16(buf[:], uint16(n)) for i, b := range buf { v.buf[i] = uint64(b) } case 8, 1: v.buf[0] = n default: panic("unknown integer size") } case llvm.StructTypeKind: numElements := llvmType.StructElementTypesCount() for i := 0; i < numElements; i++ { offset := r.targetData.ElementOffset(llvmType, i) field := rawValue{ buf: v.buf[offset:], } field.set(r.builder.CreateExtractValue(llvmValue, i, ""), r) } case llvm.ArrayTypeKind: numElements := llvmType.ArrayLength() childType := llvmType.ElementType() childTypeSize := r.targetData.TypeAllocSize(childType) for i := 0; i < numElements; i++ { offset := i * int(childTypeSize) field := rawValue{ buf: v.buf[offset:], } field.set(r.builder.CreateExtractValue(llvmValue, i, ""), r) } case llvm.DoubleTypeKind: f, _ := llvmValue.DoubleValue() var buf [8]byte r.byteOrder.PutUint64(buf[:], math.Float64bits(f)) for i, b := range buf { v.buf[i] = uint64(b) } case llvm.FloatTypeKind: f, _ := llvmValue.DoubleValue() var buf [4]byte r.byteOrder.PutUint32(buf[:], math.Float32bits(float32(f))) for i, b := range buf { v.buf[i] = uint64(b) } default: llvmValue.Dump() println() panic("unknown constant") } } } // hasPointer returns true if this raw value contains a pointer somewhere in the // buffer. func (v rawValue) hasPointer() bool { for _, p := range v.buf { if p > 255 { return true } } return false } // localValue is a special implementation of the value interface. It is a // placeholder for other values in instruction operands, and is replaced with // one of the others before executing. type localValue struct { value llvm.Value } func (v localValue) len(r *runner) uint32 { panic("interp: localValue.len") } func (v localValue) String() string { return "" } func (v localValue) clone() value { panic("interp: localValue.clone()") } func (v localValue) asPointer(r *runner) (pointerValue, error) { return pointerValue{}, errors.New("interp: localValue.asPointer called") } func (v localValue) asRawValue(r *runner) rawValue { panic("interp: localValue.asRawValue") } func (v localValue) Uint(r *runner) uint64 { panic("interp: localValue.Uint") } func (v localValue) Int(r *runner) int64 { panic("interp: localValue.Int") } func (v localValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, error) { return v.value, nil } func (r *runner) getValue(llvmValue llvm.Value) value { if checks && llvmValue.IsNil() { panic("nil llvmValue") } if !llvmValue.IsAGlobalValue().IsNil() { index, ok := r.globals[llvmValue] if !ok { obj := object{ llvmGlobal: llvmValue, } index = len(r.objects) r.globals[llvmValue] = index r.objects = append(r.objects, obj) if !llvmValue.IsAGlobalVariable().IsNil() { obj.size = uint32(r.targetData.TypeAllocSize(llvmValue.GlobalValueType())) if initializer := llvmValue.Initializer(); !initializer.IsNil() { obj.buffer = r.getValue(initializer) obj.constant = llvmValue.IsGlobalConstant() } } else if !llvmValue.IsAFunction().IsNil() { // OK } else { panic("interp: unknown global value") } // Update the object after it has been created. This avoids an // infinite recursion when using getValue on a global that contains // a reference to itself. r.objects[index] = obj } return newPointerValue(r, index, 0) } else if !llvmValue.IsAConstant().IsNil() { if !llvmValue.IsAConstantInt().IsNil() { n := llvmValue.ZExtValue() switch llvmValue.Type().IntTypeWidth() { case 64: return literalValue{n} case 32: return literalValue{uint32(n)} case 16: return literalValue{uint16(n)} case 8, 1: return literalValue{uint8(n)} default: panic("unknown integer size") } } size := r.targetData.TypeAllocSize(llvmValue.Type()) v := newRawValue(uint32(size)) v.set(llvmValue, r) return v } else if !llvmValue.IsAInstruction().IsNil() || !llvmValue.IsAArgument().IsNil() { return localValue{llvmValue} } else if !llvmValue.IsAInlineAsm().IsNil() { return localValue{llvmValue} } else { llvmValue.Dump() println() panic("unknown value") } } // readObjectLayout reads the object layout as it is stored by the compiler. It // returns the size in the number of words and the bitmap. // // For details on this format, see src/runtime/gc_precise.go. func (r *runner) readObjectLayout(layoutValue value) (uint64, *big.Int) { pointerSize := layoutValue.len(r) if checks && uint64(pointerSize) != r.targetData.TypeAllocSize(r.dataPtrType) { panic("inconsistent pointer size") } // The object layout can be stored in a global variable, directly as an // integer value, or can be nil. ptr, err := layoutValue.asPointer(r) if err == errIntegerAsPointer { // It's an integer, which means it's a small object or unknown. layout := layoutValue.Uint(r) if layout == 0 { // Nil pointer, which means the layout is unknown. return 0, nil } if layout%2 != 1 { // Sanity check: the least significant bit must be set. This is how // the runtime can separate pointers from integers. panic("unexpected layout") } // Determine format of bitfields in the integer. pointerBits := uint64(pointerSize * 8) var sizeFieldBits uint64 switch pointerBits { case 16: sizeFieldBits = 4 case 32: sizeFieldBits = 5 case 64: sizeFieldBits = 6 default: panic("unknown pointer size") } // Extract fields. objectSizeWords := (layout >> 1) & (1<> (1 + sizeFieldBits)) return objectSizeWords, bitmap } // Read the object size in words and the bitmap from the global. buf := r.objects[ptr.index()].buffer.(rawValue) objectSizeWords := rawValue{buf: buf.buf[:r.pointerSize]}.Uint(r) rawByteValues := buf.buf[r.pointerSize:] rawBytes := make([]byte, len(rawByteValues)) for i, v := range rawByteValues { if uint64(byte(v)) != v { panic("found pointer in data array?") // sanity check } rawBytes[i] = byte(v) } reverseBytes(rawBytes) // little-endian to big-endian bitmap := new(big.Int).SetBytes(rawBytes) return objectSizeWords, bitmap } // getLLVMTypeFromLayout returns the 'layout type', which is an approximation of // the real type. Pointers are in the correct location but the actual object may // have some additional repetition, for example in the buffer of a slice. func (r *runner) getLLVMTypeFromLayout(layoutValue value) llvm.Type { objectSizeWords, bitmap := r.readObjectLayout(layoutValue) if bitmap == nil { // No information available. return llvm.Type{} } if bitmap.BitLen() == 0 { // There are no pointers in this object, so treat this as a raw byte // buffer. This is important because objects without pointers may have // lower alignment. return r.mod.Context().Int8Type() } // Create the LLVM type. pointerSize := layoutValue.len(r) pointerAlignment := r.targetData.PrefTypeAlignment(r.dataPtrType) var fields []llvm.Type for i := 0; i < int(objectSizeWords); { if bitmap.Bit(i) != 0 { // Pointer field. fields = append(fields, r.dataPtrType) i += int(pointerSize / uint32(pointerAlignment)) } else { // Byte/word field. fields = append(fields, r.mod.Context().IntType(pointerAlignment*8)) i += 1 } } var llvmLayoutType llvm.Type if len(fields) == 1 { llvmLayoutType = fields[0] } else { llvmLayoutType = r.mod.Context().StructType(fields, false) } objectSizeBytes := objectSizeWords * uint64(pointerAlignment) if checks && r.targetData.TypeAllocSize(llvmLayoutType) != objectSizeBytes { panic("unexpected size") // sanity check } return llvmLayoutType } // Reverse a slice of bytes. From the wiki: // https://github.com/golang/go/wiki/SliceTricks#reversing func reverseBytes(buf []byte) { for i := len(buf)/2 - 1; i >= 0; i-- { opp := len(buf) - 1 - i buf[i], buf[opp] = buf[opp], buf[i] } } ================================================ FILE: interp/testdata/alloc.ll ================================================ target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target triple = "wasm32--wasi" @"runtime/gc.layout:62-2000000000000001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00 " } @pointerFree12 = global ptr null @pointerFree7 = global ptr null @pointerFree3 = global ptr null @pointerFree0 = global ptr null @layout1 = global ptr null @layout2 = global ptr null @layout3 = global ptr null @layout4 = global ptr null @bigobj1 = global ptr null declare ptr @runtime.alloc(i32, ptr) unnamed_addr define void @runtime.initAll() unnamed_addr { call void @main.init() ret void } define internal void @main.init() unnamed_addr { ; Object that's word-aligned. %pointerFree12 = call ptr @runtime.alloc(i32 12, ptr inttoptr (i32 3 to ptr)) store ptr %pointerFree12, ptr @pointerFree12 ; Object larger than a word but not word-aligned. %pointerFree7 = call ptr @runtime.alloc(i32 7, ptr inttoptr (i32 3 to ptr)) store ptr %pointerFree7, ptr @pointerFree7 ; Object smaller than a word (and of course not word-aligned). %pointerFree3 = call ptr @runtime.alloc(i32 3, ptr inttoptr (i32 3 to ptr)) store ptr %pointerFree3, ptr @pointerFree3 ; Zero-sized object. %pointerFree0 = call ptr @runtime.alloc(i32 0, ptr inttoptr (i32 3 to ptr)) store ptr %pointerFree0, ptr @pointerFree0 ; Object made out of 3 pointers. %layout1 = call ptr @runtime.alloc(i32 12, ptr inttoptr (i32 67 to ptr)) store ptr %layout1, ptr @layout1 ; Array (or slice) of 5 slices. %layout2 = call ptr @runtime.alloc(i32 60, ptr inttoptr (i32 71 to ptr)) store ptr %layout2, ptr @layout2 ; Oddly shaped object, using all bits in the layout integer. %layout3 = call ptr @runtime.alloc(i32 104, ptr inttoptr (i32 2467830261 to ptr)) store ptr %layout3, ptr @layout3 ; ...repeated. %layout4 = call ptr @runtime.alloc(i32 312, ptr inttoptr (i32 2467830261 to ptr)) store ptr %layout4, ptr @layout4 ; Large object that needs to be stored in a separate global. %bigobj1 = call ptr @runtime.alloc(i32 248, ptr @"runtime/gc.layout:62-2000000000000001") store ptr %bigobj1, ptr @bigobj1 ret void } ================================================ FILE: interp/testdata/alloc.out.ll ================================================ target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target triple = "wasm32--wasi" @pointerFree12 = local_unnamed_addr global ptr @"main$alloc" @pointerFree7 = local_unnamed_addr global ptr @"main$alloc.1" @pointerFree3 = local_unnamed_addr global ptr @"main$alloc.2" @pointerFree0 = local_unnamed_addr global ptr @"main$alloc.3" @layout1 = local_unnamed_addr global ptr @"main$alloc.4" @layout2 = local_unnamed_addr global ptr @"main$alloc.5" @layout3 = local_unnamed_addr global ptr @"main$alloc.6" @layout4 = local_unnamed_addr global ptr @"main$alloc.7" @bigobj1 = local_unnamed_addr global ptr @"main$alloc.8" @"main$alloc" = internal global [12 x i8] zeroinitializer, align 4 @"main$alloc.1" = internal global [7 x i8] zeroinitializer, align 4 @"main$alloc.2" = internal global [3 x i8] zeroinitializer, align 4 @"main$alloc.3" = internal global [0 x i8] zeroinitializer, align 4 @"main$alloc.4" = internal global [3 x ptr] zeroinitializer, align 4 @"main$alloc.5" = internal global [5 x { ptr, i32, i32 }] zeroinitializer, align 4 @"main$alloc.6" = internal global { ptr, ptr, ptr, i32, i32, ptr, ptr, i32, i32, i32, i32, i32, i32, ptr, ptr, i32, i32, i32, ptr, ptr, i32, i32, ptr, i32, i32, ptr } zeroinitializer, align 4 @"main$alloc.7" = internal global [3 x { ptr, ptr, ptr, i32, i32, ptr, ptr, i32, i32, i32, i32, i32, i32, ptr, ptr, i32, i32, i32, ptr, ptr, i32, i32, ptr, i32, i32, ptr }] zeroinitializer, align 4 @"main$alloc.8" = internal global { ptr, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, ptr } zeroinitializer, align 4 define void @runtime.initAll() unnamed_addr { ret void } ================================================ FILE: interp/testdata/basic.ll ================================================ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64--linux" @main.v1 = internal global i64 0 @main.nonConst1 = global [4 x i64] zeroinitializer @main.nonConst2 = global i64 0 @main.someArray = global [8 x {i16, i32}] zeroinitializer @main.exportedValue = global [1 x ptr] [ptr @main.exposedValue1] @main.exportedConst = constant i64 42 @main.exposedValue1 = global i16 0 @main.exposedValue2 = global i16 0 @main.insertedValue = global {i8, i32, {float, {i64, i16}}} zeroinitializer @main.gepArray = global [8 x i8] zeroinitializer @main.negativeGEP = global ptr null declare void @runtime.printint64(i64) unnamed_addr declare void @runtime.printnl() unnamed_addr define void @runtime.initAll() unnamed_addr { entry: call void @runtime.init() call void @main.init() ret void } define void @main() unnamed_addr { entry: %0 = load i64, ptr @main.v1 call void @runtime.printint64(i64 %0) call void @runtime.printnl() ret void } define internal void @runtime.init() unnamed_addr { entry: ret void } define internal void @main.init() unnamed_addr { entry: store i64 3, ptr @main.v1 call void @"main.init#1"() ; test the following pattern: ; func someValue() int // extern function ; var nonConst1 = [4]int{someValue(), 0, 0, 0} %value1 = call i64 @someValue() %gep1 = getelementptr [4 x i64], ptr @main.nonConst1, i32 0, i32 0 store i64 %value1, ptr %gep1 ; Test that the global really is marked dirty: ; var nonConst2 = nonConst1[0] %gep2 = getelementptr [4 x i64], ptr @main.nonConst1, i32 0, i32 0 %value2 = load i64, ptr %gep2 store i64 %value2, ptr @main.nonConst2 ; Test that the following GEP works: ; var someArray ; modifyExternal(&someArray[3].field1) %gep3 = getelementptr [8 x {i16, i32}], ptr @main.someArray, i32 0, i32 3, i32 1 call void @modifyExternal(ptr %gep3) ; Test that marking a value as external also marks all referenced values. call void @modifyExternal(ptr @main.exportedValue) store i16 5, ptr @main.exposedValue1 ; Test that marking a constant as external still allows loading from it. call void @readExternal(ptr @main.exportedConst) %constLoad = load i64, ptr @main.exportedConst call void @runtime.printint64(i64 %constLoad) ; Test that this even propagates through functions. call void @modifyExternal(ptr @willModifyGlobal) store i16 7, ptr @main.exposedValue2 ; Test that inline assembly is ignored. call void @modifyExternal(ptr @hasInlineAsm) ; Test switch statement. %switch1 = call i64 @testSwitch(i64 1) ; 1 returns 6 %switch2 = call i64 @testSwitch(i64 9) ; 9 returns the default value -1 call void @runtime.printint64(i64 %switch1) call void @runtime.printint64(i64 %switch2) ; Test extractvalue/insertvalue with multiple operands. %agg = call {i8, i32, {float, {i64, i16}}} @nestedStruct() %elt = extractvalue {i8, i32, {float, {i64, i16}}} %agg, 2, 1, 0 call void @runtime.printint64(i64 %elt) %agg2 = insertvalue {i8, i32, {float, {i64, i16}}} %agg, i64 5, 2, 1, 0 store {i8, i32, {float, {i64, i16}}} %agg2, ptr @main.insertedValue ; negative GEP instruction %ngep1 = getelementptr [8 x i8], ptr @main.negativeGEP, i32 0, i32 5 %ngep2 = getelementptr [8 x i8], ptr %ngep1, i32 0, i32 -3 store ptr %ngep2, ptr @main.negativeGEP ret void } define internal void @"main.init#1"() unnamed_addr { entry: call void @runtime.printint64(i64 5) call void @runtime.printnl() ret void } declare i64 @someValue() declare void @modifyExternal(ptr) declare void @readExternal(ptr) ; This function will modify an external value. By passing this function as a ; function pointer to an external function, @main.exposedValue2 should be ; marked as external. define void @willModifyGlobal() { entry: store i16 8, ptr @main.exposedValue2 ret void } ; Inline assembly should be ignored in the interp package. While it is possible ; to modify other globals that way, usually that's not the case and there is no ; real way to check. define void @hasInlineAsm() { entry: call void asm sideeffect "", ""() ret void } define i64 @testSwitch(i64 %val) { entry: ; Test switch statement. switch i64 %val, label %otherwise [ i64 0, label %zero i64 1, label %one i64 2, label %two ] zero: ret i64 5 one: ret i64 6 two: ret i64 7 otherwise: ret i64 -1 } declare {i8, i32, {float, {i64, i16}}} @nestedStruct() ================================================ FILE: interp/testdata/basic.out.ll ================================================ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64--linux" @main.nonConst1 = local_unnamed_addr global [4 x i64] zeroinitializer @main.nonConst2 = local_unnamed_addr global i64 0 @main.someArray = global [8 x { i16, i32 }] zeroinitializer @main.exportedValue = global [1 x ptr] [ptr @main.exposedValue1] @main.exportedConst = constant i64 42 @main.exposedValue1 = global i16 0 @main.exposedValue2 = local_unnamed_addr global i16 0 @main.insertedValue = local_unnamed_addr global { i8, i32, { float, { i64, i16 } } } zeroinitializer @main.gepArray = local_unnamed_addr global [8 x i8] zeroinitializer @main.negativeGEP = global ptr getelementptr inbounds nuw (i8, ptr @main.negativeGEP, i64 2) declare void @runtime.printint64(i64) unnamed_addr declare void @runtime.printnl() unnamed_addr define void @runtime.initAll() unnamed_addr { entry: call void @runtime.printint64(i64 5) call void @runtime.printnl() %value1 = call i64 @someValue() store i64 %value1, ptr @main.nonConst1, align 8 %value2 = load i64, ptr @main.nonConst1, align 8 store i64 %value2, ptr @main.nonConst2, align 8 call void @modifyExternal(ptr getelementptr inbounds (i8, ptr @main.someArray, i32 28)) call void @modifyExternal(ptr @main.exportedValue) store i16 5, ptr @main.exposedValue1, align 2 call void @readExternal(ptr @main.exportedConst) call void @runtime.printint64(i64 42) call void @modifyExternal(ptr @willModifyGlobal) store i16 7, ptr @main.exposedValue2, align 2 call void @modifyExternal(ptr @hasInlineAsm) call void @runtime.printint64(i64 6) call void @runtime.printint64(i64 -1) %agg = call { i8, i32, { float, { i64, i16 } } } @nestedStruct() %elt.agg = extractvalue { i8, i32, { float, { i64, i16 } } } %agg, 2 %elt.agg1 = extractvalue { float, { i64, i16 } } %elt.agg, 1 %elt = extractvalue { i64, i16 } %elt.agg1, 0 call void @runtime.printint64(i64 %elt) %agg2.agg0 = extractvalue { i8, i32, { float, { i64, i16 } } } %agg, 2 %agg2.agg1 = extractvalue { float, { i64, i16 } } %agg2.agg0, 1 %agg2.insertvalue2 = insertvalue { i64, i16 } %agg2.agg1, i64 5, 0 %agg2.insertvalue1 = insertvalue { float, { i64, i16 } } %agg2.agg0, { i64, i16 } %agg2.insertvalue2, 1 %agg2.insertvalue0 = insertvalue { i8, i32, { float, { i64, i16 } } } %agg, { float, { i64, i16 } } %agg2.insertvalue1, 2 store { i8, i32, { float, { i64, i16 } } } %agg2.insertvalue0, ptr @main.insertedValue, align 8 ret void } define void @main() unnamed_addr { entry: call void @runtime.printint64(i64 3) call void @runtime.printnl() ret void } declare i64 @someValue() local_unnamed_addr declare void @modifyExternal(ptr) local_unnamed_addr declare void @readExternal(ptr) local_unnamed_addr define void @willModifyGlobal() { entry: store i16 8, ptr @main.exposedValue2, align 2 ret void } define void @hasInlineAsm() { entry: call void asm sideeffect "", ""() ret void } define i64 @testSwitch(i64 %val) local_unnamed_addr { entry: switch i64 %val, label %otherwise [ i64 0, label %zero i64 1, label %one i64 2, label %two ] zero: ; preds = %entry ret i64 5 one: ; preds = %entry ret i64 6 two: ; preds = %entry ret i64 7 otherwise: ; preds = %entry ret i64 -1 } declare { i8, i32, { float, { i64, i16 } } } @nestedStruct() local_unnamed_addr ================================================ FILE: interp/testdata/consteval.ll ================================================ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64--linux" @intToPtrResult = global i8 0 @ptrToIntResult = global i8 0 @pointerTagResult = global i64 0 @someArray = internal global {i16, i8, i8} zeroinitializer @someArrayPointer = global ptr zeroinitializer define void @runtime.initAll() { call void @main.init() ret void } define internal void @main.init() { call void @testIntToPtr() call void @testPtrToInt() call void @testConstGEP() call void @testPointerTag() ret void } define internal void @testIntToPtr() { %nil = icmp eq ptr inttoptr (i64 1024 to ptr), null br i1 %nil, label %a, label %b a: ; should not be reached store i8 1, ptr @intToPtrResult ret void b: ; should be reached store i8 2, ptr @intToPtrResult ret void } define internal void @testPtrToInt() { %zero = icmp eq i64 ptrtoint (ptr @ptrToIntResult to i64), 0 br i1 %zero, label %a, label %b a: ; should not be reached store i8 1, ptr @ptrToIntResult ret void b: ; should be reached store i8 2, ptr @ptrToIntResult ret void } define internal void @testConstGEP() { store ptr getelementptr inbounds (i8, ptr @someArray, i32 2), ptr @someArrayPointer ret void } define internal void @testPointerTag() { %val = and i64 ptrtoint (ptr getelementptr inbounds (i8, ptr @someArray, i32 2) to i64), 3 store i64 %val, ptr @pointerTagResult ret void } ================================================ FILE: interp/testdata/consteval.out.ll ================================================ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64--linux" @intToPtrResult = local_unnamed_addr global i8 2 @ptrToIntResult = local_unnamed_addr global i8 2 @pointerTagResult = local_unnamed_addr global i64 2 @someArray = internal global { i16, i8, i8 } zeroinitializer @someArrayPointer = local_unnamed_addr global ptr getelementptr inbounds nuw (i8, ptr @someArray, i64 2) define void @runtime.initAll() local_unnamed_addr { ret void } ================================================ FILE: interp/testdata/interface.ll ================================================ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64--linux" @main.v1 = global i1 0 @main.v2 = global i1 0 @"reflect/types.type:named:main.foo" = private constant { i8, ptr, ptr } { i8 34, ptr @"reflect/types.type:pointer:named:main.foo", ptr @"reflect/types.type:basic:int" }, align 4 @"reflect/types.type:pointer:named:main.foo" = external constant { i8, ptr } @"reflect/types.typeid:named:main.foo" = external constant i8 @"reflect/types.type:basic:int" = private constant { i8, ptr } { i8 2, ptr @"reflect/types.type:pointer:basic:int" }, align 4 @"reflect/types.type:pointer:basic:int" = external constant { i8, ptr } declare i1 @runtime.typeAssert(ptr, ptr, ptr, ptr) define void @runtime.initAll() unnamed_addr { entry: call void @main.init() ret void } define internal void @main.init() unnamed_addr { entry: ; Test type asserts. %typecode = call i1 @runtime.typeAssert(ptr @"reflect/types.type:named:main.foo", ptr @"reflect/types.typeid:named:main.foo", ptr undef, ptr null) store i1 %typecode, ptr @main.v1 %typecode2 = call i1 @runtime.typeAssert(ptr null, ptr @"reflect/types.typeid:named:main.foo", ptr undef, ptr null) store i1 %typecode2, ptr @main.v2 ret void } ================================================ FILE: interp/testdata/interface.out.ll ================================================ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64--linux" @main.v1 = local_unnamed_addr global i1 true @main.v2 = local_unnamed_addr global i1 false define void @runtime.initAll() unnamed_addr { entry: ret void } ================================================ FILE: interp/testdata/phi.ll ================================================ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64--linux" @main.phiNodesResultA = global i8 0 @main.phiNodesResultB = global i8 0 define void @runtime.initAll() { call void @main.init() ret void } ; PHI nodes always use the value from the previous block, even in a loop. This ; means that the loop below should swap the values %a and %b on each iteration. ; Previously there was a bug which resulted in %b getting the value 3 on the ; second iteration while it should have gotten 1 (from the first iteration of ; %for.loop). define internal void @main.init() { entry: br label %for.loop for.loop: %a = phi i8 [ 1, %entry ], [ %b, %for.loop ] %b = phi i8 [ 3, %entry ], [ %a, %for.loop ] %icmp = icmp eq i8 %a, 3 br i1 %icmp, label %for.done, label %for.loop for.done: store i8 %a, ptr @main.phiNodesResultA store i8 %b, ptr @main.phiNodesResultB ret void } ================================================ FILE: interp/testdata/phi.out.ll ================================================ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64--linux" @main.phiNodesResultA = local_unnamed_addr global i8 3 @main.phiNodesResultB = local_unnamed_addr global i8 1 define void @runtime.initAll() local_unnamed_addr { ret void } ================================================ FILE: interp/testdata/revert.ll ================================================ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64--linux" declare void @externalCall(i64) declare i64 @ptrHash(ptr nocapture) @foo.knownAtRuntime = global i64 0 @bar.knownAtRuntime = global i64 0 @baz.someGlobal = external global [3 x {i64, i32}] @baz.someInt = global i32 0 @x.atomicNum = global i32 0 @x.volatileNum = global i32 0 @y.ready = global i32 0 @z.bloom = global i64 0 @z.arr = global [32 x i8] zeroinitializer define void @runtime.initAll() unnamed_addr { entry: call void @baz.init(ptr undef) call void @foo.init(ptr undef) call void @bar.init(ptr undef) call void @main.init(ptr undef) call void @x.init(ptr undef) call void @y.init(ptr undef) call void @z.init(ptr undef) ret void } define internal void @foo.init(ptr %context) unnamed_addr { store i64 5, ptr @foo.knownAtRuntime unreachable ; this triggers a revert of @foo.init. } define internal void @bar.init(ptr %context) unnamed_addr { %val = load i64, ptr @foo.knownAtRuntime store i64 %val, ptr @bar.knownAtRuntime ret void } define internal void @baz.init(ptr %context) unnamed_addr { ; Test extractvalue/insertvalue with more than one index. %val = load [3 x {i64, i32}], ptr @baz.someGlobal %part = extractvalue [3 x {i64, i32}] %val, 0, 1 %val2 = insertvalue [3 x {i64, i32}] %val, i32 5, 2, 1 unreachable ; trigger revert } define internal void @main.init(ptr %context) unnamed_addr { entry: call void @externalCall(i64 3) ret void } define internal void @x.init(ptr %context) unnamed_addr { ; Test atomic and volatile memory accesses. store atomic i32 1, ptr @x.atomicNum seq_cst, align 4 %x = load atomic i32, ptr @x.atomicNum seq_cst, align 4 store i32 %x, ptr @x.atomicNum %y = load volatile i32, ptr @x.volatileNum store volatile i32 %y, ptr @x.volatileNum ret void } define internal void @y.init(ptr %context) unnamed_addr { entry: br label %loop loop: ; Test a wait-loop. ; This function must be reverted. %val = load atomic i32, ptr @y.ready seq_cst, align 4 %ready = icmp eq i32 %val, 1 br i1 %ready, label %end, label %loop end: ret void } define internal void @z.init(ptr %context) unnamed_addr { ; This can be safely expanded. call void @z.setArr(ptr @z.bloom, i64 1, ptr @z.bloom) ; This call should be reverted to prevent unrolling. call void @z.setArr(ptr @z.arr, i64 32, ptr @z.bloom) ret void } define internal void @z.setArr(ptr %arr, i64 %n, ptr %context) unnamed_addr { entry: br label %loop loop: %prev = phi i64 [ %n, %entry ], [ %idx, %loop ] %idx = sub i64 %prev, 1 %elem = getelementptr i8, ptr %arr, i64 %idx call void @z.set(ptr %elem, ptr %context) %done = icmp eq i64 %idx, 0 br i1 %done, label %end, label %loop end: ret void } define internal void @z.set(ptr %ptr, ptr %context) unnamed_addr { ; Insert the pointer into the Bloom filter. %hash = call i64 @ptrHash(ptr %ptr) %index = lshr i64 %hash, 58 %bit = shl i64 1, %index %old = load i64, ptr %context %new = or i64 %old, %bit store i64 %new, ptr %context ret void } ================================================ FILE: interp/testdata/revert.out.ll ================================================ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64--linux" @foo.knownAtRuntime = local_unnamed_addr global i64 0 @bar.knownAtRuntime = local_unnamed_addr global i64 0 @baz.someGlobal = external local_unnamed_addr global [3 x { i64, i32 }] @baz.someInt = local_unnamed_addr global i32 0 @x.atomicNum = local_unnamed_addr global i32 0 @x.volatileNum = global i32 0 @y.ready = local_unnamed_addr global i32 0 @z.bloom = global i64 0 @z.arr = global [32 x i8] zeroinitializer declare void @externalCall(i64) local_unnamed_addr declare i64 @ptrHash(ptr nocapture) local_unnamed_addr define void @runtime.initAll() unnamed_addr { entry: call fastcc void @baz.init(ptr undef) call fastcc void @foo.init(ptr undef) %val = load i64, ptr @foo.knownAtRuntime, align 8 store i64 %val, ptr @bar.knownAtRuntime, align 8 call void @externalCall(i64 3) store atomic i32 1, ptr @x.atomicNum seq_cst, align 4 %x = load atomic i32, ptr @x.atomicNum seq_cst, align 4 store i32 %x, ptr @x.atomicNum, align 4 %y = load volatile i32, ptr @x.volatileNum, align 4 store volatile i32 %y, ptr @x.volatileNum, align 4 call fastcc void @y.init(ptr undef) call fastcc void @z.set(ptr @z.bloom, ptr @z.bloom) call fastcc void @z.setArr(ptr @z.arr, i64 32, ptr @z.bloom) ret void } define internal fastcc void @foo.init(ptr %context) unnamed_addr { store i64 5, ptr @foo.knownAtRuntime, align 8 unreachable } define internal fastcc void @baz.init(ptr %context) unnamed_addr { unreachable } define internal fastcc void @y.init(ptr %context) unnamed_addr { entry: br label %loop loop: ; preds = %loop, %entry %val = load atomic i32, ptr @y.ready seq_cst, align 4 %ready = icmp eq i32 %val, 1 br i1 %ready, label %end, label %loop end: ; preds = %loop ret void } define internal fastcc void @z.setArr(ptr %arr, i64 %n, ptr %context) unnamed_addr { entry: br label %loop loop: ; preds = %loop, %entry %prev = phi i64 [ %n, %entry ], [ %idx, %loop ] %idx = sub i64 %prev, 1 %elem = getelementptr i8, ptr %arr, i64 %idx call fastcc void @z.set(ptr %elem, ptr %context) %done = icmp eq i64 %idx, 0 br i1 %done, label %end, label %loop end: ; preds = %loop ret void } define internal fastcc void @z.set(ptr %ptr, ptr %context) unnamed_addr { %hash = call i64 @ptrHash(ptr %ptr) %index = lshr i64 %hash, 58 %bit = shl i64 1, %index %old = load i64, ptr %context, align 8 %new = or i64 %old, %bit store i64 %new, ptr %context, align 8 ret void } ================================================ FILE: interp/testdata/slice-copy.ll ================================================ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64--linux" @main.uint8SliceSrc.buf = internal global [2 x i8] c"\03d" @main.uint8SliceSrc = internal unnamed_addr global { ptr, i64, i64 } { ptr @main.uint8SliceSrc.buf, i64 2, i64 2 } @main.uint8SliceDst = internal unnamed_addr global { ptr, i64, i64 } zeroinitializer @main.int16SliceSrc.buf = internal global [3 x i16] [i16 5, i16 123, i16 1024] @main.int16SliceSrc = internal unnamed_addr global { ptr, i64, i64 } { ptr @main.int16SliceSrc.buf, i64 3, i64 3 } @main.int16SliceDst = internal unnamed_addr global { ptr, i64, i64 } zeroinitializer @main.sliceSrcUntaint.buf = internal global [2 x i8] c"ab" @main.sliceDstUntaint.buf = internal global [2 x i8] zeroinitializer @main.sliceSrcTaint.buf = internal global [2 x i8] c"cd" @main.sliceDstTaint.buf = internal global [2 x i8] zeroinitializer @main.sliceSrcExternal1.buf = external global [2 x i8] @main.sliceDstExternal1.buf = internal global [2 x i8] zeroinitializer @main.sliceSrcExternal2.buf = internal global [2 x i8] zeroinitializer @main.sliceDstExternal2.buf = external global [2 x i8] declare i64 @runtime.sliceCopy(ptr %dst, ptr %src, i64 %dstLen, i64 %srcLen, i64 %elemSize) unnamed_addr declare ptr @runtime.alloc(i64, ptr) unnamed_addr declare void @runtime.printuint8(i8) declare void @runtime.printint16(i16) declare void @use(ptr) define void @runtime.initAll() unnamed_addr { entry: call void @main.init() ret void } define void @main() unnamed_addr { entry: ; print(uintSliceSrc[0]) %uint8SliceSrc.buf = load ptr, ptr @main.uint8SliceSrc %uint8SliceSrc.val = load i8, ptr %uint8SliceSrc.buf call void @runtime.printuint8(i8 %uint8SliceSrc.val) ; print(uintSliceDst[0]) %uint8SliceDst.buf = load ptr, ptr @main.uint8SliceDst %uint8SliceDst.val = load i8, ptr %uint8SliceDst.buf call void @runtime.printuint8(i8 %uint8SliceDst.val) ; print(int16SliceSrc[0]) %int16SliceSrc.buf = load ptr, ptr @main.int16SliceSrc %int16SliceSrc.val = load i16, ptr %int16SliceSrc.buf call void @runtime.printint16(i16 %int16SliceSrc.val) ; print(int16SliceDst[0]) %int16SliceDst.buf = load ptr, ptr @main.int16SliceDst %int16SliceDst.val = load i16, ptr %int16SliceDst.buf call void @runtime.printint16(i16 %int16SliceDst.val) ; print(sliceDstUntaint[0]) %sliceDstUntaint.val = load i8, ptr getelementptr inbounds (i8, ptr @main.sliceDstUntaint.buf, i32 0) call void @runtime.printuint8(i8 %sliceDstUntaint.val) ; print(sliceDstTaint[0]) %sliceDstTaint.val = load i8, ptr getelementptr inbounds (i8, ptr @main.sliceDstTaint.buf, i32 0) call void @runtime.printuint8(i8 %sliceDstTaint.val) ; print(sliceDstExternal1[0]) %sliceDstExternal1.val = load i8, ptr getelementptr inbounds (i8, ptr @main.sliceDstExternal1.buf, i32 0) call void @runtime.printuint8(i8 %sliceDstExternal1.val) ; print(sliceDstExternal2[0]) %sliceDstExternal2.val = load i8, ptr getelementptr inbounds (i8, ptr @main.sliceDstExternal2.buf, i32 0) call void @runtime.printuint8(i8 %sliceDstExternal2.val) ret void } define internal void @main.init() unnamed_addr { entry: ; equivalent of: ; uint8SliceDst = make([]uint8, len(uint8SliceSrc)) %uint8SliceSrc = load { ptr, i64, i64 }, ptr @main.uint8SliceSrc %uint8SliceSrc.len = extractvalue { ptr, i64, i64 } %uint8SliceSrc, 1 %uint8SliceDst.buf = call ptr @runtime.alloc(i64 %uint8SliceSrc.len, ptr null) %0 = insertvalue { ptr, i64, i64 } undef, ptr %uint8SliceDst.buf, 0 %1 = insertvalue { ptr, i64, i64 } %0, i64 %uint8SliceSrc.len, 1 %2 = insertvalue { ptr, i64, i64 } %1, i64 %uint8SliceSrc.len, 2 store { ptr, i64, i64 } %2, ptr @main.uint8SliceDst ; equivalent of: ; copy(uint8SliceDst, uint8SliceSrc) %uint8SliceSrc.buf = extractvalue { ptr, i64, i64 } %uint8SliceSrc, 0 %copy.n = call i64 @runtime.sliceCopy(ptr %uint8SliceDst.buf, ptr %uint8SliceSrc.buf, i64 %uint8SliceSrc.len, i64 %uint8SliceSrc.len, i64 1) ; equivalent of: ; int16SliceDst = make([]int16, len(int16SliceSrc)) %int16SliceSrc = load { ptr, i64, i64 }, ptr @main.int16SliceSrc %int16SliceSrc.len = extractvalue { ptr, i64, i64 } %int16SliceSrc, 1 %int16SliceSrc.len.bytes = mul i64 %int16SliceSrc.len, 2 %int16SliceDst.buf = call ptr @runtime.alloc(i64 %int16SliceSrc.len.bytes, ptr null) %3 = insertvalue { ptr, i64, i64 } undef, ptr %int16SliceDst.buf, 0 %4 = insertvalue { ptr, i64, i64 } %3, i64 %int16SliceSrc.len, 1 %5 = insertvalue { ptr, i64, i64 } %4, i64 %int16SliceSrc.len, 2 store { ptr, i64, i64 } %5, ptr @main.int16SliceDst ; equivalent of: ; copy(int16SliceDst, int16SliceSrc) %int16SliceSrc.buf = extractvalue { ptr, i64, i64 } %int16SliceSrc, 0 %copy.n2 = call i64 @runtime.sliceCopy(ptr %int16SliceDst.buf, ptr %int16SliceSrc.buf, i64 %int16SliceSrc.len, i64 %int16SliceSrc.len, i64 2) ; Copy slice that has a known value. %copy.n3 = call i64 @runtime.sliceCopy(ptr @main.sliceDstUntaint.buf, ptr @main.sliceSrcUntaint.buf, i64 2, i64 2, i64 1) ; Copy slice that might have been modified by the external @use call. ; This is a fix for https://github.com/tinygo-org/tinygo/issues/3890. call void @use(ptr @main.sliceSrcTaint.buf) %copy.n4 = call i64 @runtime.sliceCopy(ptr @main.sliceDstTaint.buf, ptr @main.sliceSrcTaint.buf, i64 2, i64 2, i64 1) ; Test that copying from or into external buffers works correctly. ; These copy operations must be done at runtime. ; https://github.com/tinygo-org/tinygo/issues/4895 %copy.n5 = call i64 @runtime.sliceCopy(ptr @main.sliceDstExternal1.buf, ptr @main.sliceSrcExternal1.buf, i64 2, i64 2, i64 1) %copy.n6 = call i64 @runtime.sliceCopy(ptr @main.sliceDstExternal2.buf, ptr @main.sliceSrcExternal2.buf, i64 2, i64 2, i64 1) ret void } ================================================ FILE: interp/testdata/slice-copy.out.ll ================================================ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64--linux" @main.sliceSrcTaint.buf = internal global [2 x i8] c"cd" @main.sliceDstTaint.buf = internal global [2 x i8] zeroinitializer @main.sliceSrcExternal1.buf = external global [2 x i8] @main.sliceDstExternal1.buf = internal global [2 x i8] zeroinitializer @main.sliceSrcExternal2.buf = internal global [2 x i8] zeroinitializer @main.sliceDstExternal2.buf = external global [2 x i8] declare i64 @runtime.sliceCopy(ptr, ptr, i64, i64, i64) unnamed_addr declare void @runtime.printuint8(i8) local_unnamed_addr declare void @runtime.printint16(i16) local_unnamed_addr declare void @use(ptr) local_unnamed_addr define void @runtime.initAll() unnamed_addr { entry: call void @use(ptr @main.sliceSrcTaint.buf) %copy.n4 = call i64 @runtime.sliceCopy(ptr @main.sliceDstTaint.buf, ptr @main.sliceSrcTaint.buf, i64 2, i64 2, i64 1) %copy.n5 = call i64 @runtime.sliceCopy(ptr @main.sliceDstExternal1.buf, ptr @main.sliceSrcExternal1.buf, i64 2, i64 2, i64 1) %copy.n6 = call i64 @runtime.sliceCopy(ptr @main.sliceDstExternal2.buf, ptr @main.sliceSrcExternal2.buf, i64 2, i64 2, i64 1) ret void } define void @main() unnamed_addr { entry: call void @runtime.printuint8(i8 3) call void @runtime.printuint8(i8 3) call void @runtime.printint16(i16 5) call void @runtime.printint16(i16 5) call void @runtime.printuint8(i8 97) %sliceDstTaint.val = load i8, ptr @main.sliceDstTaint.buf, align 1 call void @runtime.printuint8(i8 %sliceDstTaint.val) %sliceDstExternal1.val = load i8, ptr @main.sliceDstExternal1.buf, align 1 call void @runtime.printuint8(i8 %sliceDstExternal1.val) %sliceDstExternal2.val = load i8, ptr @main.sliceDstExternal2.buf, align 1 call void @runtime.printuint8(i8 %sliceDstExternal2.val) ret void } ================================================ FILE: lib/picolibc-stdio.c ================================================ // This file is included in the picolibc build. // It makes stdio functions available to the C library. #include #include // Defined in the runtime package. Writes to the default console (usually, the // first UART or an USB-CDC device). int runtime_putchar(char, FILE*); // Define stdin, stdout, and stderr as a single object. // This object must not reside in ROM. static FILE __stdio = FDEV_SETUP_STREAM(runtime_putchar, NULL, NULL, _FDEV_SETUP_WRITE); // Define the underlying structs for stdin, stdout, and stderr. FILE *const stdin = &__stdio; __strong_reference(stdin, stdout); __strong_reference(stdin, stderr); ================================================ FILE: loader/errors.go ================================================ package loader import "go/scanner" // Errors contains a list of parser errors or a list of typechecker errors for // the given package. type Errors struct { Pkg *Package Errs []error } func (e Errors) Error() string { return "could not compile: " + e.Errs[0].Error() } // Error is a regular error but with an added import stack. This is especially // useful for debugging import cycle errors. type Error struct { ImportStack []string Err scanner.Error } func (e Error) Error() string { return e.Err.Error() } // Error returned when loading a *Program for a test binary but no test files // are present. type NoTestFilesError struct { ImportPath string } func (e NoTestFilesError) Error() string { return "no test files" } ================================================ FILE: loader/goroot.go ================================================ package loader // This file constructs a new temporary GOROOT directory by merging both the // standard Go GOROOT and the GOROOT from TinyGo using symlinks. // // The goal is to replace specific packages from Go with a TinyGo version. It's // never a partial replacement, either a package is fully replaced or it is not. // This is important because if we did allow to merge packages (e.g. by adding // files to a package), it would lead to a dependency on implementation details // with all the maintenance burden that results in. Only allowing to replace // packages as a whole avoids this as packages are already designed to have a // public (backwards-compatible) API. import ( "crypto/sha512" "encoding/hex" "encoding/json" "errors" "io" "io/fs" "os" "os/exec" "path" "path/filepath" "runtime" "sort" "sync" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/goenv" ) var gorootCreateMutex sync.Mutex // GetCachedGoroot creates a new GOROOT by merging both the standard GOROOT and // the GOROOT from TinyGo using lots of symbolic links. func GetCachedGoroot(config *compileopts.Config) (string, error) { goroot := goenv.Get("GOROOT") if goroot == "" { return "", errors.New("could not determine GOROOT") } tinygoroot := goenv.Get("TINYGOROOT") if tinygoroot == "" { return "", errors.New("could not determine TINYGOROOT") } // Find the overrides needed for the goroot. overrides := pathsToOverride(config.GoMinorVersion, needsSyscallPackage(config.BuildTags())) // Resolve the merge links within the goroot. merge, err := listGorootMergeLinks(goroot, tinygoroot, overrides) if err != nil { return "", err } // Hash the merge links to create a cache key. data, err := json.Marshal(merge) if err != nil { return "", err } hash := sha512.Sum512_256(data) // Do not try to create the cached GOROOT in parallel, that's only a waste // of I/O bandwidth and thus speed. Instead, use a mutex to make sure only // one goroutine does it at a time. // This is not a way to ensure atomicity (a different TinyGo invocation // could be creating the same directory), but instead a way to avoid // creating it many times in parallel when running tests in parallel. gorootCreateMutex.Lock() defer gorootCreateMutex.Unlock() // Check if the goroot already exists. cachedGorootName := "goroot-" + hex.EncodeToString(hash[:]) cachedgoroot := filepath.Join(goenv.Get("GOCACHE"), cachedGorootName) if _, err := os.Stat(cachedgoroot); err == nil { return cachedgoroot, nil } // Create the cache directory if it does not already exist. err = os.MkdirAll(goenv.Get("GOCACHE"), 0777) if err != nil { return "", err } // Create a temporary directory to construct the goroot within. tmpgoroot, err := os.MkdirTemp(goenv.Get("GOCACHE"), cachedGorootName+".tmp") if err != nil { return "", err } // Remove the temporary directory if it wasn't moved to the right place // (for example, when there was an error). defer os.RemoveAll(tmpgoroot) // Create the directory structure. // The directories are created in sorted order so that nested directories are created without extra work. { var dirs []string for dir, merge := range overrides { if merge { dirs = append(dirs, filepath.Join(tmpgoroot, "src", dir)) } } sort.Strings(dirs) for _, dir := range dirs { err := os.Mkdir(dir, 0777) if err != nil { return "", err } } } // Create all symlinks. for dst, src := range merge { err := symlink(src, filepath.Join(tmpgoroot, dst)) if err != nil { return "", err } } // Rename the new merged gorooot into place. err = os.Rename(tmpgoroot, cachedgoroot) if err != nil { if errors.Is(err, fs.ErrExist) { // Another invocation of TinyGo also seems to have created a GOROOT. // Use that one instead. Our new GOROOT will be automatically // deleted by the defer above. return cachedgoroot, nil } if runtime.GOOS == "windows" && errors.Is(err, fs.ErrPermission) { // On Windows, a rename with a destination directory that already // exists does not result in an IsExist error, but rather in an // access denied error. To be sure, check for this case by checking // whether the target directory exists. if _, err := os.Stat(cachedgoroot); err == nil { return cachedgoroot, nil } } return "", err } return cachedgoroot, nil } // listGorootMergeLinks searches goroot and tinygoroot for all symlinks that must be created within the merged goroot. func listGorootMergeLinks(goroot, tinygoroot string, overrides map[string]bool) (map[string]string, error) { goSrc := filepath.Join(goroot, "src") tinygoSrc := filepath.Join(tinygoroot, "src") merges := make(map[string]string) for dir, merge := range overrides { if !merge { // Use the TinyGo version. merges[filepath.Join("src", dir)] = filepath.Join(tinygoSrc, dir) continue } // Add files from TinyGo. tinygoDir := filepath.Join(tinygoSrc, dir) tinygoEntries, err := os.ReadDir(tinygoDir) if err != nil { return nil, err } var hasTinyGoFiles bool for _, e := range tinygoEntries { if e.IsDir() { continue } // Link this file. name := e.Name() merges[filepath.Join("src", dir, name)] = filepath.Join(tinygoDir, name) hasTinyGoFiles = true } // Add all directories from $GOROOT that are not part of the TinyGo // overrides. goDir := filepath.Join(goSrc, dir) goEntries, err := os.ReadDir(goDir) if err != nil { return nil, err } for _, e := range goEntries { isDir := e.IsDir() if hasTinyGoFiles && !isDir { // Only merge files from Go if TinyGo does not have any files. // Otherwise we'd end up with a weird mix from both Go // implementations. continue } name := e.Name() if _, ok := overrides[path.Join(dir, name)+"/"]; ok { // This entry is overridden by TinyGo. // It has/will be merged elsewhere. continue } // Add a link to this entry merges[filepath.Join("src", dir, name)] = filepath.Join(goDir, name) } } // Merge the special directories from goroot. for _, dir := range []string{"bin", "lib", "pkg"} { merges[dir] = filepath.Join(goroot, dir) } // Required starting in Go 1.21 due to https://github.com/golang/go/issues/61928 if _, err := os.Stat(filepath.Join(goroot, "go.env")); err == nil { merges["go.env"] = filepath.Join(goroot, "go.env") } return merges, nil } // needsSyscallPackage returns whether the syscall package should be overridden // with the TinyGo version. This is the case on some targets. func needsSyscallPackage(buildTags []string) bool { for _, tag := range buildTags { if tag == "baremetal" || tag == "nintendoswitch" || tag == "tinygo.wasm" { return true } } return false } // The boolean indicates whether to merge the subdirs. True means merge, false // means use the TinyGo version. func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { paths := map[string]bool{ "": true, "crypto/": true, "crypto/rand/": false, "crypto/tls/": false, "crypto/x509/": true, "crypto/x509/internal/": true, "crypto/x509/internal/macos/": false, "device/": false, "examples/": false, "internal/": true, "internal/abi/": false, "internal/binary/": false, "internal/bytealg/": false, "internal/cm/": false, "internal/futex/": false, "internal/fuzz/": false, "internal/reflectlite/": false, "internal/gclayout": false, "internal/task/": false, "internal/wasi/": false, "machine/": false, "net/": true, "net/http/": false, "os/": true, "reflect/": false, "runtime/": false, "sync/": true, "testing/": true, "tinygo/": false, "unique/": false, } if goMinor >= 19 { paths["crypto/internal/"] = true paths["crypto/internal/boring/"] = true paths["crypto/internal/boring/sig/"] = false } if needsSyscallPackage { paths["syscall/"] = true // include syscall/js paths["internal/syscall/"] = true paths["internal/syscall/unix/"] = false } return paths } // symlink creates a symlink or something similar. On Unix-like systems, it // always creates a symlink. On Windows, it tries to create a symlink and if // that fails, creates a hardlink or directory junction instead. // // Note that while Windows 10 does support symlinks and allows them to be // created using os.Symlink, it requires developer mode to be enabled. // Therefore provide a fallback for when symlinking is not possible. // Unfortunately this fallback only works when TinyGo is installed on the same // filesystem as the TinyGo cache and the Go installation (which is usually the // C drive). func symlink(oldname, newname string) error { symlinkErr := os.Symlink(oldname, newname) if runtime.GOOS == "windows" && symlinkErr != nil { // Fallback for when developer mode is disabled. // Note that we return the symlink error even if something else fails // later on. This is because symlinks are the easiest to support // (they're also used on Linux and MacOS) and enabling them is easy: // just enable developer mode. st, err := os.Stat(oldname) if err != nil { return symlinkErr } if st.IsDir() { // Make a directory junction. There may be a way to do this // programmatically, but it involves a lot of magic. Use the mklink // command built into cmd instead (mklink is a builtin, not an // external command). err := exec.Command("cmd", "/k", "mklink", "/J", newname, oldname).Run() if err != nil { return symlinkErr } } else { // Try making a hard link. err := os.Link(oldname, newname) if err != nil { // Making a hardlink failed. Try copying the file as a last // fallback. inf, err := os.Open(oldname) if err != nil { return err } defer inf.Close() outf, err := os.Create(newname) if err != nil { return err } defer outf.Close() _, err = io.Copy(outf, inf) if err != nil { os.Remove(newname) return err } // File was copied. } } return nil // success } return symlinkErr } ================================================ FILE: loader/list.go ================================================ package loader import ( "os" "os/exec" "path/filepath" "strings" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/goenv" ) // List returns a ready-to-run *exec.Cmd for running the `go list` command with // the configuration used for TinyGo. func List(config *compileopts.Config, extraArgs, pkgs []string) (*exec.Cmd, error) { goroot, err := GetCachedGoroot(config) if err != nil { return nil, err } args := append([]string{"list"}, extraArgs...) if len(config.BuildTags()) != 0 { args = append(args, "-tags", strings.Join(config.BuildTags(), " ")) } args = append(args, pkgs...) cmd := exec.Command(filepath.Join(goenv.Get("GOROOT"), "bin", "go"), args...) cmd.Env = append(os.Environ(), "GOROOT="+goroot, "GOOS="+config.GOOS(), "GOARCH="+config.GOARCH(), "CGO_ENABLED=1") if config.Options.Directory != "" { cmd.Dir = config.Options.Directory } return cmd, nil } ================================================ FILE: loader/loader.go ================================================ package loader import ( "bytes" "crypto/sha512" "encoding/json" "errors" "fmt" "go/ast" "go/constant" "go/parser" "go/scanner" "go/token" "go/types" "io" "os" "os/exec" "path" "path/filepath" "runtime" "strconv" "strings" "unicode" "github.com/tinygo-org/tinygo/cgo" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/goenv" ) var initFileVersions = func(info *types.Info) {} // Program holds all packages and some metadata about the program as a whole. type Program struct { config *compileopts.Config typeChecker types.Config goroot string // synthetic GOROOT workingDir string Packages map[string]*Package sorted []*Package fset *token.FileSet // Information obtained during parsing. LDFlags []string } // PackageJSON is a subset of the JSON struct returned from `go list`. type PackageJSON struct { Dir string ImportPath string Name string ForTest string Root string Module struct { Path string Main bool Dir string GoMod string GoVersion string } // Source files GoFiles []string CgoFiles []string CFiles []string // Embedded files EmbedFiles []string // Dependency information Imports []string ImportMap map[string]string // Error information Error *struct { ImportStack []string Pos string Err string } } // Package holds a loaded package, its imports, and its parsed files. type Package struct { PackageJSON program *Program Files []*ast.File FileHashes map[string][]byte CFlags []string // CFlags used during CGo preprocessing (only set if CGo is used) CGoHeaders []string // text above 'import "C"' lines EmbedGlobals map[string][]*EmbedFile Pkg *types.Package info types.Info } type EmbedFile struct { Name string Size uint64 Hash string // hash of the file (as a hex string) NeedsData bool // true if this file is embedded as a byte slice Data []byte // contents of this file (only if NeedsData is set) } // Load loads the given package with all dependencies (including the runtime // package). Call .Parse() afterwards to parse all Go files (including CGo // processing, if necessary). func Load(config *compileopts.Config, inputPkg string, typeChecker types.Config) (*Program, error) { goroot, err := GetCachedGoroot(config) if err != nil { return nil, err } var wd string if config.Options.Directory != "" { wd = config.Options.Directory } else { wd, err = os.Getwd() if err != nil { return nil, err } } p := &Program{ config: config, typeChecker: typeChecker, goroot: goroot, workingDir: wd, Packages: make(map[string]*Package), fset: token.NewFileSet(), } // List the dependencies of this package, in raw JSON format. extraArgs := []string{"-json", "-deps", "-e"} if config.TestConfig.CompileTestBinary { extraArgs = append(extraArgs, "-test") } cmd, err := List(config, extraArgs, []string{inputPkg}) if err != nil { return nil, err } buf := &bytes.Buffer{} cmd.Stdout = buf cmd.Stderr = os.Stderr err = cmd.Run() if err != nil { if exitErr, ok := err.(*exec.ExitError); ok { os.Exit(exitErr.ExitCode()) } return nil, fmt.Errorf("failed to run `go list`: %s", err) } // Parse the returned json from `go list`. decoder := json.NewDecoder(buf) var pkgErrors []error for { pkg := &Package{ program: p, FileHashes: make(map[string][]byte), EmbedGlobals: make(map[string][]*EmbedFile), info: types.Info{ Types: make(map[ast.Expr]types.TypeAndValue), Instances: make(map[*ast.Ident]types.Instance), Defs: make(map[*ast.Ident]types.Object), Uses: make(map[*ast.Ident]types.Object), Implicits: make(map[ast.Node]types.Object), Scopes: make(map[ast.Node]*types.Scope), Selections: make(map[*ast.SelectorExpr]*types.Selection), }, } err := decoder.Decode(&pkg.PackageJSON) if err != nil { if err == io.EOF { break } return nil, err } if pkg.Error != nil { // There was an error while importing (for example, a circular // dependency). pos := token.Position{} fields := strings.Split(pkg.Error.Pos, ":") if len(fields) >= 2 { // There is some file/line/column information. if n, err := strconv.Atoi(fields[len(fields)-2]); err == nil { // Format: filename.go:line:column pos.Filename = strings.Join(fields[:len(fields)-2], ":") pos.Line = n pos.Column, _ = strconv.Atoi(fields[len(fields)-1]) } else { // Format: filename.go:line pos.Filename = strings.Join(fields[:len(fields)-1], ":") pos.Line, _ = strconv.Atoi(fields[len(fields)-1]) } if abs, err := filepath.Abs(pos.Filename); err == nil { // Make the path absolute, so that error messages will be // prettier (it will be turned back into a relative path // when printing the error). pos.Filename = abs } pos.Filename = p.getOriginalPath(pos.Filename) } err := scanner.Error{ Pos: pos, Msg: pkg.Error.Err, } if len(pkg.Error.ImportStack) != 0 { pkgErrors = append(pkgErrors, Error{ ImportStack: pkg.Error.ImportStack, Err: err, }) continue } return nil, err } if config.TestConfig.CompileTestBinary { // When creating a test binary, `go list` will list two or three // packages used for testing the package. The first is the original // package as if it were built normally, the second is the same // package but with the *_test.go files included. A possible third // may be included for _test packages (such as math_test), used to // test the external API with no access to internal functions. // All packages that are necessary for testing (including the to be // tested package with *_test.go files, but excluding the original // unmodified package) have a suffix added to the import path, for // example the math package has import path "math [math.test]" and // test dependencies such as fmt will have an import path of the // form "fmt [math.test]". // The code below removes this suffix, and if this results in a // duplicate (which happens with the to-be-tested package without // *.test.go files) the previous package is removed from the list of // packages included in this build. // This is necessary because the change in import paths results in // breakage to //go:linkname. Additionally, the duplicated package // slows down the build and so is best removed. if pkg.ForTest != "" && strings.HasSuffix(pkg.ImportPath, " ["+pkg.ForTest+".test]") { newImportPath := pkg.ImportPath[:len(pkg.ImportPath)-len(" ["+pkg.ForTest+".test]")] if _, ok := p.Packages[newImportPath]; ok { // Delete the previous package (that this package overrides). delete(p.Packages, newImportPath) for i, pkg := range p.sorted { if pkg.ImportPath == newImportPath { p.sorted = append(p.sorted[:i], p.sorted[i+1:]...) // remove element from slice break } } } pkg.ImportPath = newImportPath } } p.sorted = append(p.sorted, pkg) p.Packages[pkg.ImportPath] = pkg } if len(pkgErrors) != 0 { // TODO: use errors.Join in Go 1.20. return nil, Errors{ Errs: pkgErrors, } } if config.TestConfig.CompileTestBinary && !strings.HasSuffix(p.sorted[len(p.sorted)-1].ImportPath, ".test") { // Trying to compile a test binary but there are no test files in this // package. return p, NoTestFilesError{p.sorted[len(p.sorted)-1].ImportPath} } return p, nil } // getOriginalPath looks whether this path is in the generated GOROOT and if so, // replaces the path with the original path (in GOROOT or TINYGOROOT). Otherwise // the input path is returned. func (p *Program) getOriginalPath(path string) string { originalPath := path if strings.HasPrefix(path, p.goroot+string(filepath.Separator)) { // If this file is part of the synthetic GOROOT, try to infer the // original path. relpath := path[len(filepath.Join(p.goroot, "src"))+1:] realgorootPath := filepath.Join(goenv.Get("GOROOT"), "src", relpath) if _, err := os.Stat(realgorootPath); err == nil { originalPath = realgorootPath } maybeInTinyGoRoot := false for prefix := range pathsToOverride(p.config.GoMinorVersion, needsSyscallPackage(p.config.BuildTags())) { if runtime.GOOS == "windows" { prefix = strings.ReplaceAll(prefix, "/", "\\") } if !strings.HasPrefix(relpath, prefix) { continue } maybeInTinyGoRoot = true } if maybeInTinyGoRoot { tinygoPath := filepath.Join(goenv.Get("TINYGOROOT"), "src", relpath) if _, err := os.Stat(tinygoPath); err == nil { originalPath = tinygoPath } } } return originalPath } // Sorted returns a list of all packages, sorted in a way that no packages come // before the packages they depend upon. func (p *Program) Sorted() []*Package { return p.sorted } // MainPkg returns the last package in the Sorted() slice. This is the main // package of the program. func (p *Program) MainPkg() *Package { return p.sorted[len(p.sorted)-1] } // Parse parses all packages and typechecks them. // // The returned error may be an Errors error, which contains a list of errors. // // Idempotent. func (p *Program) Parse() error { // Parse all packages. // TODO: do this in parallel. for _, pkg := range p.sorted { err := pkg.Parse() if err != nil { return err } } // Typecheck all packages. for _, pkg := range p.sorted { err := pkg.Check() if err != nil { return err } } return nil } // OriginalDir returns the real directory name. It is the same as p.Dir except // that if it is part of the cached GOROOT, its real location is returned. func (p *Package) OriginalDir() string { return strings.TrimSuffix(p.program.getOriginalPath(p.Dir+string(os.PathSeparator)), string(os.PathSeparator)) } // parseFile is a wrapper around parser.ParseFile. func (p *Package) parseFile(path string, mode parser.Mode) (*ast.File, error) { originalPath := p.program.getOriginalPath(path) data, err := os.ReadFile(path) if err != nil { return nil, err } sum := sha512.Sum512_224(data) p.FileHashes[originalPath] = sum[:] return parser.ParseFile(p.program.fset, originalPath, data, mode) } // Parse parses and typechecks this package. // // Idempotent. func (p *Package) Parse() error { if len(p.Files) != 0 { return nil // nothing to do (?) } // Load the AST. if p.ImportPath == "unsafe" { // Special case for the unsafe package, which is defined internally by // the types package. p.Pkg = types.Unsafe return nil } files, err := p.parseFiles() if err != nil { return err } p.Files = files return nil } // Check runs the package through the typechecker. The package must already be // loaded and all dependencies must have been checked already. // // Idempotent. func (p *Package) Check() error { if p.Pkg != nil { return nil // already typechecked } // Prepare some state used during type checking. var typeErrors []error checker := p.program.typeChecker // make a copy, because it will be modified checker.Error = func(err error) { typeErrors = append(typeErrors, err) } checker.Importer = p if p.Module.GoVersion != "" { // Setting the Go version for a module makes sure the type checker // errors out on language features not supported in that particular // version. checker.GoVersion = "go" + p.Module.GoVersion } else { // Version is not known, so use the currently installed Go version. // This is needed for `tinygo run` for example. // Normally we'd use goenv.GorootVersionString(), but for compatibility // with Go 1.20 and below we need a version in the form of "go1.12" (no // patch version). major, minor, err := goenv.GetGorootVersion() if err != nil { return err } checker.GoVersion = fmt.Sprintf("go%d.%d", major, minor) } initFileVersions(&p.info) // Do typechecking of the package. packageName := p.ImportPath if p == p.program.MainPkg() { if p.Name != "main" { return Errors{p, []error{ scanner.Error{ Pos: p.program.fset.Position(p.Files[0].Name.Pos()), Msg: fmt.Sprintf("expected main package to have name \"main\", not %#v", p.Name), }, }} } packageName = "main" } typesPkg, err := checker.Check(packageName, p.program.fset, p.Files, &p.info) if err != nil { if err, ok := err.(Errors); ok { return err } if len(typeErrors) != 0 { // Got type errors, so return them. return Errors{p, typeErrors} } // This can happen in some weird cases. // The only case I know is when compiling a Go 1.23 program, with a // TinyGo version that supports Go 1.23 but is compiled using Go 1.22. // So this should be pretty rare. return Errors{p, []error{err}} } p.Pkg = typesPkg p.extractEmbedLines(checker.Error) if len(typeErrors) != 0 { return Errors{p, typeErrors} } return nil } // parseFiles parses the loaded list of files and returns this list. func (p *Package) parseFiles() ([]*ast.File, error) { var files []*ast.File var fileErrs []error // Parse all files (including CgoFiles). parseFile := func(file string) { if !filepath.IsAbs(file) { file = filepath.Join(p.Dir, file) } f, err := p.parseFile(file, parser.ParseComments) if err != nil { fileErrs = append(fileErrs, err) return } files = append(files, f) } for _, file := range p.GoFiles { parseFile(file) } for _, file := range p.CgoFiles { parseFile(file) } // Do CGo processing. // This is done when there are any CgoFiles at all. In that case, len(files) // should be non-zero. However, if len(GoFiles) == 0 and len(CgoFiles) == 1 // and there is a syntax error in a CGo file, len(files) may be 0. Don't try // to call cgo.Process in that case as it will only cause issues. if len(p.CgoFiles) != 0 && len(files) != 0 { var initialCFlags []string initialCFlags = append(initialCFlags, p.program.config.CFlags(true)...) initialCFlags = append(initialCFlags, "-I"+p.Dir) generated, headerCode, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.ImportPath, p.program.fset, initialCFlags, p.program.config.GOOS()) p.CFlags = append(initialCFlags, cflags...) p.CGoHeaders = headerCode for path, hash := range accessedFiles { p.FileHashes[path] = hash } if errs != nil { fileErrs = append(fileErrs, errs...) } files = append(files, generated...) p.program.LDFlags = append(p.program.LDFlags, ldflags...) } // Only return an error after CGo processing, so that errors in parsing and // CGo can be reported together. if len(fileErrs) != 0 { return nil, Errors{p, fileErrs} } return files, nil } // extractEmbedLines finds all //go:embed lines in the package and matches them // against EmbedFiles from `go list`. func (p *Package) extractEmbedLines(addError func(error)) { for _, file := range p.Files { // Check for an `import "embed"` line at the start of the file. // //go:embed lines are only valid if the given file itself imports the // embed package. It is not valid if it is only imported in a separate // Go file. hasEmbed := false for _, importSpec := range file.Imports { if importSpec.Path.Value == `"embed"` { hasEmbed = true } } for _, decl := range file.Decls { switch decl := decl.(type) { case *ast.GenDecl: if decl.Tok != token.VAR { continue } for _, spec := range decl.Specs { spec := spec.(*ast.ValueSpec) var doc *ast.CommentGroup if decl.Lparen == token.NoPos { // Plain 'var' declaration, like: // //go:embed hello.txt // var hello string doc = decl.Doc } else { // Bigger 'var' declaration like: // var ( // //go:embed hello.txt // hello string // ) doc = spec.Doc } if doc == nil { continue } // Look for //go:embed comments. var allPatterns []string for _, comment := range doc.List { if comment.Text != "//go:embed" && !strings.HasPrefix(comment.Text, "//go:embed ") { continue } if !hasEmbed { addError(types.Error{ Fset: p.program.fset, Pos: comment.Pos() + 2, Msg: "//go:embed only allowed in Go files that import \"embed\"", }) // Continue, because otherwise we might run into // issues below. continue } patterns, err := p.parseGoEmbed(comment.Text[len("//go:embed"):], comment.Slash) if err != nil { addError(err) continue } if len(patterns) == 0 { addError(types.Error{ Fset: p.program.fset, Pos: comment.Pos() + 2, Msg: "usage: //go:embed pattern...", }) continue } for _, pattern := range patterns { // Check that the pattern is well-formed. // It must be valid: the Go toolchain has already // checked for invalid patterns. But let's check // anyway to be sure. if _, err := path.Match(pattern, ""); err != nil { addError(types.Error{ Fset: p.program.fset, Pos: comment.Pos(), Msg: "invalid pattern syntax", }) continue } allPatterns = append(allPatterns, pattern) } } if len(allPatterns) != 0 { // This is a //go:embed global. Do a few more checks. if len(spec.Names) != 1 { addError(types.Error{ Fset: p.program.fset, Pos: spec.Names[1].NamePos, Msg: "//go:embed cannot apply to multiple vars", }) } if spec.Values != nil { addError(types.Error{ Fset: p.program.fset, Pos: spec.Values[0].Pos(), Msg: "//go:embed cannot apply to var with initializer", }) } globalName := spec.Names[0].Name globalType := p.Pkg.Scope().Lookup(globalName).Type() valid, byteSlice := isValidEmbedType(globalType) if !valid { addError(types.Error{ Fset: p.program.fset, Pos: spec.Type.Pos(), Msg: "//go:embed cannot apply to var of type " + globalType.String(), }) } // Match all //go:embed patterns against the embed files // provided by `go list`. for _, name := range p.EmbedFiles { for _, pattern := range allPatterns { if matchPattern(pattern, name) { p.EmbedGlobals[globalName] = append(p.EmbedGlobals[globalName], &EmbedFile{ Name: name, NeedsData: byteSlice, }) break } } } } } } } } } // matchPattern returns true if (and only if) the given pattern would match the // filename. The pattern could also match a parent directory of name, in which // case hidden files do not match. func matchPattern(pattern, name string) bool { // Match this file. matched, _ := path.Match(pattern, name) if matched { return true } // Match parent directories. dir := name for { dir, _ = path.Split(dir) if dir == "" { return false } dir = path.Clean(dir) if matched, _ := path.Match(pattern, dir); matched { // Pattern matches the directory. suffix := name[len(dir):] if strings.Contains(suffix, "/_") || strings.Contains(suffix, "/.") { // Pattern matches a hidden file. // Hidden files are included when listed directly as a // pattern, but not when they are part of a directory tree. // Source: // > If a pattern names a directory, all files in the // > subtree rooted at that directory are embedded // > (recursively), except that files with names beginning // > with ‘.’ or ‘_’ are excluded. return false } return true } } } // parseGoEmbed is like strings.Fields but for a //go:embed line. It parses // regular fields and quoted fields (that may contain spaces). func (p *Package) parseGoEmbed(args string, pos token.Pos) (patterns []string, err error) { args = strings.TrimSpace(args) initialLen := len(args) for args != "" { patternPos := pos + token.Pos(initialLen-len(args)) switch args[0] { case '`', '"', '\\': // Parse the next pattern using the Go scanner. // This is perhaps a bit overkill, but it does correctly implement // parsing of the various Go strings. var sc scanner.Scanner fset := &token.FileSet{} file := fset.AddFile("", 0, len(args)) sc.Init(file, []byte(args), nil, 0) _, tok, lit := sc.Scan() if tok != token.STRING || sc.ErrorCount != 0 { // Calculate start of token return nil, types.Error{ Fset: p.program.fset, Pos: patternPos, Msg: "invalid quoted string in //go:embed", } } pattern := constant.StringVal(constant.MakeFromLiteral(lit, tok, 0)) patterns = append(patterns, pattern) args = strings.TrimLeftFunc(args[len(lit):], unicode.IsSpace) default: // The value is just a regular value. // Split it at the first white space. index := strings.IndexFunc(args, unicode.IsSpace) if index < 0 { index = len(args) } pattern := args[:index] patterns = append(patterns, pattern) args = strings.TrimLeftFunc(args[len(pattern):], unicode.IsSpace) } if _, err := path.Match(patterns[len(patterns)-1], ""); err != nil { return nil, types.Error{ Fset: p.program.fset, Pos: patternPos, Msg: "invalid pattern syntax", } } } return patterns, nil } // isValidEmbedType returns whether the given Go type can be used as a // //go:embed type. This is only true for embed.FS, strings, and byte slices. // The second return value indicates that this is a byte slice, and therefore // the contents of the file needs to be passed to the compiler. func isValidEmbedType(typ types.Type) (valid, byteSlice bool) { if typ.Underlying() == types.Typ[types.String] { // string type return true, false } if sliceType, ok := typ.Underlying().(*types.Slice); ok { if elemType, ok := sliceType.Elem().Underlying().(*types.Basic); ok && elemType.Kind() == types.Byte { // byte slice type return true, true } } if namedType, ok := typ.(*types.Named); ok && namedType.String() == "embed.FS" { // embed.FS type return true, false } return false, false } // Import implements types.Importer. It loads and parses packages it encounters // along the way, if needed. func (p *Package) Import(to string) (*types.Package, error) { if to == "unsafe" { return types.Unsafe, nil } if newTo, ok := p.ImportMap[to]; ok && !strings.HasSuffix(newTo, ".test]") { to = newTo } if imported, ok := p.program.Packages[to]; ok { return imported.Pkg, nil } else { return nil, errors.New("package not imported: " + to) } } ================================================ FILE: loader/loader_go122.go ================================================ //go:build go1.22 // types.Info.FileVersions was added in Go 1.22, so we can only initialize it // when built with Go 1.22. package loader import ( "go/ast" "go/types" ) func init() { initFileVersions = func(info *types.Info) { info.FileVersions = make(map[*ast.File]string) } } ================================================ FILE: loader/ssa.go ================================================ package loader import ( "golang.org/x/tools/go/ssa" ) // LoadSSA constructs the SSA form of the loaded packages. // // The program must already be parsed and type-checked with the .Parse() method. func (p *Program) LoadSSA() *ssa.Program { // TODO: re-enable SanityCheckFunctions when x/tools is upgraded to // a version with a fix for https://golang.org/issues/73594. prog := ssa.NewProgram(p.fset /*ssa.SanityCheckFunctions|*/, ssa.BareInits|ssa.GlobalDebug|ssa.InstantiateGenerics) for _, pkg := range p.sorted { prog.CreatePackage(pkg.Pkg, pkg.Files, &pkg.info, true) } return prog } // LoadSSA constructs the SSA form of this package. // // The program must already be parsed and type-checked with the .Parse() method. func (p *Package) LoadSSA() *ssa.Package { prog := ssa.NewProgram(p.program.fset, ssa.SanityCheckFunctions|ssa.BareInits|ssa.GlobalDebug) return prog.CreatePackage(p.Pkg, p.Files, &p.info, true) } ================================================ FILE: main.go ================================================ package main import ( "bufio" "bytes" "context" "encoding/json" "errors" "flag" "fmt" "io" "os" "os/exec" "os/signal" "path/filepath" "regexp" "runtime" "runtime/pprof" "sort" "strconv" "strings" "sync" "sync/atomic" "time" "github.com/google/shlex" "github.com/inhies/go-bytesize" "github.com/mattn/go-colorable" "github.com/tinygo-org/tinygo/builder" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/diagnostics" "github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/loader" "golang.org/x/tools/go/buildutil" "tinygo.org/x/go-llvm" "go.bug.st/serial" "go.bug.st/serial/enumerator" ) // commandError is an error type to wrap os/exec.Command errors. This provides // some more information regarding what went wrong while running a command. type commandError struct { Msg string File string Err error } func (e *commandError) Error() string { return e.Msg + " " + e.File + ": " + e.Err.Error() } // moveFile renames the file from src to dst. If renaming doesn't work (for // example, the rename crosses a filesystem boundary), the file is copied and // the old file is removed. func moveFile(src, dst string) error { err := os.Rename(src, dst) if err == nil { // Success! return nil } // Failed to move, probably a different filesystem. // Do a copy + remove. err = copyFile(src, dst) if err != nil { return err } return os.Remove(src) } // copyFile copies the given file or directory from src to dst. It can copy over // a possibly already existing file (but not directory) at the destination. func copyFile(src, dst string) error { source, err := os.Open(src) if err != nil { return err } defer source.Close() st, err := source.Stat() if err != nil { return err } if st.IsDir() { err := os.Mkdir(dst, st.Mode().Perm()) if err != nil { return err } names, err := source.Readdirnames(0) if err != nil { return err } for _, name := range names { err := copyFile(filepath.Join(src, name), filepath.Join(dst, name)) if err != nil { return err } } return nil } else { destination, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, st.Mode()) if err != nil { return err } defer destination.Close() _, err = io.Copy(destination, source) return err } } // executeCommand is a simple wrapper to exec.Cmd func executeCommand(options *compileopts.Options, name string, arg ...string) *exec.Cmd { if options.PrintCommands != nil { options.PrintCommands(name, arg...) } return exec.Command(name, arg...) } // printCommand prints a command to stdout while formatting it like a real // command (escaping characters etc). The resulting command should be easy to // run directly in a shell, although it is not guaranteed to be a safe shell // escape. That's not a problem as the primary use case is printing the command, // not running it. func printCommand(cmd string, args ...string) { command := append([]string{cmd}, args...) for i, arg := range command { // Source: https://www.oreilly.com/library/view/learning-the-bash/1565923472/ch01s09.html const specialChars = "~`#$&*()\\|[]{};'\"<>?! " if strings.ContainsAny(arg, specialChars) { // See: https://stackoverflow.com/questions/15783701/which-characters-need-to-be-escaped-when-using-bash arg = "'" + strings.ReplaceAll(arg, `'`, `'\''`) + "'" command[i] = arg } } fmt.Fprintln(os.Stderr, strings.Join(command, " ")) } // Build compiles and links the given package and writes it to outpath. func Build(pkgName, outpath string, config *compileopts.Config) error { // Create a temporary directory for intermediary files. tmpdir, err := os.MkdirTemp("", "tinygo") if err != nil { return err } if !config.Options.Work { defer os.RemoveAll(tmpdir) } // Do the build. result, err := builder.Build(pkgName, outpath, tmpdir, config) if err != nil { return err } if result.Binary != "" { // If result.Binary is set, it means there is a build output (elf, hex, // etc) that we need to move to the outpath. If it isn't set, it means // the build output was a .ll, .bc or .o file that has already been // written to outpath and so we don't need to do anything. if outpath == "" { if strings.HasSuffix(pkgName, ".go") { // A Go file was specified directly on the command line. // Base the binary name off of it. outpath = filepath.Base(pkgName[:len(pkgName)-3]) + config.DefaultBinaryExtension() } else { // Pick a default output path based on the main directory. outpath = filepath.Base(result.MainDir) + config.DefaultBinaryExtension() } } if err := os.Rename(result.Binary, outpath); err != nil { // Moving failed. Do a file copy. inf, err := os.Open(result.Binary) if err != nil { return err } defer inf.Close() outf, err := os.OpenFile(outpath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777) if err != nil { return err } // Copy data to output file. _, err = io.Copy(outf, inf) if err != nil { return err } // Check whether file writing was successful. return outf.Close() } } // Move was successful. return nil } // Test runs the tests in the given package. Returns whether the test passed and // possibly an error if the test failed to run. func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options, outpath string) (bool, error) { options.TestConfig.CompileTestBinary = true config, err := builder.NewConfig(options) if err != nil { return false, err } testConfig := &options.TestConfig // Pass test flags to the test binary. var flags []string if testConfig.Verbose { flags = append(flags, "-test.v") } if testConfig.Short { flags = append(flags, "-test.short") } if testConfig.RunRegexp != "" { flags = append(flags, "-test.run="+testConfig.RunRegexp) } if testConfig.SkipRegexp != "" { flags = append(flags, "-test.skip="+testConfig.SkipRegexp) } if testConfig.BenchRegexp != "" { flags = append(flags, "-test.bench="+testConfig.BenchRegexp) } if testConfig.BenchTime != "" { flags = append(flags, "-test.benchtime="+testConfig.BenchTime) } if testConfig.BenchMem { flags = append(flags, "-test.benchmem") } if testConfig.Count != nil && *testConfig.Count != 1 { flags = append(flags, "-test.count="+strconv.Itoa(*testConfig.Count)) } if testConfig.Shuffle != "" { flags = append(flags, "-test.shuffle="+testConfig.Shuffle) } logToStdout := testConfig.Verbose || testConfig.BenchRegexp != "" var buf bytes.Buffer var output io.Writer = &buf // Send the test output to stdout if -v or -bench if logToStdout { output = os.Stdout } passed := false var duration time.Duration result, err := buildAndRun(pkgName, config, output, flags, nil, 0, func(cmd *exec.Cmd, result builder.BuildResult) error { if testConfig.CompileOnly || outpath != "" { // Write test binary to the specified file name. if outpath == "" { // No -o path was given, so create one now. // This matches the behavior of go test. outpath = filepath.Base(result.MainDir) + ".test" } copyFile(result.Binary, outpath) } if testConfig.CompileOnly { // Do not run the test. passed = true return nil } // Tests are always run in the package directory. cmd.Dir = result.MainDir // Run the test. start := time.Now() err = cmd.Run() duration = time.Since(start) passed = err == nil // if verbose or benchmarks, then output is already going to stdout // However, if we failed and weren't printing to stdout, print the output we accumulated. if !passed && !logToStdout { buf.WriteTo(stdout) } if _, ok := err.(*exec.ExitError); ok { // Binary exited with a non-zero exit code, which means the test // failed. Return nil to avoid printing a useless "exited with // error" error message. return nil } return err }) if testConfig.CompileOnly { // Return the compiler error, if there is one. return true, err } importPath := strings.TrimSuffix(result.ImportPath, ".test") var w io.Writer = stdout if logToStdout { w = os.Stdout } if err, ok := err.(loader.NoTestFilesError); ok { fmt.Fprintf(w, "? \t%s\t[no test files]\n", err.ImportPath) // Pretend the test passed - it at least didn't fail. return true, nil } else if passed { fmt.Fprintf(w, "ok \t%s\t%.3fs\n", importPath, duration.Seconds()) } else { fmt.Fprintf(w, "FAIL\t%s\t%.3fs\n", importPath, duration.Seconds()) } return passed, err } func dirsToModuleRootRel(maindir, modroot string) []string { var dirs []string last := ".." // strip off path elements until we hit the module root // adding `..`, `../..`, `../../..` until we're done for maindir != modroot { dirs = append(dirs, last) last = filepath.Join(last, "..") maindir = filepath.Dir(maindir) } dirs = append(dirs, ".") return dirs } func dirsToModuleRootAbs(maindir, modroot string) []string { var dirs = []string{maindir} last := filepath.Join(maindir, "..") // strip off path elements until we hit the module root // adding `..`, `../..`, `../../..` until we're done for maindir != modroot { dirs = append(dirs, last) last = filepath.Join(last, "..") maindir = filepath.Dir(maindir) } return dirs } // validateOutputFormat checks if the output file extension matches the expected format func validateOutputFormat(outpath, expectedExt string) error { actualExt := filepath.Ext(outpath) if actualExt != expectedExt { return fmt.Errorf("output format %s does not match target format %s", actualExt, expectedExt) } return nil } // Flash builds and flashes the built binary to the given serial port. func Flash(pkgName, port, outpath string, options *compileopts.Options) error { config, err := builder.NewConfig(options) if err != nil { return err } // determine the type of file to compile var fileExt string flashMethod, _ := config.Programmer() switch flashMethod { case "command", "": switch { case strings.Contains(config.Target.FlashCommand, "{hex}"): fileExt = ".hex" case strings.Contains(config.Target.FlashCommand, "{elf}"): fileExt = ".elf" case strings.Contains(config.Target.FlashCommand, "{bin}"): fileExt = ".bin" case strings.Contains(config.Target.FlashCommand, "{uf2}"): fileExt = ".uf2" case strings.Contains(config.Target.FlashCommand, "{zip}"): fileExt = ".zip" default: return errors.New("invalid target file - did you forget the {hex} token in the 'flash-command' section?") } case "msd": if config.Target.FlashFilename == "" { return errors.New("invalid target file: flash-method was set to \"msd\" but no msd-firmware-name was set") } fileExt = filepath.Ext(config.Target.FlashFilename) case "openocd": fileExt = ".hex" case "bmp": fileExt = ".elf" case "native": return errors.New("unknown flash method \"native\" - did you miss a -target flag?") default: return errors.New("unknown flash method: " + flashMethod) } // Create a temporary directory for intermediary files. tmpdir, err := os.MkdirTemp("", "tinygo") if err != nil { return err } if !options.Work { defer os.RemoveAll(tmpdir) } // Validate output format before building if outpath != "" { if err := validateOutputFormat(outpath, fileExt); err != nil { return err } } // Build the binary. result, err := builder.Build(pkgName, fileExt, tmpdir, config) if err != nil { return err } // Save output file if specified (after build, before flashing) if outpath != "" { if err := copyFile(result.Binary, outpath); err != nil { return fmt.Errorf("failed to save output file: %v", err) } } // do we need port reset to put MCU into bootloader mode? if config.Target.PortReset == "true" && flashMethod != "openocd" { port, err := getDefaultPort(port, config.Target.SerialPort) if err == nil { err = touchSerialPortAt1200bps(port) if err != nil { return &commandError{"failed to reset port", port, err} } // give the target MCU a chance to restart into bootloader time.Sleep(3 * time.Second) } } // Flash the binary to the MCU. switch flashMethod { case "", "command": // Create the command. flashCmd := config.Target.FlashCommand flashCmdList, err := shlex.Split(flashCmd) if err != nil { return fmt.Errorf("could not parse flash command %#v: %w", flashCmd, err) } if strings.Contains(flashCmd, "{port}") { var err error port, err = getDefaultPort(port, config.Target.SerialPort) if err != nil { return err } } // Fill in fields in the command template. fileToken := "{" + fileExt[1:] + "}" for i, arg := range flashCmdList { arg = strings.ReplaceAll(arg, fileToken, result.Binary) arg = strings.ReplaceAll(arg, "{port}", port) flashCmdList[i] = arg } // Execute the command. if len(flashCmdList) < 2 { return fmt.Errorf("invalid flash command: %#v", flashCmd) } cmd := executeCommand(config.Options, flashCmdList[0], flashCmdList[1:]...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Dir = goenv.Get("TINYGOROOT") err = cmd.Run() if err != nil { return &commandError{"failed to flash", result.Binary, err} } case "msd": // this flashing method copies the binary data to a Mass Storage Device (msd) switch fileExt { case ".uf2": err := flashUF2UsingMSD(config.Target.FlashVolume, result.Binary, config.Options) if err != nil { return &commandError{"failed to flash", result.Binary, err} } case ".hex": err := flashHexUsingMSD(config.Target.FlashVolume, result.Binary, config.Options) if err != nil { return &commandError{"failed to flash", result.Binary, err} } default: return errors.New("mass storage device flashing currently only supports uf2 and hex") } case "openocd": args, err := config.OpenOCDConfiguration() if err != nil { return err } exit := " reset exit" if config.Target.OpenOCDVerify != nil && *config.Target.OpenOCDVerify { exit = " verify" + exit } args = append(args, "-c", "program "+filepath.ToSlash(result.Binary)+exit) cmd := executeCommand(config.Options, "openocd", args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err = cmd.Run() if err != nil { return &commandError{"failed to flash", result.Binary, err} } case "bmp": gdb, err := config.Target.LookupGDB() if err != nil { return err } var bmpGDBPort string bmpGDBPort, _, err = getBMPPorts() if err != nil { return err } args := []string{"-ex", "target extended-remote " + bmpGDBPort, "-ex", "monitor swdp_scan", "-ex", "attach 1", "-ex", "load", filepath.ToSlash(result.Binary)} cmd := executeCommand(config.Options, gdb, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err = cmd.Run() if err != nil { return &commandError{"failed to flash", result.Binary, err} } default: return fmt.Errorf("unknown flash method: %s", flashMethod) } if options.Monitor { return Monitor(result.Executable, "", config) } return nil } // Debug compiles and flashes a program to a microcontroller (just like Flash) // but instead of resetting the target, it will drop into a debug shell like GDB // or LLDB. You can then set breakpoints, run the `continue` command to start, // hit Ctrl+C to break the running program, etc. // // Note: this command is expected to execute just before exiting, as it // modifies global state. func Debug(debugger, pkgName string, ocdOutput bool, options *compileopts.Options) error { config, err := builder.NewConfig(options) if err != nil { return err } var cmdName string switch debugger { case "gdb": cmdName, err = config.Target.LookupGDB() case "lldb": cmdName, err = builder.LookupCommand("lldb") } if err != nil { return err } // Create a temporary directory for intermediary files. tmpdir, err := os.MkdirTemp("", "tinygo") if err != nil { return err } if !options.Work { defer os.RemoveAll(tmpdir) } // Build the binary to debug. format, fileExt := config.EmulatorFormat() result, err := builder.Build(pkgName, fileExt, tmpdir, config) if err != nil { return err } // Find a good way to run GDB. gdbInterface, openocdInterface := config.Programmer() switch gdbInterface { case "msd", "command", "": emulator := config.EmulatorName() if emulator != "" { if emulator == "mgba" { gdbInterface = "mgba" } else if emulator == "simavr" { gdbInterface = "simavr" } else if strings.HasPrefix(emulator, "qemu-system-") { gdbInterface = "qemu" } else { // Assume QEMU as an emulator. gdbInterface = "qemu-user" } } else if openocdInterface != "" && config.Target.OpenOCDTarget != "" { gdbInterface = "openocd" } else if config.Target.JLinkDevice != "" { gdbInterface = "jlink" } else { gdbInterface = "native" } } // Run the GDB server, if necessary. port := "" var gdbCommands []string var daemon *exec.Cmd emulator, err := config.Emulator(format, result.Binary) if err != nil { return err } switch gdbInterface { case "native": // Run GDB directly. case "bmp": var bmpGDBPort string bmpGDBPort, _, err = getBMPPorts() if err != nil { return err } port = bmpGDBPort gdbCommands = append(gdbCommands, "monitor swdp_scan", "compare-sections", "attach 1", "load") case "openocd": port = ":3333" gdbCommands = append(gdbCommands, "monitor halt", "load", "monitor reset halt") // We need a separate debugging daemon for on-chip debugging. args, err := config.OpenOCDConfiguration() if err != nil { return err } daemon = executeCommand(config.Options, "openocd", args...) if ocdOutput { // Make it clear which output is from the daemon. w := &ColorWriter{ Out: colorable.NewColorableStderr(), Prefix: "openocd: ", Color: TermColorYellow, } daemon.Stdout = w daemon.Stderr = w } case "jlink": port = ":2331" gdbCommands = append(gdbCommands, "load", "monitor reset halt") // We need a separate debugging daemon for on-chip debugging. daemon = executeCommand(config.Options, "JLinkGDBServer", "-device", config.Target.JLinkDevice) if ocdOutput { // Make it clear which output is from the daemon. w := &ColorWriter{ Out: colorable.NewColorableStderr(), Prefix: "jlink: ", Color: TermColorYellow, } daemon.Stdout = w daemon.Stderr = w } case "qemu": port = ":1234" // Run in an emulator. args := append(emulator[1:], "-s", "-S") daemon = executeCommand(config.Options, emulator[0], args...) daemon.Stdout = os.Stdout daemon.Stderr = os.Stderr case "qemu-user": port = ":1234" // Run in an emulator. args := append([]string{"-g", "1234"}, emulator[1:]...) daemon = executeCommand(config.Options, emulator[0], args...) daemon.Stdout = os.Stdout daemon.Stderr = os.Stderr case "mgba": port = ":2345" // Run in an emulator. args := append(emulator[1:], "-g") daemon = executeCommand(config.Options, emulator[0], args...) daemon.Stdout = os.Stdout daemon.Stderr = os.Stderr case "simavr": port = ":1234" // Run in an emulator. args := append(emulator[1:], "-g") daemon = executeCommand(config.Options, emulator[0], args...) daemon.Stdout = os.Stdout daemon.Stderr = os.Stderr case "msd": return errors.New("gdb is not supported for drag-and-drop programmable devices") default: return fmt.Errorf("gdb is not supported with interface %#v", gdbInterface) } if daemon != nil { // Make sure the daemon doesn't receive Ctrl-C that is intended for // GDB (to break the currently executing program). setCommandAsDaemon(daemon) // Start now, and kill it on exit. err = daemon.Start() if err != nil { return &commandError{"failed to run", daemon.Path, err} } defer func() { daemon.Process.Signal(os.Interrupt) var stopped uint32 go func() { time.Sleep(time.Millisecond * 100) if atomic.LoadUint32(&stopped) == 0 { daemon.Process.Kill() } }() daemon.Wait() atomic.StoreUint32(&stopped, 1) }() } // Ignore Ctrl-C, it must be passed on to GDB. c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) go func() { for range c { } }() // Construct and execute a gdb or lldb command. // By default: gdb -ex run // Exit the debugger with Ctrl-D. params := []string{result.Executable} switch debugger { case "gdb": if port != "" { params = append(params, "-ex", "target extended-remote "+port) } for _, cmd := range gdbCommands { params = append(params, "-ex", cmd) } case "lldb": params = append(params, "--arch", config.Triple()) if port != "" { if strings.HasPrefix(port, ":") { params = append(params, "-o", "gdb-remote "+port[1:]) } else { return fmt.Errorf("cannot use LLDB over a gdb-remote that isn't a TCP port: %s", port) } } for _, cmd := range gdbCommands { if strings.HasPrefix(cmd, "monitor ") { params = append(params, "-o", "process plugin packet "+cmd) } else if cmd == "load" { params = append(params, "-o", "target modules load --load --slide 0") } else { return fmt.Errorf("don't know how to convert GDB command %#v to LLDB", cmd) } } } cmd := executeCommand(config.Options, cmdName, params...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err = cmd.Run() if err != nil { return &commandError{"failed to run " + cmdName + " with", result.Executable, err} } return nil } // Run compiles and runs the given program. Depending on the target provided in // the options, it will run the program directly on the host or will run it in // an emulator. For example, -target=wasm will cause the binary to be run inside // of a WebAssembly VM. func Run(pkgName string, options *compileopts.Options, cmdArgs []string) error { config, err := builder.NewConfig(options) if err != nil { return err } _, err = buildAndRun(pkgName, config, os.Stdout, cmdArgs, nil, 0, func(cmd *exec.Cmd, result builder.BuildResult) error { return cmd.Run() }) return err } // buildAndRun builds and runs the given program, writing output to stdout and // errors to os.Stderr. It takes care of emulators (qemu, wasmtime, etc) and // passes command line arguments and environment variables in a way appropriate // for the given emulator. func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, cmdArgs, environmentVars []string, timeout time.Duration, run func(cmd *exec.Cmd, result builder.BuildResult) error) (builder.BuildResult, error) { // Determine whether we're on a system that supports environment variables // and command line parameters (operating systems, WASI) or not (baremetal, // WebAssembly in the browser). If we're on a system without an environment, // we need to pass command line arguments and environment variables through // global variables (built into the binary directly) instead of the // conventional way. needsEnvInVars := config.GOOS() == "js" for _, tag := range config.BuildTags() { if tag == "baremetal" { needsEnvInVars = true } } var args, env []string var extraCmdEnv []string if needsEnvInVars { runtimeGlobals := make(map[string]string) if len(cmdArgs) != 0 { runtimeGlobals["osArgs"] = strings.Join(cmdArgs, "\x00") } if len(environmentVars) != 0 { runtimeGlobals["osEnv"] = strings.Join(environmentVars, "\x00") } if len(runtimeGlobals) != 0 { // This sets the global variables like they would be set with // `-ldflags="-X=runtime.osArgs=first\x00second`. // The runtime package has two variables (osArgs and osEnv) that are // both strings, from which the parameters and environment variables // are read. config.Options.GlobalValues = map[string]map[string]string{ "runtime": runtimeGlobals, } } } else { // Pass environment variables and command line parameters as usual. // This also works on qemu-aarch64 etc. args = cmdArgs env = environmentVars } // Create a temporary directory for intermediary files. tmpdir, err := os.MkdirTemp("", "tinygo") if err != nil { return builder.BuildResult{}, err } if !config.Options.Work { defer os.RemoveAll(tmpdir) } // Build the binary to be run. format, fileExt := config.EmulatorFormat() result, err := builder.Build(pkgName, fileExt, tmpdir, config) if err != nil { return result, err } // If needed, set a timeout on the command. This is done in tests so // they don't waste resources on a stalled test. var ctx context.Context if timeout != 0 { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(context.Background(), timeout) defer cancel() } // Set up the command. var name string if config.Target.Emulator == "" { name = result.Binary } else { emulator, err := config.Emulator(format, result.Binary) if err != nil { return result, err } name, emulator = emulator[0], emulator[1:] // wasmtime is a WebAssembly runtime CLI with WASI enabled by default. // By default, only stdio is allowed. For example, while STDOUT routes // to the host, other files don't. It also does not inherit environment // variables from the host. Some tests read testdata files, often from // outside the package directory. Other tests require temporary // writeable directories. We allow this by adding wasmtime flags below. if name == "wasmtime" { var emuArgs []string // Extract the wasmtime subcommand (e.g. "run" or "serve") if len(emulator) > 1 { emuArgs = append(emuArgs, emulator[0]) emulator = emulator[1:] } wd, _ := os.Getwd() // Below adds additional wasmtime flags in case a test reads files // outside its directory, like "../testdata/e.txt". This allows any // relative directory up to the module root, even if the test never // reads any files. if config.TestConfig.CompileTestBinary { // Set working directory to package dir wd = result.MainDir // Add relative dirs (../, ../..) up to module root (for wasip1) dirs := dirsToModuleRootRel(result.MainDir, result.ModuleRoot) // Add absolute dirs up to module root (for wasip2) dirs = append(dirs, dirsToModuleRootAbs(result.MainDir, result.ModuleRoot)...) for _, d := range dirs { emuArgs = append(emuArgs, "--dir="+d) } } else { emuArgs = append(emuArgs, "--dir=.") } emuArgs = append(emuArgs, "--dir="+wd) emuArgs = append(emuArgs, "--env=PWD="+wd) for _, v := range environmentVars { emuArgs = append(emuArgs, "--env", v) } // Set this for nicer backtraces during tests, but don't override the user. if _, ok := os.LookupEnv("WASMTIME_BACKTRACE_DETAILS"); !ok { extraCmdEnv = append(extraCmdEnv, "WASMTIME_BACKTRACE_DETAILS=1") } emulator = append(emuArgs, emulator...) } args = append(emulator, args...) } var cmd *exec.Cmd if ctx != nil { cmd = exec.CommandContext(ctx, name, args...) } else { cmd = exec.Command(name, args...) } cmd.Env = append(cmd.Env, env...) cmd.Env = append(cmd.Env, extraCmdEnv...) // Configure stdout/stderr. The stdout may go to a buffer, not a real // stdout. cmd.Stdout = newOutputWriter(stdout, result.Executable) cmd.Stderr = os.Stderr if config.EmulatorName() == "simavr" { cmd.Stdout = nil // don't print initial load commands cmd.Stderr = stdout } // If this is a test, reserve CPU time for it so that increased // parallelism doesn't blow up memory usage. If this isn't a test but // simply `tinygo run`, then it is practically a no-op. config.Options.Semaphore <- struct{}{} defer func() { <-config.Options.Semaphore }() // Run binary. if config.Options.PrintCommands != nil { config.Options.PrintCommands(cmd.Path, cmd.Args[1:]...) } err = run(cmd, result) if err != nil { if ctx != nil && ctx.Err() == context.DeadlineExceeded { fmt.Fprintf(stdout, "--- timeout of %s exceeded, terminating...\n", timeout) err = ctx.Err() } return result, &commandError{"failed to run compiled binary", result.Binary, err} } return result, nil } func touchSerialPortAt1200bps(port string) (err error) { retryCount := 3 for i := 0; i < retryCount; i++ { // Open port p, e := serial.Open(port, &serial.Mode{BaudRate: 1200}) if e != nil { if runtime.GOOS == `windows` { se, ok := e.(*serial.PortError) if ok && se.Code() == serial.InvalidSerialPort { // InvalidSerialPort error occurs when transitioning to boot return nil } } time.Sleep(1 * time.Second) err = e continue } defer p.Close() p.SetDTR(false) return nil } return fmt.Errorf("opening port: %s", err) } func flashUF2UsingMSD(volumes []string, tmppath string, options *compileopts.Options) error { for start := time.Now(); time.Since(start) < options.Timeout; { // Find a UF2 mount point. mounts, err := findFATMounts(options) if err != nil { return err } for _, mount := range mounts { for _, volume := range volumes { if mount.name != volume { continue } if _, err := os.Stat(filepath.Join(mount.path, "INFO_UF2.TXT")); err != nil { // No INFO_UF2.TXT found, which is expected on a UF2 // filesystem. continue } // Found the filesystem, so flash the device! return moveFile(tmppath, filepath.Join(mount.path, "flash.uf2")) } } time.Sleep(500 * time.Millisecond) } return errors.New("unable to locate any volume: [" + strings.Join(volumes, ",") + "]") } func flashHexUsingMSD(volumes []string, tmppath string, options *compileopts.Options) error { for start := time.Now(); time.Since(start) < options.Timeout; { // Find all mount points. mounts, err := findFATMounts(options) if err != nil { return err } for _, mount := range mounts { for _, volume := range volumes { if mount.name != volume { continue } // Found the filesystem, so flash the device! return moveFile(tmppath, filepath.Join(mount.path, "flash.hex")) } } time.Sleep(500 * time.Millisecond) } return errors.New("unable to locate any volume: [" + strings.Join(volumes, ",") + "]") } type mountPoint struct { name string path string } // Find all the mount points on the system that use the FAT filesystem. func findFATMounts(options *compileopts.Options) ([]mountPoint, error) { var points []mountPoint switch runtime.GOOS { case "darwin": list, err := os.ReadDir("/Volumes") if err != nil { return nil, fmt.Errorf("could not list mount points: %w", err) } for _, elem := range list { volumePath := filepath.Join("/Volumes", elem.Name()) if _, err := os.Stat(volumePath); err != nil { continue } cmd := exec.Command("diskutil", "info", volumePath) var out bytes.Buffer cmd.Stdout = &out if err := cmd.Run(); err != nil { continue // skip if diskutil failed } diskInfo := map[string]string{} scanner := bufio.NewScanner(&out) for scanner.Scan() { line := scanner.Text() parts := strings.SplitN(line, ":", 2) if len(parts) != 2 { continue } key := strings.TrimSpace(parts[0]) value := strings.TrimSpace(parts[1]) diskInfo[key] = value } if err := scanner.Err(); err != nil { continue } volName, okv := diskInfo["Volume Name"] fsType, okf := diskInfo["File System Personality"] if !okv || !okf { continue } // Check if a filesystem type is FAT-based if strings.Contains(strings.ToUpper(fsType), "FAT") { points = append(points, mountPoint{ name: volName, path: volumePath, }) } } sort.Slice(points, func(i, j int) bool { return points[i].path < points[j].path }) return points, nil case "linux": tab, err := os.ReadFile("/proc/mounts") // symlink to /proc/self/mounts on my system if err != nil { return nil, fmt.Errorf("could not list mount points: %w", err) } for _, line := range strings.Split(string(tab), "\n") { fields := strings.Fields(line) if len(fields) <= 2 { continue } fstype := fields[2] // chromeos bind mounts use 9p if !(fstype == "vfat" || fstype == "9p") { continue } fspath := strings.ReplaceAll(fields[1], "\\040", " ") points = append(points, mountPoint{ name: filepath.Base(fspath), path: fspath, }) } return points, nil case "windows": // Obtain a list of all currently mounted volumes. cmd := executeCommand(options, "powershell", "-c", "Get-CimInstance -ClassName Win32_LogicalDisk | Select-Object DeviceID, DriveType, FileSystem, VolumeName") var out bytes.Buffer cmd.Stdout = &out err := cmd.Run() if err != nil { return nil, fmt.Errorf("could not list mount points: %w", err) } // Extract data to convert to a []mountPoint slice. for _, line := range strings.Split(out.String(), "\n") { words := strings.Fields(line) if len(words) < 3 { continue } if words[1] != "2" || words[2] != "FAT" { // - DriveType 2 is removable (which we're looking for). // - We only want to return FAT filesystems. continue } points = append(points, mountPoint{ name: words[3], path: words[0], }) } return points, nil default: return nil, fmt.Errorf("unknown GOOS for listing mount points: %s", runtime.GOOS) } } // getDefaultPort returns the default serial port depending on the operating system. func getDefaultPort(portFlag string, usbInterfaces []string) (port string, err error) { portCandidates := strings.FieldsFunc(portFlag, func(c rune) bool { return c == ',' }) if len(portCandidates) == 1 { return portCandidates[0], nil } var ports []string switch runtime.GOOS { case "freebsd": ports, err = filepath.Glob("/dev/cuaU*") case "darwin", "linux", "windows": var portsList []*enumerator.PortDetails portsList, err = enumerator.GetDetailedPortsList() if err != nil { return "", err } var preferredPortIDs [][2]uint16 for _, s := range usbInterfaces { parts := strings.Split(s, ":") if len(parts) != 2 { return "", fmt.Errorf("could not parse USB VID/PID pair %q", s) } vid, err := strconv.ParseUint(parts[0], 16, 16) if err != nil { return "", fmt.Errorf("could not parse USB vendor ID %q: %w", parts[1], err) } pid, err := strconv.ParseUint(parts[1], 16, 16) if err != nil { return "", fmt.Errorf("could not parse USB product ID %q: %w", parts[1], err) } preferredPortIDs = append(preferredPortIDs, [2]uint16{uint16(vid), uint16(pid)}) } var primaryPorts []string // ports picked from preferred USB VID/PID var secondaryPorts []string // other ports (as a fallback) for _, p := range portsList { if !p.IsUSB { continue } if p.VID != "" && p.PID != "" { foundPort := false vid, vidErr := strconv.ParseUint(p.VID, 16, 16) pid, pidErr := strconv.ParseUint(p.PID, 16, 16) if vidErr == nil && pidErr == nil { for _, id := range preferredPortIDs { if uint16(vid) == id[0] && uint16(pid) == id[1] { primaryPorts = append(primaryPorts, p.Name) foundPort = true continue } } } if foundPort { continue } } secondaryPorts = append(secondaryPorts, p.Name) } if len(primaryPorts) == 1 { // There is exactly one match in the set of preferred ports. Use // this port, even if there may be others available. This allows // flashing a specific board even if there are multiple available. return primaryPorts[0], nil } else if len(primaryPorts) > 1 { // There are multiple preferred ports, probably because more than // one device of the same type are connected (e.g. two Arduino // Unos). ports = primaryPorts } else { // No preferred ports found. Fall back to other serial ports // available in the system. ports = secondaryPorts } default: return "", errors.New("unable to search for a default USB device to be flashed on this OS") } if err != nil { return "", err } else if ports == nil { return "", errors.New("unable to locate a serial port") } else if len(ports) == 0 { return "", errors.New("no serial ports available") } if len(portCandidates) == 0 { if len(usbInterfaces) > 0 { return "", errors.New("unable to search for a default USB device - use -port flag, available ports are " + strings.Join(ports, ", ")) } else if len(ports) == 1 { return ports[0], nil } else { return "", errors.New("multiple serial ports available - use -port flag, available ports are " + strings.Join(ports, ", ")) } } for _, ps := range portCandidates { for _, p := range ports { if p == ps { return p, nil } } } return "", errors.New("port you specified '" + strings.Join(portCandidates, ",") + "' does not exist, available ports are " + strings.Join(ports, ", ")) } // getBMPPorts returns BlackMagicProbe's serial ports if any func getBMPPorts() (gdbPort, uartPort string, err error) { var portsList []*enumerator.PortDetails portsList, err = enumerator.GetDetailedPortsList() if err != nil { return "", "", err } var ports []string for _, p := range portsList { if !p.IsUSB { continue } if p.VID != "" && p.PID != "" { vid, vidErr := strconv.ParseUint(p.VID, 16, 16) pid, pidErr := strconv.ParseUint(p.PID, 16, 16) if vidErr == nil && pidErr == nil && vid == 0x1d50 && pid == 0x6018 { ports = append(ports, p.Name) } } } if len(ports) == 2 { return ports[0], ports[1], nil } else if len(ports) == 0 { return "", "", errors.New("no BMP detected") } else { return "", "", fmt.Errorf("expected 2 BMP serial ports, found %d - did you perhaps connect more than one BMP?", len(ports)) } } const ( usageBuild = `Build compiles the packages named by the import paths, along with their dependencies, but it does not install the results. The output binary is specified using the -o parameter. The generated file type depends on the extension: .o: Create a relocatable object file. You can use this option if you don't want to use the TinyGo build system or want to do other custom things. .ll: Create textual LLVM IR, after optimization. This is mainly useful for debugging. .bc: Create LLVM bitcode, after optimization. This may be useful for debugging or for linking into other programs using LTO. .hex: Create an Intel HEX file to flash it to a microcontroller. .bin: Similar, but create a binary file. .wasm: Compile and link a WebAssembly file. (all other) Compile and link the program into a regular executable. For microcontrollers, it is common to use the .elf file extension to indicate a linked ELF file is generated. For Linux, it is common to build binaries with no extension at all.` usageRun = `Run the program, either directly on the host or in an emulated environment (depending on -target).` usageFlash = `Flash the program to a microcontroller. Some common flags are described below. -target={name}: Specifies the type of microcontroller that is used. The name of the microcontroller is given on the individual pages for each board type listed under Microcontrollers (https://tinygo.org/docs/reference/microcontrollers/). Examples: "arduino-nano", "d1mini", "xiao". -o={filename}: Save the built binary to the specified output file. The file format must match the target's expected format (e.g., .hex, .uf2). Both flashing and saving will be performed. -monitor: Start the serial monitor (see below) immediately after flashing. However, some microcontrollers need a split second or two to configure the serial port after flashing, and using the "-monitor" flag can fail because the serial monitor starts too quickly. In that case, use the "tinygo monitor" command explicitly.` usageMonitor = `Start the serial monitor on the serial port that is connected to the microcontroller. If there is only a single board attached to the host computer, the default values for various options should be sufficient. In other situations, particularly if you have multiple microcontrollers attached, some parameters may need to be overridden using the following flags: -port={port}: If there are multiple microcontroller attached, an error message will display a list of potential serial ports. The appropriate port can be specified by this flag. On Linux, the port will be something like /dev/ttyUSB0 or /dev/ttyACM1. On MacOS, the port will look like /dev/cu.usbserial-1420. On Windows, the port will be something like COM1 or COM31. -baudrate={rate}: The default baud rate is 115200. Boards using the AVR processor (e.g. Arduino Nano, Arduino Mega 2560) use 9600 instead. -target={name}: If you have more than one microcontrollers attached, you can sometimes just specify the target name and let tinygo monitor figure out the port. Sometimes, this does not work and you have to explicitly use the -port flag. The serial monitor intercepts several control characters for its own use instead of sending them to the microcontroller: Control-C: terminates the tinygo monitor Control-Z: suspends the tinygo monitor and drops back into shell Control-\: terminates the tinygo monitor with a stack trace Control-S: flow control, suspends output to the console Control-Q: flow control, resumes output to the console Control-@: thrown away by tinygo monitor Note: If you are using os.Stdin on the microcontroller, you may find that a CR character on the host computer (also known as Enter, ^M, or \r) is transmitted to the microcontroller without conversion, so os.Stdin returns a \r character instead of the expected \n (also known as ^J, NL, or LF) to indicate end-of-line. You may be able to get around this problem by hitting Control-J in tinygo monitor to transmit the \n end-of-line character.` usageGdb = `Build the program, optionally flash it to a microcontroller if it is a remote target, and drop into a GDB shell. From there you can set breakpoints, start the program with "run" or "continue" ("run" for a local program, continue for on-chip debugging), single-step, show a backtrace, break and resume the program with Ctrl-C/"continue", etc. You may need to install extra tools (like openocd and arm-none-eabi-gdb) to be able to do this. Also, you may need a dedicated debugger to be able to debug certain boards if no debugger is integrated. Some boards (like the BBC micro:bit and most professional evaluation boards) have an integrated debugger.` usageClean = `Clean the cache directory, normally stored in $HOME/.cache/tinygo. This is not normally needed.` usageHelp = `Print a short summary of the available commands, plus a list of command flags.` usageVersion = `Print the version of the command and the version of the used $GOROOT.` usageEnv = `Print a list of environment variables that affect TinyGo (as a shell script). If one or more variable names are given as arguments, env prints the value of each on a new line.` usageDefault = `TinyGo is a Go compiler for small places. version: %s usage: %s [arguments] commands: build: compile packages and dependencies run: compile and run immediately test: test packages flash: compile and flash to the device gdb: run/flash and immediately enter GDB lldb: run/flash and immediately enter LLDB monitor: open communication port ports: list available serial ports env: list environment variables used during build list: run go list using the TinyGo root clean: empty cache directory (%s) targets: list targets info: show info for specified target version: show version help: print this help text` ) var ( commandHelp = map[string]string{ "build": usageBuild, "run": usageRun, "flash": usageFlash, "monitor": usageMonitor, "gdb": usageGdb, "clean": usageClean, "help": usageHelp, "version": usageVersion, "env": usageEnv, } ) func usage(command string) { val, ok := commandHelp[command] if !ok { fmt.Fprintf(os.Stderr, usageDefault, goenv.Version(), os.Args[0], goenv.Get("GOCACHE")) if flag.Parsed() { fmt.Fprintln(os.Stderr, "\nflags:") flag.PrintDefaults() } fmt.Fprintln(os.Stderr, "\nfor more details, see https://tinygo.org/docs/reference/usage/") } else { fmt.Fprintln(os.Stderr, val) } } // Print diagnostics very similar to the -json flag in Go. func printBuildOutput(err error, jsonDiagnostics bool) { if err == nil { return // nothing to report } if jsonDiagnostics { workingDir, getwdErr := os.Getwd() if getwdErr != nil { workingDir = "" } type jsonDiagnosticOutput struct { ImportPath string Action string Output string `json:",omitempty"` StartPos string `json:",omitempty"` // non-standard EndPos string `json:",omitempty"` // non-standard } for _, diags := range diagnostics.CreateDiagnostics(err) { if diags.ImportPath != "" { output, _ := json.Marshal(jsonDiagnosticOutput{ ImportPath: diags.ImportPath, Action: "build-output", Output: "# " + diags.ImportPath + "\n", }) os.Stdout.Write(append(output, '\n')) } for _, diag := range diags.Diagnostics { w := &bytes.Buffer{} diag.WriteTo(w, workingDir) data := jsonDiagnosticOutput{ ImportPath: diags.ImportPath, Action: "build-output", Output: w.String(), } if diag.StartPos.IsValid() && diag.EndPos.IsValid() { // Include the non-standard StartPos/EndPos values. These // are useful for the TinyGo Playground to show better error // messages. data.StartPos = diagnostics.RelativePosition(diag.StartPos, workingDir).String() data.EndPos = diagnostics.RelativePosition(diag.EndPos, workingDir).String() } output, _ := json.Marshal(data) os.Stdout.Write(append(output, '\n')) } // Emit the "Action":"build-fail" JSON. output, _ := json.Marshal(jsonDiagnosticOutput{ ImportPath: diags.ImportPath, Action: "build-fail", }) os.Stdout.Write(append(output, '\n')) } os.Exit(1) } // Regular diagnostic handling. handleCompilerError(err) } func handleCompilerError(err error) { if err != nil { wd, getwdErr := os.Getwd() if getwdErr != nil { wd = "" } diagnostics.CreateDiagnostics(err).WriteTo(os.Stderr, wd) os.Exit(1) } } // This is a special type for the -X flag to parse the pkgpath.Var=stringVal // format. It has to be a special type to allow multiple variables to be defined // this way. type globalValuesFlag map[string]map[string]string func (m globalValuesFlag) String() string { return "pkgpath.Var=value" } func (m globalValuesFlag) Set(value string) error { equalsIndex := strings.IndexByte(value, '=') if equalsIndex < 0 { return errors.New("expected format pkgpath.Var=value") } pathAndName := value[:equalsIndex] pointIndex := strings.LastIndexByte(pathAndName, '.') if pointIndex < 0 { return errors.New("expected format pkgpath.Var=value") } path := pathAndName[:pointIndex] name := pathAndName[pointIndex+1:] stringValue := value[equalsIndex+1:] if m[path] == nil { m[path] = make(map[string]string) } m[path][name] = stringValue return nil } // parseGoLinkFlag parses the -ldflags parameter. Its primary purpose right now // is the -X flag, for setting the value of global string variables. func parseGoLinkFlag(flagsString string) (map[string]map[string]string, string, error) { set := flag.NewFlagSet("link", flag.ExitOnError) globalVarValues := make(globalValuesFlag) set.Var(globalVarValues, "X", "Set the value of the string variable to the given value.") extLDFlags := set.String("extldflags", "", "additional flags to pass to external linker") flags, err := shlex.Split(flagsString) if err != nil { return nil, "", err } err = set.Parse(flags) if err != nil { return nil, "", err } return map[string]map[string]string(globalVarValues), *extLDFlags, nil } // getListOfPackages returns a standard list of packages for a given list that might // include wildards using `go list`. // For example [./...] => ["pkg1", "pkg1/pkg12", "pkg2"] func getListOfPackages(pkgs []string, options *compileopts.Options) ([]string, error) { config, err := builder.NewConfig(options) if err != nil { return nil, err } cmd, err := loader.List(config, nil, pkgs) if err != nil { return nil, fmt.Errorf("failed to run `go list`: %w", err) } outputBuf := bytes.NewBuffer(nil) cmd.Stdout = outputBuf cmd.Stderr = os.Stderr err = cmd.Run() if err != nil { return nil, err } var pkgNames []string sc := bufio.NewScanner(outputBuf) for sc.Scan() { pkgNames = append(pkgNames, sc.Text()) } return pkgNames, nil } func main() { if len(os.Args) < 2 { fmt.Fprintln(os.Stderr, "No command-line arguments supplied.") usage("") os.Exit(1) } command := os.Args[1] opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z") gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative, custom, precise, boehm)") panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)") scheduler := flag.String("scheduler", "", "which scheduler to use (none, tasks, cores, threads, asyncify)") serial := flag.String("serial", "", "which serial output to use (none, uart, usb, rtt)") work := flag.Bool("work", false, "print the name of the temporary build directory and do not delete this directory on exit") interpTimeout := flag.Duration("interp-timeout", 180*time.Second, "interp optimization pass timeout") var tags buildutil.TagsFlag flag.Var(&tags, "tags", "a space-separated list of extra build tags") target := flag.String("target", "", "chip/board name or JSON target specification file") buildMode := flag.String("buildmode", "", "build mode to use (default, c-shared, wasi-legacy)") var stackSize uint64 flag.Func("stack-size", "goroutine stack size (if unknown at compile time)", func(s string) error { size, err := bytesize.Parse(s) stackSize = uint64(size) return err }) printSize := flag.String("size", "", "print sizes (none, short, full, html)") printStacks := flag.Bool("print-stacks", false, "print stack sizes of goroutines") printAllocsString := flag.String("print-allocs", "", "regular expression of functions for which heap allocations should be printed") printCommands := flag.Bool("x", false, "Print commands") flagJSON := flag.Bool("json", false, "print output in JSON format") parallelism := flag.Int("p", runtime.GOMAXPROCS(0), "the number of build jobs that can run in parallel") nodebug := flag.Bool("no-debug", false, "strip debug information") nobounds := flag.Bool("nobounds", false, "do not emit bounds checks") ocdCommandsString := flag.String("ocd-commands", "", "OpenOCD commands, overriding target spec (can specify multiple separated by commas)") ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug") port := flag.String("port", "", "flash port (can specify multiple candidates separated by commas)") timeout := flag.Duration("timeout", 20*time.Second, "the length of time to retry locating the MSD volume to be used for flashing") programmer := flag.String("programmer", "", "which hardware programmer to use") ldflags := flag.String("ldflags", "", "Go link tool compatible ldflags") llvmFeatures := flag.String("llvm-features", "", "comma separated LLVM features to enable") cpuprofile := flag.String("cpuprofile", "", "cpuprofile output") monitor := flag.Bool("monitor", false, "enable serial monitor") baudrate := flag.Int("baudrate", 115200, "baudrate of serial monitor") gocompatibility := flag.Bool("go-compatibility", true, "enable to check for Go versions compatibility, you can also configure this by setting the TINYGO_GOCOMPATIBILITY environment variable") // Internal flags, that are only intended for TinyGo development. printIR := flag.Bool("internal-printir", false, "print LLVM IR") dumpSSA := flag.Bool("internal-dumpssa", false, "dump internal Go SSA") verifyIR := flag.Bool("internal-verifyir", false, "run extra verification steps on LLVM IR") // Don't generate debug information in the IR, to make IR more readable. // You generally want debug information in IR for various features, like // stack size calculation and features like -size=short, -print-allocs=, // etc. The -no-debug flag is used to strip it at link time. But for TinyGo // development it can be useful to not emit debug information at all. skipDwarf := flag.Bool("internal-nodwarf", false, "internal flag, use -no-debug instead") var flagDeps, flagTest bool if command == "help" || command == "list" { flag.BoolVar(&flagDeps, "deps", false, "supply -deps flag to go list") flag.BoolVar(&flagTest, "test", false, "supply -test flag to go list") } var outpath string if command == "help" || command == "build" || command == "test" || command == "flash" { flag.StringVar(&outpath, "o", "", "output filename") } var witPackage, witWorld string if command == "help" || command == "build" || command == "test" || command == "run" { flag.StringVar(&witPackage, "wit-package", "", "wit package for wasm component embedding") flag.StringVar(&witWorld, "wit-world", "", "wit world for wasm component embedding") } var testConfig compileopts.TestConfig if command == "help" || command == "test" { flag.BoolVar(&testConfig.CompileOnly, "c", false, "compile the test binary but do not run it") flag.BoolVar(&testConfig.Verbose, "v", false, "verbose: print additional output") flag.BoolVar(&testConfig.Short, "short", false, "short: run smaller test suite to save time") flag.StringVar(&testConfig.RunRegexp, "run", "", "run: regexp of tests to run") flag.StringVar(&testConfig.SkipRegexp, "skip", "", "skip: regexp of tests to skip") testConfig.Count = flag.Int("count", 1, "count: number of times to run tests/benchmarks `count` times") flag.StringVar(&testConfig.BenchRegexp, "bench", "", "bench: regexp of benchmarks to run") flag.StringVar(&testConfig.BenchTime, "benchtime", "", "run each benchmark for duration `d`") flag.BoolVar(&testConfig.BenchMem, "benchmem", false, "show memory stats for benchmarks") flag.StringVar(&testConfig.Shuffle, "shuffle", "", "shuffle the order the tests and benchmarks run") } // Early command processing, before commands are interpreted by the Go flag // library. handleChdirFlag() switch command { case "clang", "ld.lld", "wasm-ld": err := builder.RunTool(command, os.Args[2:]...) if err != nil { // The tool should have printed an error message already. // Don't print another error message here. os.Exit(1) } os.Exit(0) } flag.CommandLine.Parse(os.Args[2:]) globalVarValues, extLDFlags, err := parseGoLinkFlag(*ldflags) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } var printAllocs *regexp.Regexp if *printAllocsString != "" { printAllocs, err = regexp.Compile(*printAllocsString) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } var ocdCommands []string if *ocdCommandsString != "" { ocdCommands = strings.Split(*ocdCommandsString, ",") } val, ok := os.LookupEnv("TINYGO_GOCOMPATIBILITY") if ok { b, err := strconv.ParseBool(val) if err != nil { fmt.Fprintf(os.Stderr, "could not parse TINYGO_GOCOMPATIBILITY value %q: %v\n", val, err) os.Exit(1) } *gocompatibility = b } options := &compileopts.Options{ GOOS: goenv.Get("GOOS"), GOARCH: goenv.Get("GOARCH"), GOARM: goenv.Get("GOARM"), GOMIPS: goenv.Get("GOMIPS"), Target: *target, BuildMode: *buildMode, StackSize: stackSize, Opt: *opt, GC: *gc, PanicStrategy: *panicStrategy, Scheduler: *scheduler, Serial: *serial, Work: *work, InterpTimeout: *interpTimeout, PrintIR: *printIR, DumpSSA: *dumpSSA, VerifyIR: *verifyIR, SkipDWARF: *skipDwarf, Semaphore: make(chan struct{}, *parallelism), Debug: !*nodebug, Nobounds: *nobounds, PrintSizes: *printSize, PrintStacks: *printStacks, PrintAllocs: printAllocs, Tags: []string(tags), TestConfig: testConfig, GlobalValues: globalVarValues, Programmer: *programmer, OpenOCDCommands: ocdCommands, LLVMFeatures: *llvmFeatures, Monitor: *monitor, BaudRate: *baudrate, Timeout: *timeout, WITPackage: witPackage, WITWorld: witWorld, GoCompatibility: *gocompatibility, } if *printCommands { options.PrintCommands = printCommand } if extLDFlags != "" { options.ExtLDFlags, err = shlex.Split(extLDFlags) if err != nil { fmt.Fprintln(os.Stderr, "could not parse -extldflags:", err) os.Exit(1) } } err = options.Verify() if err != nil { fmt.Fprintln(os.Stderr, err.Error()) usage(command) os.Exit(1) } if *cpuprofile != "" { f, err := os.Create(*cpuprofile) if err != nil { fmt.Fprintln(os.Stderr, "could not create CPU profile: ", err) os.Exit(1) } defer f.Close() if err := pprof.StartCPUProfile(f); err != nil { fmt.Fprintln(os.Stderr, "could not start CPU profile: ", err) os.Exit(1) } defer pprof.StopCPUProfile() } switch command { case "build": pkgName := "." if flag.NArg() == 1 { pkgName = filepath.ToSlash(flag.Arg(0)) } else if flag.NArg() > 1 { fmt.Fprintln(os.Stderr, "build only accepts a single positional argument: package name, but multiple were specified") usage(command) os.Exit(1) } if filepath.Ext(outpath) == ".wasm" && options.GOARCH != "wasm" && options.Target == "" { fmt.Fprintln(os.Stderr, "you appear to want to build a wasm file, but have not specified either a target flag, or the GOARCH/GOOS to use.") os.Exit(1) } config, err := builder.NewConfig(options) handleCompilerError(err) err = Build(pkgName, outpath, config) printBuildOutput(err, *flagJSON) case "flash", "gdb", "lldb": pkgName := filepath.ToSlash(flag.Arg(0)) if command == "flash" { err := Flash(pkgName, *port, outpath, options) printBuildOutput(err, *flagJSON) } else { if !options.Debug { fmt.Fprintln(os.Stderr, "Debug disabled while running debugger?") usage(command) os.Exit(1) } err := Debug(command, pkgName, *ocdOutput, options) printBuildOutput(err, *flagJSON) } case "run": if flag.NArg() < 1 { fmt.Fprintln(os.Stderr, "No package specified.") usage(command) os.Exit(1) } pkgName := filepath.ToSlash(flag.Arg(0)) err := Run(pkgName, options, flag.Args()[1:]) printBuildOutput(err, *flagJSON) case "test": var pkgNames []string for i := 0; i < flag.NArg(); i++ { pkgNames = append(pkgNames, filepath.ToSlash(flag.Arg(i))) } if len(pkgNames) == 0 { pkgNames = []string{"."} } explicitPkgNames, err := getListOfPackages(pkgNames, options) if err != nil { fmt.Printf("cannot resolve packages: %v\n", err) os.Exit(1) } if outpath != "" && len(explicitPkgNames) > 1 { fmt.Println("cannot use -o flag with multiple packages") os.Exit(1) } fail := make(chan struct{}, 1) var wg sync.WaitGroup bufs := make([]testOutputBuf, len(explicitPkgNames)) for i := range bufs { bufs[i].done = make(chan struct{}) } wg.Add(1) go func() { defer wg.Done() // Flush the output one test at a time. // This ensures that outputs from different tests are not mixed together. for i := range bufs { err := bufs[i].flush(os.Stdout, os.Stderr) if err != nil { // There was an error writing to stdout or stderr, so we probably cannot print this. select { case fail <- struct{}{}: default: } } } }() // Build and run the tests concurrently. // This uses an additional semaphore to reduce the memory usage. testSema := make(chan struct{}, cap(options.Semaphore)) for i, pkgName := range explicitPkgNames { pkgName := pkgName buf := &bufs[i] testSema <- struct{}{} wg.Add(1) go func() { defer wg.Done() defer func() { <-testSema }() defer close(buf.done) stdout := (*testStdout)(buf) stderr := (*testStderr)(buf) passed, err := Test(pkgName, stdout, stderr, options, outpath) if err != nil { wd, getwdErr := os.Getwd() if getwdErr != nil { wd = "" } diagnostics.CreateDiagnostics(err).WriteTo(os.Stderr, wd) os.Exit(1) } if !passed { select { case fail <- struct{}{}: default: } } }() } // Wait for all tests to finish. wg.Wait() close(fail) if _, fail := <-fail; fail { os.Exit(1) } case "monitor": config, err := builder.NewConfig(options) handleCompilerError(err) err = Monitor("", *port, config) handleCompilerError(err) case "ports": serialPortInfo, err := ListSerialPorts() handleCompilerError(err) if len(serialPortInfo) == 0 { fmt.Println("No serial ports found.") } fmt.Printf("%-20s %-9s %s\n", "Port", "ID", "Boards") for _, s := range serialPortInfo { fmt.Printf("%-20s %4s:%4s %s\n", s.Name, s.VID, s.PID, s.Target) } case "targets": specs, err := compileopts.GetTargetSpecs() if err != nil { fmt.Fprintln(os.Stderr, "could not list targets:", err) os.Exit(1) return } names := []string{} for key := range specs { names = append(names, key) } sort.Strings(names) for _, name := range names { fmt.Println(name) } case "info": if flag.NArg() == 1 { options.Target = flag.Arg(0) } else if flag.NArg() > 1 { fmt.Fprintln(os.Stderr, "only one target name is accepted") usage(command) os.Exit(1) } config, err := builder.NewConfig(options) if err != nil { fmt.Fprintln(os.Stderr, err) usage(command) os.Exit(1) } config.GoMinorVersion = 0 // this avoids creating the list of Go1.x build tags. cachedGOROOT, err := loader.GetCachedGoroot(config) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } if *flagJSON { json, _ := json.MarshalIndent(struct { Target *compileopts.TargetSpec `json:"target"` GOROOT string `json:"goroot"` GOOS string `json:"goos"` GOARCH string `json:"goarch"` GOARM string `json:"goarm"` GOMIPS string `json:"gomips"` BuildTags []string `json:"build_tags"` GC string `json:"garbage_collector"` Scheduler string `json:"scheduler"` LLVMTriple string `json:"llvm_triple"` }{ Target: config.Target, GOROOT: cachedGOROOT, GOOS: config.GOOS(), GOARCH: config.GOARCH(), GOARM: config.GOARM(), GOMIPS: config.GOMIPS(), BuildTags: config.BuildTags(), GC: config.GC(), Scheduler: config.Scheduler(), LLVMTriple: config.Triple(), }, "", " ") fmt.Println(string(json)) } else { fmt.Printf("LLVM triple: %s\n", config.Triple()) fmt.Printf("GOOS: %s\n", config.GOOS()) fmt.Printf("GOARCH: %s\n", config.GOARCH()) fmt.Printf("build tags: %s\n", strings.Join(config.BuildTags(), " ")) fmt.Printf("garbage collector: %s\n", config.GC()) fmt.Printf("scheduler: %s\n", config.Scheduler()) fmt.Printf("cached GOROOT: %s\n", cachedGOROOT) } case "list": config, err := builder.NewConfig(options) if err != nil { fmt.Fprintln(os.Stderr, err) usage(command) os.Exit(1) } var extraArgs []string if *flagJSON { extraArgs = append(extraArgs, "-json") } if flagDeps { extraArgs = append(extraArgs, "-deps") } if flagTest { extraArgs = append(extraArgs, "-test") } cmd, err := loader.List(config, extraArgs, flag.Args()) if err != nil { fmt.Fprintln(os.Stderr, "failed to run `go list`:", err) os.Exit(1) } cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err = cmd.Run() if err != nil { if exitErr, ok := err.(*exec.ExitError); ok { os.Exit(exitErr.ExitCode()) } fmt.Fprintln(os.Stderr, "failed to run `go list`:", err) os.Exit(1) } case "clean": // remove cache directory err := os.RemoveAll(goenv.Get("GOCACHE")) if err != nil { fmt.Fprintln(os.Stderr, "cannot clean cache:", err) os.Exit(1) } case "help": command := "" if flag.NArg() >= 1 { command = flag.Arg(0) } usage(command) case "version": goversion := "" if s, err := goenv.GorootVersionString(); err == nil { goversion = s } fmt.Printf("tinygo version %s %s/%s (using go version %s and LLVM version %s)\n", goenv.Version(), runtime.GOOS, runtime.GOARCH, goversion, llvm.Version) case "env": if flag.NArg() == 0 { // Show all environment variables. for _, key := range goenv.Keys { fmt.Printf("%s=%#v\n", key, goenv.Get(key)) } } else { // Show only one (or a few) environment variables. for i := 0; i < flag.NArg(); i++ { fmt.Println(goenv.Get(flag.Arg(i))) } } default: fmt.Fprintln(os.Stderr, "Unknown command:", command) usage("") os.Exit(1) } } // testOutputBuf is used to buffer the output of concurrent tests. type testOutputBuf struct { mu sync.Mutex output []outputEntry stdout, stderr io.Writer outerr, errerr error done chan struct{} } // flush the output to stdout and stderr. // This waits until done is closed. func (b *testOutputBuf) flush(stdout, stderr io.Writer) error { b.mu.Lock() var err error b.stdout = stdout b.stderr = stderr for _, e := range b.output { var w io.Writer var errDst *error if e.stderr { w = stderr errDst = &b.errerr } else { w = stdout errDst = &b.outerr } if *errDst != nil { continue } _, werr := w.Write(e.data) if werr != nil { if err == nil { err = werr } *errDst = err } } b.mu.Unlock() <-b.done return err } // testStdout writes stdout from a test to the output buffer. type testStdout testOutputBuf func (out *testStdout) Write(data []byte) (int, error) { buf := (*testOutputBuf)(out) buf.mu.Lock() if buf.stdout != nil { // Write the output directly. err := out.outerr buf.mu.Unlock() if err != nil { return 0, err } return buf.stdout.Write(data) } defer buf.mu.Unlock() // Append the output. if len(buf.output) == 0 || buf.output[len(buf.output)-1].stderr { buf.output = append(buf.output, outputEntry{ stderr: false, }) } last := &buf.output[len(buf.output)-1] last.data = append(last.data, data...) return len(data), nil } // testStderr writes stderr from a test to the output buffer. type testStderr testOutputBuf func (out *testStderr) Write(data []byte) (int, error) { buf := (*testOutputBuf)(out) buf.mu.Lock() if buf.stderr != nil { // Write the output directly. err := out.errerr buf.mu.Unlock() if err != nil { return 0, err } return buf.stderr.Write(data) } defer buf.mu.Unlock() // Append the output. if len(buf.output) == 0 || !buf.output[len(buf.output)-1].stderr { buf.output = append(buf.output, outputEntry{ stderr: true, }) } last := &buf.output[len(buf.output)-1] last.data = append(last.data, data...) return len(data), nil } type outputEntry struct { stderr bool data []byte } // handleChdirFlag handles the -C flag before doing anything else. // The -C flag must be the first flag on the command line, to make it easy to find // even with commands that have custom flag parsing. // handleChdirFlag handles the flag by chdir'ing to the directory // and then removing that flag from the command line entirely. // // We have to handle the -C flag this way for two reasons: // // 1. Toolchain selection needs to be in the right directory to look for go.mod and go.work. // // 2. A toolchain switch later on reinvokes the new go command with the same arguments. // The parent toolchain has already done the chdir; the child must not try to do it again. func handleChdirFlag() { used := 2 // b.c. command at os.Args[1] if used >= len(os.Args) { return } var dir string switch a := os.Args[used]; { default: return case a == "-C", a == "--C": if used+1 >= len(os.Args) { return } dir = os.Args[used+1] os.Args = slicesDelete(os.Args, used, used+2) case strings.HasPrefix(a, "-C="), strings.HasPrefix(a, "--C="): _, dir, _ = strings.Cut(a, "=") os.Args = slicesDelete(os.Args, used, used+1) } if err := os.Chdir(dir); err != nil { fmt.Fprintln(os.Stderr, "cannot chdir:", err) os.Exit(1) } } // go1.19 compatibility: lacks slices package func slicesDelete[S ~[]E, E any](s S, i, j int) S { _ = s[i:j:len(s)] // bounds check if i == j { return s } return append(s[:i], s[j:]...) } ================================================ FILE: main_test.go ================================================ package main // This file tests the compiler by running Go files in testdata/*.go and // comparing their output with the expected output in testdata/*.txt. import ( "bufio" "bytes" "context" "errors" "flag" "io" "os" "os/exec" "reflect" "regexp" "runtime" "strings" "sync" "testing" "time" "github.com/aykevl/go-wasm" "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" "github.com/tetratelabs/wazero/sys" "github.com/tinygo-org/tinygo/builder" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/diagnostics" "github.com/tinygo-org/tinygo/goenv" ) const TESTDATA = "testdata" var testTarget = flag.String("target", "", "override test target") var testOnlyCurrentOS = flag.Bool("only-current-os", false, "") var supportedLinuxArches = map[string]string{ "AMD64Linux": "linux/amd64", "X86Linux": "linux/386", "ARMLinux": "linux/arm/6", "ARM64Linux": "linux/arm64", "MIPSLinux": "linux/mips/hardfloat", "WASIp1": "wasip1/wasm", } func init() { major, _, _ := goenv.GetGorootVersion() if major < 21 { // Go 1.20 backwards compatibility. // Should be removed once we drop support for Go 1.20. delete(supportedLinuxArches, "WASIp1") } } var sema = make(chan struct{}, runtime.NumCPU()) func TestBuild(t *testing.T) { t.Parallel() tests := []string{ "alias.go", "atomic.go", "binop.go", "calls.go", "cgo/", "channel.go", "embed/", "float.go", "gc.go", "generics.go", "goroutines.go", "init.go", "init_multi.go", "interface.go", "json.go", "map.go", "math.go", "oldgo/", "print.go", "reflect.go", "signal.go", "slice.go", "sort.go", "stdlib.go", "string.go", "structs.go", "testing.go", "timers.go", "zeroalloc.go", } // Go 1.21 made some changes to the language, which we can only test when // we're actually on Go 1.21. _, minor, err := goenv.GetGorootVersion() if err != nil { t.Fatal("could not get version:", minor) } if minor >= 21 { tests = append(tests, "go1.21.go") } if minor >= 22 { tests = append(tests, "go1.22/") } if minor >= 23 { tests = append(tests, "go1.23/") } if *testTarget != "" { // This makes it possible to run one specific test (instead of all), // which is especially useful to quickly check whether some changes // affect a particular target architecture. runPlatTests(optionsFromTarget(*testTarget, sema), tests, t) return } t.Run("Host", func(t *testing.T) { t.Parallel() runPlatTests(optionsFromTarget("", sema), tests, t) }) // Test a few build options. t.Run("build-options", func(t *testing.T) { t.Parallel() // Test with few optimizations enabled (no inlining, etc). t.Run("opt=1", func(t *testing.T) { t.Parallel() opts := optionsFromTarget("", sema) opts.Opt = "1" runTestWithConfig("stdlib.go", t, opts, nil, nil) }) // Test with only the bare minimum of optimizations enabled. // TODO: fix this for stdlib.go, which currently fails. t.Run("opt=0", func(t *testing.T) { t.Parallel() opts := optionsFromTarget("", sema) opts.Opt = "0" runTestWithConfig("print.go", t, opts, nil, nil) }) t.Run("ldflags", func(t *testing.T) { t.Parallel() opts := optionsFromTarget("", sema) opts.GlobalValues = map[string]map[string]string{ "main": { "someGlobal": "foobar", }, } runTestWithConfig("ldflags.go", t, opts, nil, nil) }) }) if testing.Short() { // Don't test other targets when the -short flag is used. Only test the // host system. return } if !*testOnlyCurrentOS { t.Run("EmulatedCortexM3", func(t *testing.T) { t.Parallel() runPlatTests(optionsFromTarget("cortex-m-qemu", sema), tests, t) }) t.Run("EmulatedRISCV", func(t *testing.T) { t.Parallel() runPlatTests(optionsFromTarget("riscv-qemu", sema), tests, t) }) t.Run("AVR", func(t *testing.T) { t.Parallel() runPlatTests(optionsFromTarget("simavr", sema), tests, t) }) t.Run("WebAssembly", func(t *testing.T) { t.Parallel() runPlatTests(optionsFromTarget("wasm", sema), tests, t) // Test with -gc=boehm. t.Run("gc.go-boehm", func(t *testing.T) { t.Parallel() optionsBoehm := optionsFromTarget("wasm", sema) optionsBoehm.GC = "boehm" runTest("gc.go", optionsBoehm, t, nil, nil) }) }) t.Run("WASIp1", func(t *testing.T) { t.Parallel() runPlatTests(optionsFromTarget("wasip1", sema), tests, t) // Test with -gc=boehm. t.Run("gc.go-boehm", func(t *testing.T) { t.Parallel() optionsBoehm := optionsFromTarget("wasip1", sema) optionsBoehm.GC = "boehm" runTest("gc.go", optionsBoehm, t, nil, nil) }) }) t.Run("WASIp2", func(t *testing.T) { t.Parallel() runPlatTests(optionsFromTarget("wasip2", sema), tests, t) }) } if runtime.GOOS == "linux" { for name, osArch := range supportedLinuxArches { options := optionsFromOSARCH(osArch, sema) if options.GOARCH != runtime.GOARCH { // Native architecture already run above. t.Run(name, func(t *testing.T) { runPlatTests(options, tests, t) }) } } t.Run("MIPS little-endian", func(t *testing.T) { // Run a single test for GOARCH=mipsle to see whether it works at // all. It is already mostly tested because GOARCH=mips and // GOARCH=mipsle are so similar, but it's good to have an extra test // to be sure. t.Parallel() options := optionsFromOSARCH("linux/mipsle/softfloat", sema) runTest("cgo/", options, t, nil, nil) }) } else if runtime.GOOS == "windows" { if runtime.GOARCH != "386" { t.Run("Windows386", func(t *testing.T) { t.Parallel() runPlatTests(optionsFromOSARCH("windows/386", sema), tests, t) }) } } } func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { emuCheck(t, options) spec, err := compileopts.LoadTarget(&options) if err != nil { t.Fatal("failed to load target spec:", err) } // FIXME: this should really be: // isWebAssembly := strings.HasPrefix(spec.Triple, "wasm") isWASI := strings.HasPrefix(options.Target, "wasi") isWebAssembly := isWASI || strings.HasPrefix(options.Target, "wasm") || (options.Target == "" && strings.HasPrefix(options.GOARCH, "wasm")) isBaremetal := options.Target == "simavr" || options.Target == "cortex-m-qemu" || options.Target == "riscv-qemu" for _, name := range tests { if options.GOOS == "linux" && (options.GOARCH == "arm" || options.GOARCH == "386") { switch name { case "timers.go": // Timer tests do not work because syscall.seek is implemented // as Assembly in mainline Go and causes linker failure continue } } if options.GOOS == "linux" && (options.GOARCH == "mips" || options.GOARCH == "mipsle") { if name == "atomic.go" || name == "timers.go" { // 64-bit atomic operations aren't currently supported on MIPS. continue } } if options.GOOS == "linux" && options.GOARCH == "mips" { if name == "cgo/" { // CGo isn't supported yet on big-endian systems (needs updates // to bitfield access methods). continue } } if options.Target == "simavr" { // Not all tests are currently supported on AVR. // Skip the ones that aren't. switch name { case "reflect.go": // Reflect tests do not run correctly, probably because of the // limited amount of memory. continue case "gc.go": // Does not pass due to high mark false positive rate. continue case "json.go", "stdlib.go", "testing.go": // Too big for AVR. Doesn't fit in flash/RAM. continue case "math.go": // Needs newer picolibc version (for sqrt). continue case "cgo/": // CGo function pointers don't work on AVR (needs LLVM 16 and // some compiler changes). continue case "timers.go": // Crashes starting with Go 1.23. // Bug: https://github.com/llvm/llvm-project/issues/104032 continue default: } } if options.Target == "wasip2" { switch name { case "cgo/": // waisp2 use our own libc; cgo tests fail continue } } if isWebAssembly || isBaremetal || options.GOOS == "windows" { switch name { case "signal.go": // Signals only work on POSIX-like systems. continue } } name := name // redefine to avoid race condition t.Run(name, func(t *testing.T) { t.Parallel() runTest(name, options, t, nil, nil) }) } if !strings.HasPrefix(spec.Emulator, "simavr ") { t.Run("env.go", func(t *testing.T) { t.Parallel() runTest("env.go", options, t, []string{"first", "second"}, []string{"ENV1=VALUE1", "ENV2=VALUE2"}) }) } if isWebAssembly { t.Run("alias.go-scheduler-none", func(t *testing.T) { t.Parallel() options := compileopts.Options(options) options.Scheduler = "none" runTest("alias.go", options, t, nil, nil) }) } if options.Target == "" || isWASI { t.Run("filesystem.go", func(t *testing.T) { t.Parallel() runTest("filesystem.go", options, t, nil, nil) }) } if options.Target == "" || options.Target == "wasm" || isWASI { t.Run("rand.go", func(t *testing.T) { t.Parallel() runTest("rand.go", options, t, nil, nil) }) } if !isWebAssembly { // The recover() builtin isn't supported yet on WebAssembly and Windows. t.Run("recover.go", func(t *testing.T) { t.Parallel() runTest("recover.go", options, t, nil, nil) }) } } func emuCheck(t *testing.T, options compileopts.Options) { // Check if the emulator is installed. spec, err := compileopts.LoadTarget(&options) if err != nil { t.Fatal("failed to load target spec:", err) } if spec.Emulator != "" { emulatorCommand := strings.SplitN(spec.Emulator, " ", 2)[0] _, err := exec.LookPath(emulatorCommand) if err != nil { if errors.Is(err, exec.ErrNotFound) { t.Skipf("emulator not installed: %q", emulatorCommand) } t.Errorf("searching for emulator: %v", err) return } } } func optionsFromTarget(target string, sema chan struct{}) compileopts.Options { separators := strings.Count(target, "/") if (separators == 1 || separators == 2) && !strings.HasSuffix(target, ".json") { return optionsFromOSARCH(target, sema) } return compileopts.Options{ // GOOS/GOARCH are only used if target == "" GOOS: goenv.Get("GOOS"), GOARCH: goenv.Get("GOARCH"), GOARM: goenv.Get("GOARM"), GOMIPS: goenv.Get("GOMIPS"), Target: target, Semaphore: sema, InterpTimeout: 180 * time.Second, Debug: true, VerifyIR: true, Opt: "z", } } // optionsFromOSARCH returns a set of options based on the "osarch" string. This // string is in the form of "os/arch/subarch", with the subarch only sometimes // being necessary. Examples are "darwin/amd64" or "linux/arm/7". func optionsFromOSARCH(osarch string, sema chan struct{}) compileopts.Options { parts := strings.Split(osarch, "/") options := compileopts.Options{ GOOS: parts[0], GOARCH: parts[1], Semaphore: sema, InterpTimeout: 180 * time.Second, Debug: true, VerifyIR: true, Opt: "z", } switch options.GOARCH { case "arm": options.GOARM = parts[2] case "mips", "mipsle": options.GOMIPS = parts[2] } return options } func runTest(name string, options compileopts.Options, t *testing.T, cmdArgs, environmentVars []string) { t.Helper() runTestWithConfig(name, t, options, cmdArgs, environmentVars) } func runTestWithConfig(name string, t *testing.T, options compileopts.Options, cmdArgs, environmentVars []string) { t.Helper() // Get the expected output for this test. // Note: not using filepath.Join as it strips the path separator at the end // of the path. path := TESTDATA + "/" + name // Get the expected output for this test. expectedOutputPath := path[:len(path)-3] + ".txt" pkgName := "./" + path if path[len(path)-1] == '/' { expectedOutputPath = path + "out.txt" options.Directory = path pkgName = "." } config, err := builder.NewConfig(&options) if err != nil { t.Fatal(err) } // Build the test binary. stdout := &bytes.Buffer{} _, err = buildAndRun(pkgName, config, stdout, cmdArgs, environmentVars, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error { return cmd.Run() }) if err != nil { w := &bytes.Buffer{} diagnostics.CreateDiagnostics(err).WriteTo(w, "") for _, line := range strings.Split(strings.TrimRight(w.String(), "\n"), "\n") { t.Log(line) } if stdout.Len() != 0 { t.Logf("output:\n%s", stdout.String()) } t.Fail() return } actual := stdout.Bytes() if config.EmulatorName() == "simavr" { // Strip simavr log formatting. actual = bytes.Replace(actual, []byte{0x1b, '[', '3', '2', 'm'}, nil, -1) actual = bytes.Replace(actual, []byte{0x1b, '[', '0', 'm'}, nil, -1) actual = bytes.Replace(actual, []byte{'.', '.', '\n'}, []byte{'\n'}, -1) actual = bytes.Replace(actual, []byte{'\n', '.', '\n'}, []byte{'\n', '\n'}, -1) } if name == "testing.go" { // Strip actual time. re := regexp.MustCompile(`\([0-9]\.[0-9][0-9]s\)`) actual = re.ReplaceAllLiteral(actual, []byte{'(', '0', '.', '0', '0', 's', ')'}) } // Check whether the command ran successfully. if err != nil { t.Error("failed to run:", err) } checkOutput(t, expectedOutputPath, actual) if t.Failed() { r := bufio.NewReader(bytes.NewReader(actual)) for { line, err := r.ReadString('\n') if err != nil { break } t.Log("stdout:", line[:len(line)-1]) } t.Fail() } } // Test WebAssembly files for certain properties. func TestWebAssembly(t *testing.T) { t.Parallel() type testCase struct { name string target string panicStrategy string imports []string } for _, tc := range []testCase{ // Test whether there really are no imports when using -panic=trap. This // tests the bugfix for https://github.com/tinygo-org/tinygo/issues/4161. {name: "panic-default", target: "wasip1", imports: []string{"wasi_snapshot_preview1.fd_write", "wasi_snapshot_preview1.random_get"}}, {name: "panic-trap", target: "wasm-unknown", panicStrategy: "trap", imports: []string{}}, } { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() tmpdir := t.TempDir() options := optionsFromTarget(tc.target, sema) options.PanicStrategy = tc.panicStrategy config, err := builder.NewConfig(&options) if err != nil { t.Fatal(err) } result, err := builder.Build("testdata/trivialpanic.go", ".wasm", tmpdir, config) if err != nil { t.Fatal("failed to build binary:", err) } f, err := os.Open(result.Binary) if err != nil { t.Fatal("could not open output binary:", err) } defer f.Close() module, err := wasm.Parse(f) if err != nil { t.Fatal("could not parse output binary:", err) } // Test the list of imports. if tc.imports != nil { var imports []string for _, section := range module.Sections { switch section := section.(type) { case *wasm.SectionImport: for _, symbol := range section.Entries { imports = append(imports, symbol.Module+"."+symbol.Field) } } } if !stringSlicesEqual(imports, tc.imports) { t.Errorf("import list not as expected!\nexpected: %v\nactual: %v", tc.imports, imports) } } }) } } func stringSlicesEqual(s1, s2 []string) bool { // We can use slices.Equal once we drop support for Go 1.20 (it was added in // Go 1.21). if len(s1) != len(s2) { return false } for i, s := range s1 { if s != s2[i] { return false } } return true } func TestWasmExport(t *testing.T) { t.Parallel() type testCase struct { name string target string buildMode string scheduler string file string noOutput bool command bool // call _start (command mode) instead of _initialize } tests := []testCase{ // "command mode" WASI { name: "WASIp1-command", target: "wasip1", command: true, }, // "reactor mode" WASI (with -buildmode=c-shared) { name: "WASIp1-reactor", target: "wasip1", buildMode: "c-shared", }, // Make sure reactor mode also works without a scheduler. { name: "WASIp1-reactor-noscheduler", target: "wasip1", buildMode: "c-shared", scheduler: "none", file: "wasmexport-noscheduler.go", }, // Test -target=wasm-unknown with the default build mode (which is // c-shared). { name: "wasm-unknown-reactor", target: "wasm-unknown", file: "wasmexport-noscheduler.go", noOutput: true, // wasm-unknown cannot produce output }, // Test -target=wasm-unknown with -buildmode=default, which makes it run // in command mode. { name: "wasm-unknown-command", target: "wasm-unknown", buildMode: "default", file: "wasmexport-noscheduler.go", noOutput: true, // wasm-unknown cannot produce output command: true, }, // Test buildmode=wasi-legacy with WASI. { name: "WASIp1-legacy", target: "wasip1", buildMode: "wasi-legacy", scheduler: "none", file: "wasmexport-noscheduler.go", command: true, }, } for _, tc := range tests { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() // Build the wasm binary. tmpdir := t.TempDir() options := optionsFromTarget(tc.target, sema) options.BuildMode = tc.buildMode options.Scheduler = tc.scheduler buildConfig, err := builder.NewConfig(&options) if err != nil { t.Fatal(err) } filename := "wasmexport.go" if tc.file != "" { filename = tc.file } result, err := builder.Build("testdata/"+filename, ".wasm", tmpdir, buildConfig) if err != nil { t.Fatal("failed to build binary:", err) } // Read the wasm binary back into memory. data, err := os.ReadFile(result.Binary) if err != nil { t.Fatal("could not read wasm binary: ", err) } // Set up the wazero runtime. output := &bytes.Buffer{} ctx := context.Background() r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter()) defer r.Close(ctx) config := wazero.NewModuleConfig(). WithStdout(output).WithStderr(output). WithStartFunctions() // Prepare for testing. var mod api.Module mustCall := func(results []uint64, err error) []uint64 { if err != nil { t.Error("failed to run function:", err) } return results } checkResult := func(name string, results []uint64, expected []uint64) { if len(results) != len(expected) { t.Errorf("%s: expected %v but got %v", name, expected, results) } for i, result := range results { if result != expected[i] { t.Errorf("%s: expected %v but got %v", name, expected, results) break } } } runTests := func() { // Test an exported function without params or return value. checkResult("hello()", mustCall(mod.ExportedFunction("hello").Call(ctx)), nil) // Test that we can call an exported function more than once. checkResult("add(3, 5)", mustCall(mod.ExportedFunction("add").Call(ctx, 3, 5)), []uint64{8}) checkResult("add(7, 9)", mustCall(mod.ExportedFunction("add").Call(ctx, 7, 9)), []uint64{16}) checkResult("add(6, 1)", mustCall(mod.ExportedFunction("add").Call(ctx, 6, 1)), []uint64{7}) // Test that imported functions can call exported functions // again. checkResult("reentrantCall(2, 3)", mustCall(mod.ExportedFunction("reentrantCall").Call(ctx, 2, 3)), []uint64{5}) checkResult("reentrantCall(1, 8)", mustCall(mod.ExportedFunction("reentrantCall").Call(ctx, 1, 8)), []uint64{9}) // Check that goroutines started inside //go:wasmexport don't // block the called function from returning. checkResult("goroutineExit()", mustCall(mod.ExportedFunction("goroutineExit").Call(ctx)), nil) } // Add wasip1 module. wasi_snapshot_preview1.MustInstantiate(ctx, r) // Add custom "tester" module. callOutside := func(a, b int32) int32 { results, err := mod.ExportedFunction("add").Call(ctx, uint64(a), uint64(b)) if err != nil { t.Error("could not call exported add function:", err) } return int32(results[0]) } callTestMain := func() { runTests() } builder := r.NewHostModuleBuilder("tester") builder.NewFunctionBuilder().WithFunc(callOutside).Export("callOutside") builder.NewFunctionBuilder().WithFunc(callTestMain).Export("callTestMain") _, err = builder.Instantiate(ctx) if err != nil { t.Fatal(err) } // Parse and instantiate the wasm. mod, err = r.InstantiateWithConfig(ctx, data, config) if err != nil { t.Fatal("could not instantiate wasm module:", err) } // Initialize the module and run the tests. if tc.command { // Call _start (the entry point), which calls // tester.callTestMain, which then runs all the tests. _, err := mod.ExportedFunction("_start").Call(ctx) if err != nil { if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() == 0 { // Exited with code 0. Nothing to worry about. } else { t.Error("failed to run _start:", err) } } } else { // Run the _initialize call, because this is reactor mode wasm. mustCall(mod.ExportedFunction("_initialize").Call(ctx)) runTests() } // Check that the output matches the expected output. // (Skip this for wasm-unknown because it can't produce output). if !tc.noOutput { checkOutput(t, "testdata/wasmexport.txt", output.Bytes()) } }) } } // Test js.FuncOf (for syscall/js). // This test might be extended in the future to cover more cases in syscall/js. func TestWasmFuncOf(t *testing.T) { // Build the wasm binary. tmpdir := t.TempDir() options := optionsFromTarget("wasm", sema) buildConfig, err := builder.NewConfig(&options) if err != nil { t.Fatal(err) } result, err := builder.Build("testdata/wasmfunc.go", ".wasm", tmpdir, buildConfig) if err != nil { t.Fatal("failed to build binary:", err) } // Test the resulting binary using NodeJS. output := &bytes.Buffer{} cmd := exec.Command("node", "testdata/wasmfunc.js", result.Binary, buildConfig.BuildMode()) cmd.Stdout = output cmd.Stderr = output err = cmd.Run() if err != nil { t.Error("failed to run node:", err) } checkOutput(t, "testdata/wasmfunc.txt", output.Bytes()) } // Test //go:wasmexport in JavaScript (using NodeJS). func TestWasmExportJS(t *testing.T) { t.Parallel() type testCase struct { name string buildMode string } tests := []testCase{ {name: "default"}, {name: "c-shared", buildMode: "c-shared"}, } for _, tc := range tests { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() // Build the wasm binary. tmpdir := t.TempDir() options := optionsFromTarget("wasm", sema) options.BuildMode = tc.buildMode buildConfig, err := builder.NewConfig(&options) if err != nil { t.Fatal(err) } result, err := builder.Build("testdata/wasmexport-noscheduler.go", ".wasm", tmpdir, buildConfig) if err != nil { t.Fatal("failed to build binary:", err) } // Test the resulting binary using NodeJS. output := &bytes.Buffer{} cmd := exec.Command("node", "testdata/wasmexport.js", result.Binary, buildConfig.BuildMode()) cmd.Stdout = output cmd.Stderr = output err = cmd.Run() if err != nil { t.Error("failed to run node:", err) } checkOutput(t, "testdata/wasmexport.txt", output.Bytes()) }) } } // Test whether Go.run() (in wasm_exec.js) normally returns and returns the // right exit code. func TestWasmExit(t *testing.T) { t.Parallel() type testCase struct { name string output string } tests := []testCase{ {name: "normal", output: "exit code: 0\n"}, {name: "exit-0", output: "exit code: 0\n"}, {name: "exit-0-sleep", output: "slept\nexit code: 0\n"}, {name: "exit-1", output: "exit code: 1\n"}, {name: "exit-1-sleep", output: "slept\nexit code: 1\n"}, } for _, tc := range tests { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() options := optionsFromTarget("wasm", sema) buildConfig, err := builder.NewConfig(&options) if err != nil { t.Fatal(err) } buildConfig.Target.Emulator = "node testdata/wasmexit.js {}" output := &bytes.Buffer{} _, err = buildAndRun("testdata/wasmexit.go", buildConfig, output, []string{tc.name}, nil, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error { return cmd.Run() }) if err != nil { t.Error(err) } expected := "wasmexit test: " + tc.name + "\n" + tc.output checkOutputData(t, []byte(expected), output.Bytes()) }) } } // Check whether the output of a test equals the expected output. func checkOutput(t *testing.T, filename string, actual []byte) { t.Helper() expectedOutput, err := os.ReadFile(filename) if err != nil { t.Fatal("could not read output file:", err) } checkOutputData(t, expectedOutput, actual) } func checkOutputData(t *testing.T, expectedOutput, actual []byte) { t.Helper() expectedOutput = bytes.ReplaceAll(expectedOutput, []byte("\r\n"), []byte("\n")) actual = bytes.ReplaceAll(actual, []byte("\r\n"), []byte("\n")) if !bytes.Equal(actual, expectedOutput) { t.Errorf("output did not match (expected %d bytes, got %d bytes):", len(expectedOutput), len(actual)) t.Error(string(Diff("expected", expectedOutput, "actual", actual))) } } func TestTest(t *testing.T) { t.Parallel() type targ struct { name string opts compileopts.Options } targs := []targ{ // Host {"Host", optionsFromTarget("", sema)}, } if !testing.Short() { if runtime.GOOS == "linux" { for name, osArch := range supportedLinuxArches { options := optionsFromOSARCH(osArch, sema) if options.GOARCH != runtime.GOARCH { // Native architecture already run above. targs = append(targs, targ{name, options}) } } } targs = append(targs, // QEMU microcontrollers targ{"EmulatedCortexM3", optionsFromTarget("cortex-m-qemu", sema)}, targ{"EmulatedRISCV", optionsFromTarget("riscv-qemu", sema)}, // Node/Wasmtime targ{"WASM", optionsFromTarget("wasm", sema)}, targ{"WASI", optionsFromTarget("wasip1", sema)}, ) } for _, targ := range targs { targ := targ t.Run(targ.name, func(t *testing.T) { t.Parallel() emuCheck(t, targ.opts) t.Run("Pass", func(t *testing.T) { t.Parallel() // Test a package which builds and passes normally. var wg sync.WaitGroup defer wg.Wait() out := ioLogger(t, &wg) defer out.Close() opts := targ.opts passed, err := Test("github.com/tinygo-org/tinygo/tests/testing/pass", out, out, &opts, "") if err != nil { t.Errorf("test error: %v", err) } if !passed { t.Error("test failed") } }) t.Run("Fail", func(t *testing.T) { t.Parallel() // Test a package which builds fine but fails. var wg sync.WaitGroup defer wg.Wait() out := ioLogger(t, &wg) defer out.Close() opts := targ.opts passed, err := Test("github.com/tinygo-org/tinygo/tests/testing/fail", out, out, &opts, "") if err != nil { t.Errorf("test error: %v", err) } if passed { t.Error("test passed") } }) if targ.name != "Host" { // Emulated tests are somewhat slow, and these do not need to be run across every platform. return } t.Run("Nothing", func(t *testing.T) { t.Parallel() // Test a package with no test files. var wg sync.WaitGroup defer wg.Wait() out := ioLogger(t, &wg) defer out.Close() var output bytes.Buffer opts := targ.opts passed, err := Test("github.com/tinygo-org/tinygo/tests/testing/nothing", io.MultiWriter(&output, out), out, &opts, "") if err != nil { t.Errorf("test error: %v", err) } if !passed { t.Error("test failed") } if !strings.Contains(output.String(), "[no test files]") { t.Error("missing [no test files] in output") } }) t.Run("BuildErr", func(t *testing.T) { t.Parallel() // Test a package which fails to build. var wg sync.WaitGroup defer wg.Wait() out := ioLogger(t, &wg) defer out.Close() opts := targ.opts passed, err := Test("github.com/tinygo-org/tinygo/tests/testing/builderr", out, out, &opts, "") if err == nil { t.Error("test did not error") } if passed { t.Error("test passed") } }) }) } } func ioLogger(t *testing.T, wg *sync.WaitGroup) io.WriteCloser { r, w := io.Pipe() wg.Add(1) go func() { defer wg.Done() defer r.Close() scanner := bufio.NewScanner(r) for scanner.Scan() { t.Log(scanner.Text()) } }() return w } func TestGetListOfPackages(t *testing.T) { opts := optionsFromTarget("", sema) tests := []struct { pkgs []string expectedPkgs []string expectesError bool }{ { pkgs: []string{"./tests/testing/recurse/..."}, expectedPkgs: []string{ "github.com/tinygo-org/tinygo/tests/testing/recurse", "github.com/tinygo-org/tinygo/tests/testing/recurse/subdir", }, }, { pkgs: []string{"./tests/testing/pass"}, expectedPkgs: []string{ "github.com/tinygo-org/tinygo/tests/testing/pass", }, }, { pkgs: []string{"./tests/testing"}, expectesError: true, }, } for _, test := range tests { actualPkgs, err := getListOfPackages(test.pkgs, &opts) if err != nil && !test.expectesError { t.Errorf("unexpected error: %v", err) } else if err == nil && test.expectesError { t.Error("expected error, but got none") } if !reflect.DeepEqual(test.expectedPkgs, actualPkgs) { t.Errorf("expected two slices to be equal, expected %v got %v", test.expectedPkgs, actualPkgs) } } } // This TestMain is necessary because TinyGo may also be invoked to run certain // LLVM tools in a separate process. Not capturing these invocations would lead // to recursive tests. func TestMain(m *testing.M) { if len(os.Args) >= 2 { switch os.Args[1] { case "clang", "ld.lld", "wasm-ld": // Invoke a specific tool. err := builder.RunTool(os.Args[1], os.Args[2:]...) if err != nil { // The tool should have printed an error message already. // Don't print another error message here. os.Exit(1) } os.Exit(0) } } // Run normal tests. os.Exit(m.Run()) } ================================================ FILE: misspell.csv ================================================ acces,access acuire,acquire addess,address adust,adjust allcoate,allocate alloated,allocated archtecture,architecture arcive,archive ardiuno,arduino beconfigured,be configured calcluate,calculate colum,column configration,configuration contants,constants cricital,critical deffered,deferred evaulator,evaluator evironment,environment freqency,frequency frquency,frequency implmented,implemented interrput,interrupt interrut,interrupt interupt,interrupt measuing,measuring numer of,number of orignal,original overrided,overridden poiners,pointers poitner,pointer probbably,probably recogized,recognized refection,reflection requries,requires satisifying,satisfying simulataneously,simultaneously suggets,suggests transmition,transmission undefied,undefined unecessary,unnecessary unsiged,unsigned ================================================ FILE: monitor.go ================================================ package main import ( "bufio" "debug/dwarf" "debug/elf" "debug/macho" "debug/pe" "errors" "fmt" "go/token" "io" "net" "os" "os/signal" "regexp" "strconv" "strings" "time" "github.com/mattn/go-tty" "github.com/tinygo-org/tinygo/compileopts" "go.bug.st/serial" "go.bug.st/serial/enumerator" ) // Monitor connects to the given port and reads/writes the serial port. func Monitor(executable, port string, config *compileopts.Config) error { const timeout = time.Second * 3 var exit func() // function to be called before exiting var serialConn io.ReadWriter if config.Options.Serial == "rtt" { // Use the RTT interface, which is documented (in part) here: // https://wiki.segger.com/RTT // Try to find the "_SEGGER_RTT" symbol (machine.rttSerialInstance) // symbol, which is the RTT control block. file, err := elf.Open(executable) if err != nil { return fmt.Errorf("could not open ELF file to determine RTT control block: %w", err) } defer file.Close() symbols, err := file.Symbols() if err != nil { return fmt.Errorf("could not read ELF symbol table to determine RTT control block: %w", err) } var address uint64 for _, symbol := range symbols { if symbol.Name == "_SEGGER_RTT" { address = symbol.Value break } } if address == 0 { return fmt.Errorf("could not find RTT control block in ELF file") } // Start an openocd process in the background. args, err := config.OpenOCDConfiguration() if err != nil { return err } args = append(args, "-c", fmt.Sprintf("rtt setup 0x%x 16 \"SEGGER RTT\"", address), "-c", "init", "-c", "rtt server start 0 0") cmd := executeCommand(config.Options, "openocd", args...) stderr, err := cmd.StderrPipe() if err != nil { return err } cmd.Stdout = os.Stdout err = cmd.Start() if err != nil { return err } defer cmd.Process.Kill() exit = func() { // Make sure the openocd process is terminated at exit. // This does not happen through the defer above when exiting through // os.Exit. cmd.Process.Kill() } // Read the stderr, which logs various important messages we need. r := bufio.NewReader(stderr) var telnet net.Conn var timeoutAt time.Time for { // Read the next line from the openocd process. lineBytes, err := r.ReadBytes('\n') if err != nil { return err } line := string(lineBytes) if line == "Info : rtt: No control block found\n" { // Message that is sent back when OpenOCD can't find the control // block after a 'rtt start' message. if time.Now().After(timeoutAt) { return fmt.Errorf("RTT timeout (could not locate RTT control block at 0x%08x)", address) } time.Sleep(time.Millisecond * 100) telnet.Write([]byte("rtt start\r\n")) } else if strings.HasPrefix(line, "Info : Listening on port") { // We need two different ports for controlling OpenOCD // (typically port 4444) and the RTT channel 0 socket (arbitrary // port). var port int var protocol string fmt.Sscanf(line, "Info : Listening on port %d for %s connections\n", &port, &protocol) if protocol == "telnet" && telnet == nil { // Connect to the "telnet" command line interface. telnet, err = net.Dial("tcp4", fmt.Sprintf("localhost:%d", port)) if err != nil { return err } // Tell OpenOCD to start scanning for the RTT control block. telnet.Write([]byte("rtt start\r\n")) // Also make sure we will time out if the control block just // can't be found. timeoutAt = time.Now().Add(timeout) } else if protocol == "rtt" { // Connect to the RTT channel, for both stdin and stdout. conn, err := net.Dial("tcp4", fmt.Sprintf("localhost:%d", port)) if err != nil { return err } serialConn = conn } } else if strings.HasPrefix(line, "Info : rtt: Control block found at") { // Connection established! break } } } else { // -serial=uart or -serial=usb var err error wait := 300 for i := 0; i <= wait; i++ { port, err = getDefaultPort(port, config.Target.SerialPort) if err != nil { if i < wait { time.Sleep(10 * time.Millisecond) continue } return err } break } br := config.Options.BaudRate if br <= 0 { br = 115200 } wait = 300 var p serial.Port for i := 0; i <= wait; i++ { p, err = serial.Open(port, &serial.Mode{BaudRate: br}) if err != nil { if i < wait { time.Sleep(10 * time.Millisecond) continue } return err } serialConn = p break } defer p.Close() } tty, err := tty.Open() if err != nil { return err } defer tty.Close() sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt) defer signal.Stop(sig) go func() { <-sig tty.Close() if exit != nil { exit() } os.Exit(0) }() fmt.Printf("Connected to %s. Press Ctrl-C to exit.\n", port) errCh := make(chan error, 1) go func() { buf := make([]byte, 100*1024) writer := newOutputWriter(os.Stdout, executable) for { n, err := serialConn.Read(buf) if err != nil { errCh <- fmt.Errorf("read error: %w", err) return } writer.Write(buf[:n]) } }() go func() { for { r, err := tty.ReadRune() if err != nil { errCh <- err return } if r == 0 { continue } serialConn.Write([]byte(string(r))) } }() return <-errCh } // SerialPortInfo is a structure that holds information about the port and its // associated TargetSpec. type SerialPortInfo struct { Name string IsUSB bool VID string PID string Target string Spec *compileopts.TargetSpec } // ListSerialPort returns serial port information and any detected TinyGo // target. func ListSerialPorts() ([]SerialPortInfo, error) { maps, err := compileopts.GetTargetSpecs() if err != nil { return nil, err } portsList, err := enumerator.GetDetailedPortsList() if err != nil { return nil, err } serialPortInfo := []SerialPortInfo{} for _, p := range portsList { info := SerialPortInfo{ Name: p.Name, IsUSB: p.IsUSB, VID: p.VID, PID: p.PID, } vid := strings.ToLower(p.VID) pid := strings.ToLower(p.PID) for k, v := range maps { usbInterfaces := v.SerialPort for _, s := range usbInterfaces { parts := strings.Split(s, ":") if len(parts) != 2 { continue } if vid == strings.ToLower(parts[0]) && pid == strings.ToLower(parts[1]) { info.Target = k info.Spec = v } } } serialPortInfo = append(serialPortInfo, info) } return serialPortInfo, nil } var addressMatch = regexp.MustCompile(`^panic: runtime error at 0x([0-9a-f]+): `) // Extract the address from the "panic: runtime error at" message. func extractPanicAddress(line []byte) uint64 { matches := addressMatch.FindSubmatch(line) if matches != nil { address, err := strconv.ParseUint(string(matches[1]), 16, 64) if err == nil { return address } } return 0 } // Convert an address in the binary to a source address location. func addressToLine(executable string, address uint64) (token.Position, error) { data, err := readDWARF(executable) if err != nil { return token.Position{}, err } r := data.Reader() for { e, err := r.Next() if err != nil { return token.Position{}, err } if e == nil { break } switch e.Tag { case dwarf.TagCompileUnit: r.SkipChildren() lr, err := data.LineReader(e) if err != nil { return token.Position{}, err } var lineEntry = dwarf.LineEntry{ EndSequence: true, } for { // Read the next .debug_line entry. prevLineEntry := lineEntry err := lr.Next(&lineEntry) if err != nil { if err == io.EOF { break } return token.Position{}, err } if prevLineEntry.EndSequence && lineEntry.Address == 0 { // Tombstone value. This symbol has been removed, for // example by the --gc-sections linker flag. It is still // here in the debug information because the linker can't // just remove this reference. // Read until the next EndSequence so that this sequence is // skipped. // For more details, see (among others): // https://reviews.llvm.org/D84825 for { err := lr.Next(&lineEntry) if err != nil { return token.Position{}, err } if lineEntry.EndSequence { break } } } if !prevLineEntry.EndSequence { // The chunk describes the code from prevLineEntry to // lineEntry. if prevLineEntry.Address <= address && lineEntry.Address > address { return token.Position{ Filename: prevLineEntry.File.Name, Line: prevLineEntry.Line, Column: prevLineEntry.Column, }, nil } } } } } return token.Position{}, nil // location not found } // Read the DWARF debug information from a given file (in various formats). func readDWARF(executable string) (*dwarf.Data, error) { f, err := os.Open(executable) if err != nil { return nil, err } if file, err := elf.NewFile(f); err == nil { return file.DWARF() } else if file, err := macho.NewFile(f); err == nil { return file.DWARF() } else if file, err := pe.NewFile(f); err == nil { return file.DWARF() } else { return nil, errors.New("unknown binary format") } } type outputWriter struct { out io.Writer executable string line []byte } // newOutputWriter returns an io.Writer that will intercept panic addresses and // will try to insert a source location in the output if the source location can // be found in the executable. func newOutputWriter(out io.Writer, executable string) *outputWriter { return &outputWriter{ out: out, executable: executable, } } func (w *outputWriter) Write(p []byte) (n int, err error) { start := 0 for i, c := range p { if c == '\n' { w.out.Write(p[start : i+1]) start = i + 1 address := extractPanicAddress(w.line) if address != 0 { loc, err := addressToLine(w.executable, address) if err == nil && loc.Filename != "" { fmt.Printf("[tinygo: panic at %s]\n", loc.String()) } } w.line = w.line[:0] } else { w.line = append(w.line, c) } } w.out.Write(p[start:]) n = len(p) return } ================================================ FILE: monitor_test.go ================================================ package main import ( "bytes" "os/exec" "path/filepath" "runtime" "testing" "time" "github.com/tinygo-org/tinygo/builder" "github.com/tinygo-org/tinygo/compileopts" ) func TestTraceback(t *testing.T) { if runtime.GOOS != "linux" { // We care about testing the ELF format, which is only used on Linux // (not on MacOS or Windows). t.Skip("Test only works on Linux") } // Build a small binary that only panics. tmpdir := t.TempDir() config, err := builder.NewConfig(&compileopts.Options{ GOOS: runtime.GOOS, GOARCH: runtime.GOARCH, Opt: "z", InterpTimeout: time.Minute, Debug: true, }) if err != nil { t.Fatal(err) } result, err := builder.Build("testdata/trivialpanic.go", ".elf", tmpdir, config) if err != nil { t.Fatal(err) } // Run this binary, and capture the output. buf := &bytes.Buffer{} cmd := exec.Command(result.Binary) cmd.Stdout = buf cmd.Stderr = buf cmd.Run() // this will return an error because of the panic, ignore it // Extract the "runtime error at" address. line := bytes.TrimSpace(buf.Bytes()) address := extractPanicAddress(line) if address == 0 { t.Fatalf("could not extract panic address from %#v", string(line)) } // Look up the source location for this address. location, err := addressToLine(result.Executable, address) if err != nil { t.Fatal("could not read source location:", err) } // Verify that the source location is as expected. if filepath.Base(location.Filename) != "trivialpanic.go" { t.Errorf("expected path to end with trivialpanic.go, got %#v", location.Filename) } if location.Line != 6 { t.Errorf("expected panic location to be line 6, got line %d", location.Line) } } ================================================ FILE: revive.toml ================================================ ignoreGeneratedHeader = false severity = "warning" confidence = 0.8 errorCode = 0 warningCode = 0 # Enable these as we fix them [rule.blank-imports] Exclude=["src/os/file_other.go"] [rule.context-as-argument] [rule.context-keys-type] [rule.dot-imports] Exclude=["**/*_test.go"] [rule.error-return] [rule.error-strings] [rule.error-naming] [rule.exported] Exclude=["src/reflect/*.go"] [rule.increment-decrement] [rule.var-naming] Exclude=["src/os/*.go"] [rule.var-declaration] #[rule.package-comments] [rule.range] [rule.receiver-naming] [rule.time-naming] [rule.unexported-return] #[rule.indent-error-flow] [rule.errorf] #[rule.empty-block] [rule.superfluous-else] #[rule.unused-parameter] [rule.unreachable-code] Exclude=["src/reflect/visiblefields_test.go", "src/reflect/all_test.go"] #[rule.redefines-builtin-id] ================================================ FILE: src/crypto/internal/boring/sig/sig_other.go ================================================ // Package sig stubs crypto/internal/boring/sig package sig // BoringCrypto indicates that the BoringCrypto module is present. func BoringCrypto() { } // FIPSOnly indicates that package crypto/tls/fipsonly is present. func FIPSOnly() { } // StandardCrypto indicates that standard Go crypto is present. func StandardCrypto() { } ================================================ FILE: src/crypto/rand/rand.go ================================================ // Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package rand implements a cryptographically secure // random number generator. package rand import "io" // Reader is a global, shared instance of a cryptographically // secure random number generator. var Reader io.Reader // Read is a helper function that calls Reader.Read using io.ReadFull. // On return, n == len(b) if and only if err == nil. func Read(b []byte) (n int, err error) { if Reader == nil { panic("no rng") } return io.ReadFull(Reader, b) } ================================================ FILE: src/crypto/rand/rand_arc4random.go ================================================ //go:build darwin || wasip1 || wasip2 || wasm // This implementation of crypto/rand uses the arc4random_buf function // (available on both MacOS and WASI) to generate random numbers. // // Note: arc4random_buf (unlike what the name suggests) does not use the insecure // RC4 cipher. Instead, it uses a high-quality cipher, varying by the libc // implementation. package rand import "unsafe" func init() { Reader = &reader{} } type reader struct { } func (r *reader) Read(b []byte) (n int, err error) { if len(b) != 0 { libc_arc4random_buf(unsafe.Pointer(&b[0]), uint(len(b))) } return len(b), nil } // void arc4random_buf(void *buf, size_t buflen); // //export arc4random_buf func libc_arc4random_buf(buf unsafe.Pointer, buflen uint) ================================================ FILE: src/crypto/rand/rand_baremetal.go ================================================ //go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt) // If you update the above build constraint, you'll probably also need to update // src/runtime/rand_hwrng.go. package rand import ( "machine" ) func init() { Reader = &reader{} } type reader struct { } func (r *reader) Read(b []byte) (n int, err error) { if len(b) == 0 { return } var randomByte uint32 for i := range b { if i%4 == 0 { randomByte, err = machine.GetRNG() if err != nil { return n, err } } else { randomByte >>= 8 } b[i] = byte(randomByte) } return len(b), nil } ================================================ FILE: src/crypto/rand/rand_urandom.go ================================================ //go:build linux && !baremetal && !wasip1 && !wasip2 // This implementation of crypto/rand uses the /dev/urandom pseudo-file to // generate random numbers. // TODO: convert to the getentropy or getrandom libc function on Linux once it // is more widely supported. package rand import ( "syscall" ) func init() { Reader = &reader{} } type reader struct { fd int } func (r *reader) Read(b []byte) (n int, err error) { if len(b) == 0 { return } // Open /dev/urandom first if needed. if r.fd == 0 { fd, err := syscall.Open("/dev/urandom", syscall.O_RDONLY, 0) if err != nil { return 0, err } r.fd = fd } // Read from the file. return syscall.Read(r.fd, b) } ================================================ FILE: src/crypto/rand/rand_windows.go ================================================ package rand import ( "errors" "unsafe" ) func init() { Reader = &reader{} } type reader struct { } var errRandom = errors.New("failed to obtain random data from rand_s") func (r *reader) Read(b []byte) (n int, err error) { if len(b) == 0 { return } // Use the old RtlGenRandom, introduced in Windows XP. // Even though the documentation says it is deprecated, it is widely used // and probably won't go away anytime soon. // See for example: https://github.com/golang/go/issues/33542 // For Windows 7 and newer, we might switch to ProcessPrng in the future // (which is a documented function and might be a tiny bit faster). ok := libc_RtlGenRandom(unsafe.Pointer(&b[0]), len(b)) if !ok { return 0, errRandom } return len(b), nil } // This function is part of advapi32.dll, and is called SystemFunction036 for // some reason. It's available on Windows XP and newer. // See: https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom // //export SystemFunction036 func libc_RtlGenRandom(buf unsafe.Pointer, len int) bool ================================================ FILE: src/crypto/rand/util.go ================================================ // Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package rand import ( "errors" "io" "math/big" ) // smallPrimes is a list of small, prime numbers that allows us to rapidly // exclude some fraction of composite candidates when searching for a random // prime. This list is truncated at the point where smallPrimesProduct exceeds // a uint64. It does not include two because we ensure that the candidates are // odd by construction. var smallPrimes = []uint8{ 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, } // smallPrimesProduct is the product of the values in smallPrimes and allows us // to reduce a candidate prime by this number and then determine whether it's // coprime to all the elements of smallPrimes without further big.Int // operations. var smallPrimesProduct = new(big.Int).SetUint64(16294579238595022365) // Prime returns a number, p, of the given size, such that p is prime // with high probability. // Prime will return error for any error returned by rand.Read or if bits < 2. func Prime(rand io.Reader, bits int) (p *big.Int, err error) { if bits < 2 { err = errors.New("crypto/rand: prime size must be at least 2-bit") return } b := uint(bits % 8) if b == 0 { b = 8 } bytes := make([]byte, (bits+7)/8) p = new(big.Int) bigMod := new(big.Int) for { _, err = io.ReadFull(rand, bytes) if err != nil { return nil, err } // Clear bits in the first byte to make sure the candidate has a size <= bits. bytes[0] &= uint8(int(1<= 2 { bytes[0] |= 3 << (b - 2) } else { // Here b==1, because b cannot be zero. bytes[0] |= 1 if len(bytes) > 1 { bytes[1] |= 0x80 } } // Make the value odd since an even number this large certainly isn't prime. bytes[len(bytes)-1] |= 1 p.SetBytes(bytes) // Calculate the value mod the product of smallPrimes. If it's // a multiple of any of these primes we add two until it isn't. // The probability of overflowing is minimal and can be ignored // because we still perform Miller-Rabin tests on the result. bigMod.Mod(p, smallPrimesProduct) mod := bigMod.Uint64() NextDelta: for delta := uint64(0); delta < 1<<20; delta += 2 { m := mod + delta for _, prime := range smallPrimes { if m%uint64(prime) == 0 && (bits > 6 || m != uint64(prime)) { continue NextDelta } } if delta > 0 { bigMod.SetUint64(delta) p.Add(p, bigMod) } break } // There is a tiny possibility that, by adding delta, we caused // the number to be one bit too long. Thus we check BitLen // here. if p.ProbablyPrime(20) && p.BitLen() == bits { return } } } // Int returns a uniform random value in [0, max). It panics if max <= 0. func Int(rand io.Reader, max *big.Int) (n *big.Int, err error) { if max.Sign() <= 0 { panic("crypto/rand: argument to Int is <= 0") } n = new(big.Int) n.Sub(max, n.SetUint64(1)) // bitLen is the maximum bit length needed to encode a value < max. bitLen := n.BitLen() if bitLen == 0 { // the only valid result is 0 return } // k is the maximum byte length needed to encode a value < max. k := (bitLen + 7) / 8 // b is the number of bits in the most significant byte of max-1. b := uint(bitLen % 8) if b == 0 { b = 8 } bytes := make([]byte, k) for { _, err = io.ReadFull(rand, bytes) if err != nil { return nil, err } // Clear bits in the first byte to increase the probability // that the candidate is < max. bytes[0] &= uint8(int(1<>5].Set(1 << (irq & 0x1F)) } // Enable the given interrupt number. func EnableIRQ(irq uint32) { NVIC.ISER[irq>>5].Set(1 << (irq & 0x1F)) } // Disable the given interrupt number. func DisableIRQ(irq uint32) { NVIC.ICER[irq>>5].Set(1 << (irq & 0x1F)) } // Set the priority of the given interrupt number. // Note that the priority is given as a 0-255 number, where some of the lower // bits are not implemented by the hardware. For example, to set a low interrupt // priority, use 0xc0, which is equivalent to using priority level 5 when the // hardware has 8 priority levels. Also note that the priority level is inverted // in ARM: a lower number means it is a more important interrupt and will // interrupt ISRs with a higher interrupt priority. func SetPriority(irq uint32, priority uint32) { // Details: // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/Cihgjeed.html regnum := irq / 4 regpos := irq % 4 mask := uint32(0xff) << (regpos * 8) // bits to clear priority = priority << (regpos * 8) // bits to set NVIC.IPR[regnum].Set((uint32(NVIC.IPR[regnum].Get()) &^ mask) | priority) } // DisableInterrupts disables all interrupts, and returns the old interrupt // state. // //export DisableInterrupts func DisableInterrupts() uintptr // EnableInterrupts enables all interrupts again. The value passed in must be // the mask returned by DisableInterrupts. // //export EnableInterrupts func EnableInterrupts(mask uintptr) // Set up the system timer to generate periodic tick events. // This will cause SysTick_Handler to fire once per tick. // The cyclecount parameter is a counter value which can range from 0 to // 0xffffff. A value of 0 disables the timer. func SetupSystemTimer(cyclecount uint32) error { // turn it off SYST.SYST_CSR.ClearBits(SYST_CSR_TICKINT | SYST_CSR_ENABLE) if cyclecount == 0 { // leave the system timer turned off. return nil } if cyclecount&SYST_RVR_RELOAD_Msk != cyclecount { // The cycle refresh register is only 24 bits wide. The user-specified value will overflow. return errCycleCountTooLarge } // set refresh count SYST.SYST_RVR.Set(cyclecount) // set current counter value SYST.SYST_CVR.Set(cyclecount) // enable clock, enable SysTick interrupt when clock reaches 0, run it off of the processor clock SYST.SYST_CSR.SetBits(SYST_CSR_TICKINT | SYST_CSR_ENABLE | SYST_CSR_CLKSOURCE) return nil } ================================================ FILE: src/device/arm/cortexm.S ================================================ .syntax unified .cfi_sections .debug_frame // This is a convenience function for semihosting support. // At some point, this should be replaced by inline assembly. .section .text.SemihostingCall .global SemihostingCall .type SemihostingCall, %function SemihostingCall: .cfi_startproc bkpt 0xab bx lr .cfi_endproc .size SemihostingCall, .-SemihostingCall ================================================ FILE: src/device/arm/interrupts.c ================================================ #include void EnableInterrupts(uintptr_t mask) { asm volatile( "msr PRIMASK, %0" : : "r"(mask) : "memory" ); } uintptr_t DisableInterrupts() { uintptr_t mask; asm volatile( "mrs %0, PRIMASK\n\t" "cpsid i" : "=r"(mask) : : "memory" ); return mask; } ================================================ FILE: src/device/arm/scb.go ================================================ // Hand created file. DO NOT DELETE. // Cortex-M System Control Block-related definitions. //go:build cortexm package arm import ( "runtime/volatile" "unsafe" ) const SCB_BASE = SCS_BASE + 0x0D00 // System Control Block (SCB) // // SCB_Type provides the definitions for the System Control Block Registers. type SCB_Type struct { CPUID volatile.Register32 // 0xD00: CPUID Base Register ICSR volatile.Register32 // 0xD04: Interrupt Control and State Register VTOR volatile.Register32 // 0xD08: Vector Table Offset Register AIRCR volatile.Register32 // 0xD0C: Application Interrupt and Reset Control Register SCR volatile.Register32 // 0xD10: System Control Register CCR volatile.Register32 // 0xD14: Configuration and Control Register SHPR1 volatile.Register32 // 0xD18: System Handler Priority Register 1 (Cortex-M3/M33/M4/M7 only) SHPR2 volatile.Register32 // 0xD1C: System Handler Priority Register 2 SHPR3 volatile.Register32 // 0xD20: System Handler Priority Register 3 // the following are only applicable for Cortex-M3/M33/M4/M7 SHCSR volatile.Register32 // 0xD24: System Handler Control and State Register CFSR volatile.Register32 // 0xD28: Configurable Fault Status Register HFSR volatile.Register32 // 0xD2C: HardFault Status Register DFSR volatile.Register32 // 0xD30: Debug Fault Status Register MMFAR volatile.Register32 // 0xD34: MemManage Fault Address Register BFAR volatile.Register32 // 0xD38: BusFault Address Register AFSR volatile.Register32 // 0xD3C: Auxiliary Fault Status Register PFR [2]volatile.Register32 // 0xD40: Processor Feature Register DFR volatile.Register32 // 0xD48: Debug Feature Register ADR volatile.Register32 // 0xD4C: Auxiliary Feature Register MMFR [4]volatile.Register32 // 0xD50: Memory Model Feature Register ISAR [5]volatile.Register32 // 0xD60: Instruction Set Attributes Register _ [5]uint32 // reserved CPACR volatile.Register32 // 0xD88: Coprocessor Access Control Register } var SCB = (*SCB_Type)(unsafe.Pointer(uintptr(SCB_BASE))) // SystemReset performs a hard system reset. func SystemReset() { SCB.AIRCR.Set((0x5FA << SCB_AIRCR_VECTKEY_Pos) | SCB_AIRCR_SYSRESETREQ_Msk) for { Asm("wfi") } } const ( // CPUID: CPUID Base Register SCB_CPUID_REVISION_Pos = 0x0 // Position of REVISION field. SCB_CPUID_REVISION_Msk = 0xf // Bit mask of REVISION field. SCB_CPUID_PARTNO_Pos = 0x4 // Position of PARTNO field. SCB_CPUID_PARTNO_Msk = 0xfff0 // Bit mask of PARTNO field. SCB_CPUID_ARCHITECTURE_Pos = 0x10 // Position of ARCHITECTURE field. SCB_CPUID_ARCHITECTURE_Msk = 0xf0000 // Bit mask of ARCHITECTURE field. SCB_CPUID_VARIANT_Pos = 0x14 // Position of VARIANT field. SCB_CPUID_VARIANT_Msk = 0xf00000 // Bit mask of VARIANT field. SCB_CPUID_IMPLEMENTER_Pos = 0x18 // Position of IMPLEMENTER field. SCB_CPUID_IMPLEMENTER_Msk = 0xff000000 // Bit mask of IMPLEMENTER field. // ICSR: Interrupt Control and State Register SCB_ICSR_VECTACTIVE_Pos = 0x0 // Position of VECTACTIVE field. SCB_ICSR_VECTACTIVE_Msk = 0x1ff // Bit mask of VECTACTIVE field. SCB_ICSR_RETTOBASE_Pos = 0xb // Position of RETTOBASE field. SCB_ICSR_RETTOBASE_Msk = 0x800 // Bit mask of RETTOBASE field. SCB_ICSR_RETTOBASE = 0x800 // Bit RETTOBASE. SCB_ICSR_RETTOBASE_RETTOBASE_0 = 0x0 // there are preempted active exceptions to execute SCB_ICSR_RETTOBASE_RETTOBASE_1 = 0x1 // there are no active exceptions, or the currently-executing exception is the only active exception SCB_ICSR_VECTPENDING_Pos = 0xc // Position of VECTPENDING field. SCB_ICSR_VECTPENDING_Msk = 0x1ff000 // Bit mask of VECTPENDING field. SCB_ICSR_ISRPENDING_Pos = 0x16 // Position of ISRPENDING field. SCB_ICSR_ISRPENDING_Msk = 0x400000 // Bit mask of ISRPENDING field. SCB_ICSR_ISRPENDING = 0x400000 // Bit ISRPENDING. SCB_ICSR_ISRPENDING_ISRPENDING_0 = 0x0 // No external interrupt pending. SCB_ICSR_ISRPENDING_ISRPENDING_1 = 0x1 // External interrupt pending. SCB_ICSR_PENDSTCLR_Pos = 0x19 // Position of PENDSTCLR field. SCB_ICSR_PENDSTCLR_Msk = 0x2000000 // Bit mask of PENDSTCLR field. SCB_ICSR_PENDSTCLR = 0x2000000 // Bit PENDSTCLR. SCB_ICSR_PENDSTCLR_PENDSTCLR_0 = 0x0 // no effect SCB_ICSR_PENDSTCLR_PENDSTCLR_1 = 0x1 // removes the pending state from the SysTick exception SCB_ICSR_PENDSTSET_Pos = 0x1a // Position of PENDSTSET field. SCB_ICSR_PENDSTSET_Msk = 0x4000000 // Bit mask of PENDSTSET field. SCB_ICSR_PENDSTSET = 0x4000000 // Bit PENDSTSET. SCB_ICSR_PENDSTSET_PENDSTSET_0 = 0x0 // write: no effect; read: SysTick exception is not pending SCB_ICSR_PENDSTSET_PENDSTSET_1 = 0x1 // write: changes SysTick exception state to pending; read: SysTick exception is pending SCB_ICSR_PENDSVCLR_Pos = 0x1b // Position of PENDSVCLR field. SCB_ICSR_PENDSVCLR_Msk = 0x8000000 // Bit mask of PENDSVCLR field. SCB_ICSR_PENDSVCLR = 0x8000000 // Bit PENDSVCLR. SCB_ICSR_PENDSVCLR_PENDSVCLR_0 = 0x0 // no effect SCB_ICSR_PENDSVCLR_PENDSVCLR_1 = 0x1 // removes the pending state from the PendSV exception SCB_ICSR_PENDSVSET_Pos = 0x1c // Position of PENDSVSET field. SCB_ICSR_PENDSVSET_Msk = 0x10000000 // Bit mask of PENDSVSET field. SCB_ICSR_PENDSVSET = 0x10000000 // Bit PENDSVSET. SCB_ICSR_PENDSVSET_PENDSVSET_0 = 0x0 // write: no effect; read: PendSV exception is not pending SCB_ICSR_PENDSVSET_PENDSVSET_1 = 0x1 // write: changes PendSV exception state to pending; read: PendSV exception is pending SCB_ICSR_NMIPENDSET_Pos = 0x1f // Position of NMIPENDSET field. SCB_ICSR_NMIPENDSET_Msk = 0x80000000 // Bit mask of NMIPENDSET field. SCB_ICSR_NMIPENDSET = 0x80000000 // Bit NMIPENDSET. SCB_ICSR_NMIPENDSET_NMIPENDSET_0 = 0x0 // write: no effect; read: NMI exception is not pending SCB_ICSR_NMIPENDSET_NMIPENDSET_1 = 0x1 // write: changes NMI exception state to pending; read: NMI exception is pending // VTOR: Vector Table Offset Register SCB_VTOR_TBLOFF_Pos = 0x7 // Position of TBLOFF field. SCB_VTOR_TBLOFF_Msk = 0xffffff80 // Bit mask of TBLOFF field. // AIRCR: Application Interrupt and Reset Control Register SCB_AIRCR_VECTRESET_Pos = 0x0 // Position of VECTRESET field. SCB_AIRCR_VECTRESET_Msk = 0x1 // Bit mask of VECTRESET field. SCB_AIRCR_VECTRESET = 0x1 // Bit VECTRESET. SCB_AIRCR_VECTRESET_VECTRESET_0 = 0x0 // No change SCB_AIRCR_VECTRESET_VECTRESET_1 = 0x1 // Causes a local system reset SCB_AIRCR_VECTCLRACTIVE_Pos = 0x1 // Position of VECTCLRACTIVE field. SCB_AIRCR_VECTCLRACTIVE_Msk = 0x2 // Bit mask of VECTCLRACTIVE field. SCB_AIRCR_VECTCLRACTIVE = 0x2 // Bit VECTCLRACTIVE. SCB_AIRCR_VECTCLRACTIVE_VECTCLRACTIVE_0 = 0x0 // No change SCB_AIRCR_VECTCLRACTIVE_VECTCLRACTIVE_1 = 0x1 // Clears all active state information for fixed and configurable exceptions SCB_AIRCR_SYSRESETREQ_Pos = 0x2 // Position of SYSRESETREQ field. SCB_AIRCR_SYSRESETREQ_Msk = 0x4 // Bit mask of SYSRESETREQ field. SCB_AIRCR_SYSRESETREQ = 0x4 // Bit SYSRESETREQ. SCB_AIRCR_SYSRESETREQ_SYSRESETREQ_0 = 0x0 // no system reset request SCB_AIRCR_SYSRESETREQ_SYSRESETREQ_1 = 0x1 // asserts a signal to the outer system that requests a reset SCB_AIRCR_PRIGROUP_Pos = 0x8 // Position of PRIGROUP field. SCB_AIRCR_PRIGROUP_Msk = 0x700 // Bit mask of PRIGROUP field. SCB_AIRCR_ENDIANNESS_Pos = 0xf // Position of ENDIANNESS field. SCB_AIRCR_ENDIANNESS_Msk = 0x8000 // Bit mask of ENDIANNESS field. SCB_AIRCR_ENDIANNESS = 0x8000 // Bit ENDIANNESS. SCB_AIRCR_ENDIANNESS_ENDIANNESS_0 = 0x0 // Little-endian SCB_AIRCR_ENDIANNESS_ENDIANNESS_1 = 0x1 // Big-endian SCB_AIRCR_VECTKEY_Pos = 0x10 // Position of VECTKEY field. SCB_AIRCR_VECTKEY_Msk = 0xffff0000 // Bit mask of VECTKEY field. // SCR: System Control Register SCB_SCR_SLEEPONEXIT_Pos = 0x1 // Position of SLEEPONEXIT field. SCB_SCR_SLEEPONEXIT_Msk = 0x2 // Bit mask of SLEEPONEXIT field. SCB_SCR_SLEEPONEXIT = 0x2 // Bit SLEEPONEXIT. SCB_SCR_SLEEPONEXIT_SLEEPONEXIT_0 = 0x0 // o not sleep when returning to Thread mode SCB_SCR_SLEEPONEXIT_SLEEPONEXIT_1 = 0x1 // enter sleep, or deep sleep, on return from an ISR SCB_SCR_SLEEPDEEP_Pos = 0x2 // Position of SLEEPDEEP field. SCB_SCR_SLEEPDEEP_Msk = 0x4 // Bit mask of SLEEPDEEP field. SCB_SCR_SLEEPDEEP = 0x4 // Bit SLEEPDEEP. SCB_SCR_SLEEPDEEP_SLEEPDEEP_0 = 0x0 // sleep SCB_SCR_SLEEPDEEP_SLEEPDEEP_1 = 0x1 // deep sleep SCB_SCR_SEVONPEND_Pos = 0x4 // Position of SEVONPEND field. SCB_SCR_SEVONPEND_Msk = 0x10 // Bit mask of SEVONPEND field. SCB_SCR_SEVONPEND = 0x10 // Bit SEVONPEND. SCB_SCR_SEVONPEND_SEVONPEND_0 = 0x0 // only enabled interrupts or events can wakeup the processor, disabled interrupts are excluded SCB_SCR_SEVONPEND_SEVONPEND_1 = 0x1 // enabled events and all interrupts, including disabled interrupts, can wakeup the processor // CCR: Configuration and Control Register SCB_CCR_NONBASETHRDENA_Pos = 0x0 // Position of NONBASETHRDENA field. SCB_CCR_NONBASETHRDENA_Msk = 0x1 // Bit mask of NONBASETHRDENA field. SCB_CCR_NONBASETHRDENA = 0x1 // Bit NONBASETHRDENA. SCB_CCR_NONBASETHRDENA_NONBASETHRDENA_0 = 0x0 // processor can enter Thread mode only when no exception is active SCB_CCR_NONBASETHRDENA_NONBASETHRDENA_1 = 0x1 // processor can enter Thread mode from any level under the control of an EXC_RETURN value SCB_CCR_USERSETMPEND_Pos = 0x1 // Position of USERSETMPEND field. SCB_CCR_USERSETMPEND_Msk = 0x2 // Bit mask of USERSETMPEND field. SCB_CCR_USERSETMPEND = 0x2 // Bit USERSETMPEND. SCB_CCR_USERSETMPEND_USERSETMPEND_0 = 0x0 // disable SCB_CCR_USERSETMPEND_USERSETMPEND_1 = 0x1 // enable SCB_CCR_UNALIGN_TRP_Pos = 0x3 // Position of UNALIGN_TRP field. SCB_CCR_UNALIGN_TRP_Msk = 0x8 // Bit mask of UNALIGN_TRP field. SCB_CCR_UNALIGN_TRP = 0x8 // Bit UNALIGN_TRP. SCB_CCR_UNALIGN_TRP_UNALIGN_TRP_0 = 0x0 // do not trap unaligned halfword and word accesses SCB_CCR_UNALIGN_TRP_UNALIGN_TRP_1 = 0x1 // trap unaligned halfword and word accesses SCB_CCR_DIV_0_TRP_Pos = 0x4 // Position of DIV_0_TRP field. SCB_CCR_DIV_0_TRP_Msk = 0x10 // Bit mask of DIV_0_TRP field. SCB_CCR_DIV_0_TRP = 0x10 // Bit DIV_0_TRP. SCB_CCR_DIV_0_TRP_DIV_0_TRP_0 = 0x0 // do not trap divide by 0 SCB_CCR_DIV_0_TRP_DIV_0_TRP_1 = 0x1 // trap divide by 0 SCB_CCR_BFHFNMIGN_Pos = 0x8 // Position of BFHFNMIGN field. SCB_CCR_BFHFNMIGN_Msk = 0x100 // Bit mask of BFHFNMIGN field. SCB_CCR_BFHFNMIGN = 0x100 // Bit BFHFNMIGN. SCB_CCR_BFHFNMIGN_BFHFNMIGN_0 = 0x0 // data bus faults caused by load and store instructions cause a lock-up SCB_CCR_BFHFNMIGN_BFHFNMIGN_1 = 0x1 // handlers running at priority -1 and -2 ignore data bus faults caused by load and store instructions SCB_CCR_STKALIGN_Pos = 0x9 // Position of STKALIGN field. SCB_CCR_STKALIGN_Msk = 0x200 // Bit mask of STKALIGN field. SCB_CCR_STKALIGN = 0x200 // Bit STKALIGN. SCB_CCR_STKALIGN_STKALIGN_0 = 0x0 // 4-byte aligned SCB_CCR_STKALIGN_STKALIGN_1 = 0x1 // 8-byte aligned SCB_CCR_DC_Pos = 0x10 // Position of DC field. SCB_CCR_DC_Msk = 0x10000 // Bit mask of DC field. SCB_CCR_DC = 0x10000 // Bit DC. SCB_CCR_DC_DC_0 = 0x0 // L1 data cache disabled SCB_CCR_DC_DC_1 = 0x1 // L1 data cache enabled SCB_CCR_IC_Pos = 0x11 // Position of IC field. SCB_CCR_IC_Msk = 0x20000 // Bit mask of IC field. SCB_CCR_IC = 0x20000 // Bit IC. SCB_CCR_IC_IC_0 = 0x0 // L1 instruction cache disabled SCB_CCR_IC_IC_1 = 0x1 // L1 instruction cache enabled SCB_CCR_BP_Pos = 0x12 // Position of BP field. SCB_CCR_BP_Msk = 0x40000 // Bit mask of BP field. SCB_CCR_BP = 0x40000 // Bit BP. // SHPR1: System Handler Priority Register 1 SCB_SHPR1_PRI_4_Pos = 0x0 // Position of PRI_4 field. SCB_SHPR1_PRI_4_Msk = 0xff // Bit mask of PRI_4 field. SCB_SHPR1_PRI_5_Pos = 0x8 // Position of PRI_5 field. SCB_SHPR1_PRI_5_Msk = 0xff00 // Bit mask of PRI_5 field. SCB_SHPR1_PRI_6_Pos = 0x10 // Position of PRI_6 field. SCB_SHPR1_PRI_6_Msk = 0xff0000 // Bit mask of PRI_6 field. // SHPR2: System Handler Priority Register 2 SCB_SHPR2_PRI_11_Pos = 0x18 // Position of PRI_11 field. SCB_SHPR2_PRI_11_Msk = 0xff000000 // Bit mask of PRI_11 field. // SHPR3: System Handler Priority Register 3 SCB_SHPR3_PRI_14_Pos = 0x10 // Position of PRI_14 field. SCB_SHPR3_PRI_14_Msk = 0xff0000 // Bit mask of PRI_14 field. SCB_SHPR3_PRI_15_Pos = 0x18 // Position of PRI_15 field. SCB_SHPR3_PRI_15_Msk = 0xff000000 // Bit mask of PRI_15 field. // SHCSR: System Handler Control and State Register SCB_SHCSR_MEMFAULTACT_Pos = 0x0 // Position of MEMFAULTACT field. SCB_SHCSR_MEMFAULTACT_Msk = 0x1 // Bit mask of MEMFAULTACT field. SCB_SHCSR_MEMFAULTACT = 0x1 // Bit MEMFAULTACT. SCB_SHCSR_MEMFAULTACT_MEMFAULTACT_0 = 0x0 // exception is not active SCB_SHCSR_MEMFAULTACT_MEMFAULTACT_1 = 0x1 // exception is active SCB_SHCSR_BUSFAULTACT_Pos = 0x1 // Position of BUSFAULTACT field. SCB_SHCSR_BUSFAULTACT_Msk = 0x2 // Bit mask of BUSFAULTACT field. SCB_SHCSR_BUSFAULTACT = 0x2 // Bit BUSFAULTACT. SCB_SHCSR_BUSFAULTACT_BUSFAULTACT_0 = 0x0 // exception is not active SCB_SHCSR_BUSFAULTACT_BUSFAULTACT_1 = 0x1 // exception is active SCB_SHCSR_USGFAULTACT_Pos = 0x3 // Position of USGFAULTACT field. SCB_SHCSR_USGFAULTACT_Msk = 0x8 // Bit mask of USGFAULTACT field. SCB_SHCSR_USGFAULTACT = 0x8 // Bit USGFAULTACT. SCB_SHCSR_USGFAULTACT_USGFAULTACT_0 = 0x0 // exception is not active SCB_SHCSR_USGFAULTACT_USGFAULTACT_1 = 0x1 // exception is active SCB_SHCSR_SVCALLACT_Pos = 0x7 // Position of SVCALLACT field. SCB_SHCSR_SVCALLACT_Msk = 0x80 // Bit mask of SVCALLACT field. SCB_SHCSR_SVCALLACT = 0x80 // Bit SVCALLACT. SCB_SHCSR_SVCALLACT_SVCALLACT_0 = 0x0 // exception is not active SCB_SHCSR_SVCALLACT_SVCALLACT_1 = 0x1 // exception is active SCB_SHCSR_MONITORACT_Pos = 0x8 // Position of MONITORACT field. SCB_SHCSR_MONITORACT_Msk = 0x100 // Bit mask of MONITORACT field. SCB_SHCSR_MONITORACT = 0x100 // Bit MONITORACT. SCB_SHCSR_MONITORACT_MONITORACT_0 = 0x0 // exception is not active SCB_SHCSR_MONITORACT_MONITORACT_1 = 0x1 // exception is active SCB_SHCSR_PENDSVACT_Pos = 0xa // Position of PENDSVACT field. SCB_SHCSR_PENDSVACT_Msk = 0x400 // Bit mask of PENDSVACT field. SCB_SHCSR_PENDSVACT = 0x400 // Bit PENDSVACT. SCB_SHCSR_PENDSVACT_PENDSVACT_0 = 0x0 // exception is not active SCB_SHCSR_PENDSVACT_PENDSVACT_1 = 0x1 // exception is active SCB_SHCSR_SYSTICKACT_Pos = 0xb // Position of SYSTICKACT field. SCB_SHCSR_SYSTICKACT_Msk = 0x800 // Bit mask of SYSTICKACT field. SCB_SHCSR_SYSTICKACT = 0x800 // Bit SYSTICKACT. SCB_SHCSR_SYSTICKACT_SYSTICKACT_0 = 0x0 // exception is not active SCB_SHCSR_SYSTICKACT_SYSTICKACT_1 = 0x1 // exception is active SCB_SHCSR_USGFAULTPENDED_Pos = 0xc // Position of USGFAULTPENDED field. SCB_SHCSR_USGFAULTPENDED_Msk = 0x1000 // Bit mask of USGFAULTPENDED field. SCB_SHCSR_USGFAULTPENDED = 0x1000 // Bit USGFAULTPENDED. SCB_SHCSR_USGFAULTPENDED_USGFAULTPENDED_0 = 0x0 // exception is not pending SCB_SHCSR_USGFAULTPENDED_USGFAULTPENDED_1 = 0x1 // exception is pending SCB_SHCSR_MEMFAULTPENDED_Pos = 0xd // Position of MEMFAULTPENDED field. SCB_SHCSR_MEMFAULTPENDED_Msk = 0x2000 // Bit mask of MEMFAULTPENDED field. SCB_SHCSR_MEMFAULTPENDED = 0x2000 // Bit MEMFAULTPENDED. SCB_SHCSR_MEMFAULTPENDED_MEMFAULTPENDED_0 = 0x0 // exception is not pending SCB_SHCSR_MEMFAULTPENDED_MEMFAULTPENDED_1 = 0x1 // exception is pending SCB_SHCSR_BUSFAULTPENDED_Pos = 0xe // Position of BUSFAULTPENDED field. SCB_SHCSR_BUSFAULTPENDED_Msk = 0x4000 // Bit mask of BUSFAULTPENDED field. SCB_SHCSR_BUSFAULTPENDED = 0x4000 // Bit BUSFAULTPENDED. SCB_SHCSR_BUSFAULTPENDED_BUSFAULTPENDED_0 = 0x0 // exception is not pending SCB_SHCSR_BUSFAULTPENDED_BUSFAULTPENDED_1 = 0x1 // exception is pending SCB_SHCSR_SVCALLPENDED_Pos = 0xf // Position of SVCALLPENDED field. SCB_SHCSR_SVCALLPENDED_Msk = 0x8000 // Bit mask of SVCALLPENDED field. SCB_SHCSR_SVCALLPENDED = 0x8000 // Bit SVCALLPENDED. SCB_SHCSR_SVCALLPENDED_SVCALLPENDED_0 = 0x0 // exception is not pending SCB_SHCSR_SVCALLPENDED_SVCALLPENDED_1 = 0x1 // exception is pending SCB_SHCSR_MEMFAULTENA_Pos = 0x10 // Position of MEMFAULTENA field. SCB_SHCSR_MEMFAULTENA_Msk = 0x10000 // Bit mask of MEMFAULTENA field. SCB_SHCSR_MEMFAULTENA = 0x10000 // Bit MEMFAULTENA. SCB_SHCSR_MEMFAULTENA_MEMFAULTENA_0 = 0x0 // disable the exception SCB_SHCSR_MEMFAULTENA_MEMFAULTENA_1 = 0x1 // enable the exception SCB_SHCSR_BUSFAULTENA_Pos = 0x11 // Position of BUSFAULTENA field. SCB_SHCSR_BUSFAULTENA_Msk = 0x20000 // Bit mask of BUSFAULTENA field. SCB_SHCSR_BUSFAULTENA = 0x20000 // Bit BUSFAULTENA. SCB_SHCSR_BUSFAULTENA_BUSFAULTENA_0 = 0x0 // disable the exception SCB_SHCSR_BUSFAULTENA_BUSFAULTENA_1 = 0x1 // enable the exception SCB_SHCSR_USGFAULTENA_Pos = 0x12 // Position of USGFAULTENA field. SCB_SHCSR_USGFAULTENA_Msk = 0x40000 // Bit mask of USGFAULTENA field. SCB_SHCSR_USGFAULTENA = 0x40000 // Bit USGFAULTENA. SCB_SHCSR_USGFAULTENA_USGFAULTENA_0 = 0x0 // disable the exception SCB_SHCSR_USGFAULTENA_USGFAULTENA_1 = 0x1 // enable the exception // CFSR: Configurable Fault Status Register SCB_CFSR_IACCVIOL_Pos = 0x0 // Position of IACCVIOL field. SCB_CFSR_IACCVIOL_Msk = 0x1 // Bit mask of IACCVIOL field. SCB_CFSR_IACCVIOL = 0x1 // Bit IACCVIOL. SCB_CFSR_IACCVIOL_IACCVIOL_0 = 0x0 // no instruction access violation fault SCB_CFSR_IACCVIOL_IACCVIOL_1 = 0x1 // the processor attempted an instruction fetch from a location that does not permit execution SCB_CFSR_DACCVIOL_Pos = 0x1 // Position of DACCVIOL field. SCB_CFSR_DACCVIOL_Msk = 0x2 // Bit mask of DACCVIOL field. SCB_CFSR_DACCVIOL = 0x2 // Bit DACCVIOL. SCB_CFSR_DACCVIOL_DACCVIOL_0 = 0x0 // no data access violation fault SCB_CFSR_DACCVIOL_DACCVIOL_1 = 0x1 // the processor attempted a load or store at a location that does not permit the operation SCB_CFSR_MUNSTKERR_Pos = 0x3 // Position of MUNSTKERR field. SCB_CFSR_MUNSTKERR_Msk = 0x8 // Bit mask of MUNSTKERR field. SCB_CFSR_MUNSTKERR = 0x8 // Bit MUNSTKERR. SCB_CFSR_MUNSTKERR_MUNSTKERR_0 = 0x0 // no unstacking fault SCB_CFSR_MUNSTKERR_MUNSTKERR_1 = 0x1 // unstack for an exception return has caused one or more access violations SCB_CFSR_MSTKERR_Pos = 0x4 // Position of MSTKERR field. SCB_CFSR_MSTKERR_Msk = 0x10 // Bit mask of MSTKERR field. SCB_CFSR_MSTKERR = 0x10 // Bit MSTKERR. SCB_CFSR_MSTKERR_MSTKERR_0 = 0x0 // no stacking fault SCB_CFSR_MSTKERR_MSTKERR_1 = 0x1 // stacking for an exception entry has caused one or more access violations SCB_CFSR_MLSPERR_Pos = 0x5 // Position of MLSPERR field. SCB_CFSR_MLSPERR_Msk = 0x20 // Bit mask of MLSPERR field. SCB_CFSR_MLSPERR = 0x20 // Bit MLSPERR. SCB_CFSR_MLSPERR_MLSPERR_0 = 0x0 // No MemManage fault occurred during floating-point lazy state preservation SCB_CFSR_MLSPERR_MLSPERR_1 = 0x1 // A MemManage fault occurred during floating-point lazy state preservation SCB_CFSR_MMARVALID_Pos = 0x7 // Position of MMARVALID field. SCB_CFSR_MMARVALID_Msk = 0x80 // Bit mask of MMARVALID field. SCB_CFSR_MMARVALID = 0x80 // Bit MMARVALID. SCB_CFSR_MMARVALID_MMARVALID_0 = 0x0 // value in MMAR is not a valid fault address SCB_CFSR_MMARVALID_MMARVALID_1 = 0x1 // MMAR holds a valid fault address SCB_CFSR_IBUSERR_Pos = 0x8 // Position of IBUSERR field. SCB_CFSR_IBUSERR_Msk = 0x100 // Bit mask of IBUSERR field. SCB_CFSR_IBUSERR = 0x100 // Bit IBUSERR. SCB_CFSR_IBUSERR_IBUSERR_0 = 0x0 // no instruction bus error SCB_CFSR_IBUSERR_IBUSERR_1 = 0x1 // instruction bus error SCB_CFSR_PRECISERR_Pos = 0x9 // Position of PRECISERR field. SCB_CFSR_PRECISERR_Msk = 0x200 // Bit mask of PRECISERR field. SCB_CFSR_PRECISERR = 0x200 // Bit PRECISERR. SCB_CFSR_PRECISERR_PRECISERR_0 = 0x0 // no precise data bus error SCB_CFSR_PRECISERR_PRECISERR_1 = 0x1 // a data bus error has occurred, and the PC value stacked for the exception return points to the instruction that caused the fault SCB_CFSR_IMPRECISERR_Pos = 0xa // Position of IMPRECISERR field. SCB_CFSR_IMPRECISERR_Msk = 0x400 // Bit mask of IMPRECISERR field. SCB_CFSR_IMPRECISERR = 0x400 // Bit IMPRECISERR. SCB_CFSR_IMPRECISERR_IMPRECISERR_0 = 0x0 // no imprecise data bus error SCB_CFSR_IMPRECISERR_IMPRECISERR_1 = 0x1 // a data bus error has occurred, but the return address in the stack frame is not related to the instruction that caused the error SCB_CFSR_UNSTKERR_Pos = 0xb // Position of UNSTKERR field. SCB_CFSR_UNSTKERR_Msk = 0x800 // Bit mask of UNSTKERR field. SCB_CFSR_UNSTKERR = 0x800 // Bit UNSTKERR. SCB_CFSR_UNSTKERR_UNSTKERR_0 = 0x0 // no unstacking fault SCB_CFSR_UNSTKERR_UNSTKERR_1 = 0x1 // unstack for an exception return has caused one or more BusFaults SCB_CFSR_STKERR_Pos = 0xc // Position of STKERR field. SCB_CFSR_STKERR_Msk = 0x1000 // Bit mask of STKERR field. SCB_CFSR_STKERR = 0x1000 // Bit STKERR. SCB_CFSR_STKERR_STKERR_0 = 0x0 // no stacking fault SCB_CFSR_STKERR_STKERR_1 = 0x1 // stacking for an exception entry has caused one or more BusFaults SCB_CFSR_LSPERR_Pos = 0xd // Position of LSPERR field. SCB_CFSR_LSPERR_Msk = 0x2000 // Bit mask of LSPERR field. SCB_CFSR_LSPERR = 0x2000 // Bit LSPERR. SCB_CFSR_LSPERR_LSPERR_0 = 0x0 // No bus fault occurred during floating-point lazy state preservation SCB_CFSR_LSPERR_LSPERR_1 = 0x1 // A bus fault occurred during floating-point lazy state preservation SCB_CFSR_BFARVALID_Pos = 0xf // Position of BFARVALID field. SCB_CFSR_BFARVALID_Msk = 0x8000 // Bit mask of BFARVALID field. SCB_CFSR_BFARVALID = 0x8000 // Bit BFARVALID. SCB_CFSR_BFARVALID_BFARVALID_0 = 0x0 // value in BFAR is not a valid fault address SCB_CFSR_BFARVALID_BFARVALID_1 = 0x1 // BFAR holds a valid fault address SCB_CFSR_UNDEFINSTR_Pos = 0x10 // Position of UNDEFINSTR field. SCB_CFSR_UNDEFINSTR_Msk = 0x10000 // Bit mask of UNDEFINSTR field. SCB_CFSR_UNDEFINSTR = 0x10000 // Bit UNDEFINSTR. SCB_CFSR_UNDEFINSTR_UNDEFINSTR_0 = 0x0 // no undefined instruction UsageFault SCB_CFSR_UNDEFINSTR_UNDEFINSTR_1 = 0x1 // the processor has attempted to execute an undefined instruction SCB_CFSR_INVSTATE_Pos = 0x11 // Position of INVSTATE field. SCB_CFSR_INVSTATE_Msk = 0x20000 // Bit mask of INVSTATE field. SCB_CFSR_INVSTATE = 0x20000 // Bit INVSTATE. SCB_CFSR_INVSTATE_INVSTATE_0 = 0x0 // no invalid state UsageFault SCB_CFSR_INVSTATE_INVSTATE_1 = 0x1 // the processor has attempted to execute an instruction that makes illegal use of the EPSR SCB_CFSR_INVPC_Pos = 0x12 // Position of INVPC field. SCB_CFSR_INVPC_Msk = 0x40000 // Bit mask of INVPC field. SCB_CFSR_INVPC = 0x40000 // Bit INVPC. SCB_CFSR_INVPC_INVPC_0 = 0x0 // no invalid PC load UsageFault SCB_CFSR_INVPC_INVPC_1 = 0x1 // the processor has attempted an illegal load of EXC_RETURN to the PC SCB_CFSR_NOCP_Pos = 0x13 // Position of NOCP field. SCB_CFSR_NOCP_Msk = 0x80000 // Bit mask of NOCP field. SCB_CFSR_NOCP = 0x80000 // Bit NOCP. SCB_CFSR_NOCP_NOCP_0 = 0x0 // no UsageFault caused by attempting to access a coprocessor SCB_CFSR_NOCP_NOCP_1 = 0x1 // the processor has attempted to access a coprocessor SCB_CFSR_UNALIGNED_Pos = 0x18 // Position of UNALIGNED field. SCB_CFSR_UNALIGNED_Msk = 0x1000000 // Bit mask of UNALIGNED field. SCB_CFSR_UNALIGNED = 0x1000000 // Bit UNALIGNED. SCB_CFSR_UNALIGNED_UNALIGNED_0 = 0x0 // no unaligned access fault, or unaligned access trapping not enabled SCB_CFSR_UNALIGNED_UNALIGNED_1 = 0x1 // the processor has made an unaligned memory access SCB_CFSR_DIVBYZERO_Pos = 0x19 // Position of DIVBYZERO field. SCB_CFSR_DIVBYZERO_Msk = 0x2000000 // Bit mask of DIVBYZERO field. SCB_CFSR_DIVBYZERO = 0x2000000 // Bit DIVBYZERO. SCB_CFSR_DIVBYZERO_DIVBYZERO_0 = 0x0 // no divide by zero fault, or divide by zero trapping not enabled SCB_CFSR_DIVBYZERO_DIVBYZERO_1 = 0x1 // the processor has executed an SDIV or UDIV instruction with a divisor of 0 // HFSR: HardFault Status register SCB_HFSR_VECTTBL_Pos = 0x1 // Position of VECTTBL field. SCB_HFSR_VECTTBL_Msk = 0x2 // Bit mask of VECTTBL field. SCB_HFSR_VECTTBL = 0x2 // Bit VECTTBL. SCB_HFSR_VECTTBL_VECTTBL_0 = 0x0 // no BusFault on vector table read SCB_HFSR_VECTTBL_VECTTBL_1 = 0x1 // BusFault on vector table read SCB_HFSR_FORCED_Pos = 0x1e // Position of FORCED field. SCB_HFSR_FORCED_Msk = 0x40000000 // Bit mask of FORCED field. SCB_HFSR_FORCED = 0x40000000 // Bit FORCED. SCB_HFSR_FORCED_FORCED_0 = 0x0 // no forced HardFault SCB_HFSR_FORCED_FORCED_1 = 0x1 // forced HardFault SCB_HFSR_DEBUGEVT_Pos = 0x1f // Position of DEBUGEVT field. SCB_HFSR_DEBUGEVT_Msk = 0x80000000 // Bit mask of DEBUGEVT field. SCB_HFSR_DEBUGEVT = 0x80000000 // Bit DEBUGEVT. SCB_HFSR_DEBUGEVT_DEBUGEVT_0 = 0x0 // No Debug event has occurred. SCB_HFSR_DEBUGEVT_DEBUGEVT_1 = 0x1 // Debug event has occurred. The Debug Fault Status Register has been updated. // DFSR: Debug Fault Status Register SCB_DFSR_HALTED_Pos = 0x0 // Position of HALTED field. SCB_DFSR_HALTED_Msk = 0x1 // Bit mask of HALTED field. SCB_DFSR_HALTED = 0x1 // Bit HALTED. SCB_DFSR_HALTED_HALTED_0 = 0x0 // No active halt request debug event SCB_DFSR_HALTED_HALTED_1 = 0x1 // Halt request debug event active SCB_DFSR_BKPT_Pos = 0x1 // Position of BKPT field. SCB_DFSR_BKPT_Msk = 0x2 // Bit mask of BKPT field. SCB_DFSR_BKPT = 0x2 // Bit BKPT. SCB_DFSR_BKPT_BKPT_0 = 0x0 // No current breakpoint debug event SCB_DFSR_BKPT_BKPT_1 = 0x1 // At least one current breakpoint debug event SCB_DFSR_DWTTRAP_Pos = 0x2 // Position of DWTTRAP field. SCB_DFSR_DWTTRAP_Msk = 0x4 // Bit mask of DWTTRAP field. SCB_DFSR_DWTTRAP = 0x4 // Bit DWTTRAP. SCB_DFSR_DWTTRAP_DWTTRAP_0 = 0x0 // No current debug events generated by the DWT SCB_DFSR_DWTTRAP_DWTTRAP_1 = 0x1 // At least one current debug event generated by the DWT SCB_DFSR_VCATCH_Pos = 0x3 // Position of VCATCH field. SCB_DFSR_VCATCH_Msk = 0x8 // Bit mask of VCATCH field. SCB_DFSR_VCATCH = 0x8 // Bit VCATCH. SCB_DFSR_VCATCH_VCATCH_0 = 0x0 // No Vector catch triggered SCB_DFSR_VCATCH_VCATCH_1 = 0x1 // Vector catch triggered SCB_DFSR_EXTERNAL_Pos = 0x4 // Position of EXTERNAL field. SCB_DFSR_EXTERNAL_Msk = 0x10 // Bit mask of EXTERNAL field. SCB_DFSR_EXTERNAL = 0x10 // Bit EXTERNAL. SCB_DFSR_EXTERNAL_EXTERNAL_0 = 0x0 // No external debug request debug event SCB_DFSR_EXTERNAL_EXTERNAL_1 = 0x1 // External debug request debug event // MMFAR: MemManage Fault Address Register SCB_MMFAR_ADDRESS_Pos = 0x0 // Position of ADDRESS field. SCB_MMFAR_ADDRESS_Msk = 0xffffffff // Bit mask of ADDRESS field. // BFAR: BusFault Address Register SCB_BFAR_ADDRESS_Pos = 0x0 // Position of ADDRESS field. SCB_BFAR_ADDRESS_Msk = 0xffffffff // Bit mask of ADDRESS field. ) ================================================ FILE: src/device/arm/semihosting.go ================================================ package arm // Semihosting commands. // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0471c/Bgbjhiea.html const ( // Regular semihosting calls SemihostingClock = 0x10 SemihostingClose = 0x02 SemihostingElapsed = 0x30 SemihostingErrno = 0x13 SemihostingFileLen = 0x0C SemihostingGetCmdline = 0x15 SemihostingHeapInfo = 0x16 SemihostingIsError = 0x08 SemihostingIsTTY = 0x09 SemihostingOpen = 0x01 SemihostingRead = 0x06 SemihostingReadByte = 0x07 SemihostingRemove = 0x0E SemihostingRename = 0x0F SemihostingSeek = 0x0A SemihostingSystem = 0x12 SemihostingTickFreq = 0x31 SemihostingTime = 0x11 SemihostingTmpName = 0x0D SemihostingWrite = 0x05 SemihostingWrite0 = 0x04 SemihostingWriteByte = 0x03 // Angel semihosting calls SemihostingEnterSVC = 0x17 SemihostingReportException = 0x18 ) // Special codes for the Angel Semihosting interface. // https://www.keil.com/support/man/docs/armcc/armcc_pge1358787050566.htm const ( // Hardware vector reason codes SemihostingBranchThroughZero = 0x20000 SemihostingUndefinedInstr = 0x20001 SemihostingSoftwareInterrupt = 0x20002 SemihostingPrefetchAbort = 0x20003 SemihostingDataAbort = 0x20004 SemihostingAddressException = 0x20005 SemihostingIRQ = 0x20006 SemihostingFIQ = 0x20007 // Software reason codes SemihostingBreakPoint = 0x20020 SemihostingWatchPoint = 0x20021 SemihostingStepComplete = 0x20022 SemihostingRunTimeErrorUnknown = 0x20023 SemihostingInternalError = 0x20024 SemihostingUserInterruption = 0x20025 SemihostingApplicationExit = 0x20026 SemihostingStackOverflow = 0x20027 SemihostingDivisionByZero = 0x20028 SemihostingOSSpecific = 0x20029 ) // Call a semihosting function. // TODO: implement it here using inline assembly. // //go:linkname SemihostingCall SemihostingCall func SemihostingCall(num int, arg uintptr) int ================================================ FILE: src/device/arm64/arm64.go ================================================ package arm64 // Run the given assembly code. The code will be marked as having side effects, // as it doesn't produce output and thus would normally be eliminated by the // optimizer. func Asm(asm string) // Run the given inline assembly. The code will be marked as having side // effects, as it would otherwise be optimized away. The inline assembly string // recognizes template values in the form {name}, like so: // // arm.AsmFull( // "str {value}, [{result}]", // map[string]interface{}{ // "value": 1, // "result": uintptr(unsafe.Pointer(&dest)), // }) // // You can use {} in the asm string (which expands to a register) to set the // return value. func AsmFull(asm string, regs map[string]interface{}) uintptr // Run the following system call (SVCall) with 0 arguments. func SVCall0(num uintptr) uintptr // Run the following system call (SVCall) with 1 argument. func SVCall1(num uintptr, a1 interface{}) uintptr // Run the following system call (SVCall) with 2 arguments. func SVCall2(num uintptr, a1, a2 interface{}) uintptr // Run the following system call (SVCall) with 3 arguments. func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr // Run the following system call (SVCall) with 4 arguments. func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr ================================================ FILE: src/device/asm.go ================================================ package device // Run the given assembly code. The code will be marked as having side effects, // as it doesn't produce output and thus would normally be eliminated by the // optimizer. func Asm(asm string) // Run the given inline assembly. The code will be marked as having side // effects, as it would otherwise be optimized away. The inline assembly string // recognizes template values in the form {name}, like so: // // arm.AsmFull( // "str {value}, [{result}]", // map[string]interface{}{ // "value": 1, // "result": uintptr(unsafe.Pointer(&dest)), // }) // // You can use {} in the asm string (which expands to a register) to set the // return value. func AsmFull(asm string, regs map[string]interface{}) uintptr ================================================ FILE: src/device/esp/esp32.S ================================================ // The following definitions were copied from: // esp-idf/components/xtensa/include/xtensa/corebits.h #define PS_WOE_MASK 0x00040000 #define PS_OWB_MASK 0x00000F00 #define PS_CALLINC_MASK 0x00030000 #define PS_WOE PS_WOE_MASK // Only calling it call_start_cpu0 for consistency with ESP-IDF. .section .text.call_start_cpu0 1: .long _stack_top .global call_start_cpu0 call_start_cpu0: // We need to set the stack pointer to a different value. This is somewhat // complicated in the Xtensa architecture. The code below is a modified // version of the following code: // https://github.com/espressif/esp-idf/blob/c77c4ccf/components/xtensa/include/xt_instr_macros.h#L47 // Disable WOE. rsr.ps a2 movi a3, ~(PS_WOE_MASK) and a2, a2, a3 wsr.ps a2 rsync // Set WINDOWSTART to 1 << WINDOWBASE. rsr.windowbase a2 ssl a2 movi a2, 1 sll a2, a2 wsr.windowstart a2 rsync // Load new stack pointer. l32r sp, 1b // Re-enable WOE. rsr.ps a2 movi a3, PS_WOE or a2, a2, a3 wsr.ps a2 rsync // Enable the FPU (coprocessor 0 so the lowest bit). movi a2, 1 wsr.cpenable a2 rsync // Jump to the runtime start function written in Go. call4 main .section .text.tinygo_scanCurrentStack .global tinygo_scanCurrentStack tinygo_scanCurrentStack: // TODO: save callee saved registers on the stack j tinygo_scanstack ================================================ FILE: src/device/esp/esp32c3.S ================================================ // This is a very minimal bootloader for the ESP32-C3. It only initializes the // flash and then continues with the generic RISC-V initialization code, which // in turn will call runtime.main. // It is written in assembly (and not in a higher level language) to make sure // it is entirely loaded into IRAM and doesn't accidentally call functions // stored in IROM. // // For reference, here is a nice introduction into RISC-V assembly: // https://www.imperialviolet.org/2016/12/31/riscv.html .section .init .global call_start_cpu0 .type call_start_cpu0,@function call_start_cpu0: // At this point: // - The ROM bootloader is finished and has jumped to here. // - We're running from IRAM: both IRAM and DRAM segments have been loaded // by the ROM bootloader. // - We have a usable stack (but not the one we would like to use). // - No flash mappings (MMU) are set up yet. // Reset MMU, see bootloader_reset_mmu in the ESP-IDF. call Cache_Suspend_ICache mv s0, a0 // autoload value call Cache_Invalidate_ICache_All call Cache_MMU_Init // Set up DROM from flash. // Somehow, this also sets up IROM from flash. Not sure why, but it avoids // the need for another such call. // C equivalent: // Cache_Dbus_MMU_Set(MMU_ACCESS_FLASH, 0x3C00_0000, 0, 64, 128, 0) li a0, 0 // ext_ram: MMU_ACCESS_FLASH li a1, 0x3C000000 // vaddr: address in the data bus li a2, 0 // paddr: physical address in the flash chip li a3, 64 // psize: always 64 (kilobytes) li a4, 128 // num: pages to be set (8192K / 64K = 128) li a5, 0 // fixed call Cache_Dbus_MMU_Set // Enable the flash cache. mv a0, s0 // restore autoload value from Cache_Suspend_ICache call call Cache_Resume_ICache // Jump to generic RISC-V initialization, which initializes the stack // pointer and globals register. It should not return. // (It appears that the linker relaxes this jump and instead inserts the // _start function right after here). j _start .section .text.exception_vectors .global _vector_table .type _vector_table,@function _vector_table: .option push .option norvc .rept 32 j handleInterruptASM /* interrupt handler */ .endr .option pop .size _vector_table, .-_vector_table ================================================ FILE: src/device/esp/esp8266.S ================================================ .section .text.tinygo_scanCurrentStack .global tinygo_scanCurrentStack tinygo_scanCurrentStack: // TODO: save callee saved registers on the stack j tinygo_scanstack ================================================ FILE: src/device/gba/gba.go ================================================ // Hand written file mostly derived from https://problemkaputt.de/gbatek.htm //go:build gameboyadvance package gba import ( "runtime/volatile" "unsafe" ) // Interrupt numbers. const ( IRQ_VBLANK = 0 IRQ_HBLANK = 1 IRQ_VCOUNT = 2 IRQ_TIMER0 = 3 IRQ_TIMER1 = 4 IRQ_TIMER2 = 5 IRQ_TIMER3 = 6 IRQ_COM = 7 IRQ_DMA0 = 8 IRQ_DMA1 = 9 IRQ_DMA2 = 10 IRQ_DMA3 = 11 IRQ_KEYPAD = 12 IRQ_GAMEPAK = 13 ) // Peripherals var ( // Display registers DISP = (*DISP_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0000))) // Background control registers BGCNT0 = (*BGCNT_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0008))) BGCNT1 = (*BGCNT_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x000A))) BGCNT2 = (*BGCNT_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x000C))) BGCNT3 = (*BGCNT_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x000E))) BG0 = (*BG_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0010))) BG1 = (*BG_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0014))) BG2 = (*BG_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0018))) BG3 = (*BG_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x001C))) BGA2 = (*BG_AFFINE_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0020))) BGA3 = (*BG_AFFINE_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0030))) WIN = (*WIN_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0040))) GRAPHICS = (*GRAPHICS_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x004C))) // GBA Sound Channel 1 - Tone & Sweep SOUND1 = (*SOUND1_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0060))) // GBA Sound Channel 2 - Tone SOUND2 = (*SOUND2_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0068))) // GBA Sound Channel 3 - Wave Output SOUND3 = (*SOUND3_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0070))) // GBA Sound Channel 4 - Noise SOUND4 = (*SOUND4_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0078))) // GBA Sound Control SOUND = (*SOUND_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0080))) DMA0 = (*DMA_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x00B0))) DMA1 = (*DMA_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x00BC))) DMA2 = (*DMA_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x00C8))) DMA3 = (*DMA_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x00D4))) TM0 = (*TIMER_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0100))) TM1 = (*TIMER_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0104))) TM2 = (*TIMER_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0108))) TM3 = (*TIMER_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x010C))) // Communication 1 SIODATA32 = (*SIODATA32_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0120))) SIOMULTI = (*SIOMULTI_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0120))) KEY = (*KEY_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0130))) // Communication 2 SIO = (*SIO_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0134))) INTERRUPT = (*INTERRUPT_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0200))) ) // Main memory sections const ( // External work RAM MEM_EWRAM uintptr = 0x02000000 // Internal work RAM MEM_IWRAM uintptr = 0x03000000 // I/O registers MEM_IO uintptr = 0x04000000 // Palette. Note: no 8bit write !! MEM_PAL uintptr = 0x05000000 // Video RAM. Note: no 8bit write !! MEM_VRAM uintptr = 0x06000000 // Object Attribute Memory (OAM) Note: no 8bit write !! MEM_OAM uintptr = 0x07000000 // ROM. No write at all (duh) MEM_ROM uintptr = 0x08000000 // Static RAM. 8bit write only MEM_SRAM uintptr = 0x0E000000 ) // Main section sizes const ( EWRAM_SIZE uintptr = 0x40000 IWRAM_SIZE uintptr = 0x08000 PAL_SIZE uintptr = 0x00400 VRAM_SIZE uintptr = 0x18000 OAM_SIZE uintptr = 0x00400 SRAM_SIZE uintptr = 0x10000 ) // Sub section sizes const ( // BG palette size PAL_BG_SIZE = 0x00200 // Object palette size PAL_OBJ_SIZE = 0x00200 // Charblock size CBB_SIZE = 0x04000 // Screenblock size SBB_SIZE = 0x00800 // BG VRAM_MODE_0_2 size VRAM_BG_SIZE_MODE_0_2 = 0x10000 // BG VRAM size VRAM_BG_SIZE_MODE_3_5 = 0x14000 // Object VRAM size VRAM_OBJ_SIZE = 0x08000 // Mode 3 buffer size M3_SIZE = 0x12C00 // Mode 4 buffer size M4_SIZE = 0x09600 // Mode 5 buffer size M5_SIZE = 0x0A000 // Bitmap page size VRAM_PAGE_SIZE = 0x0A000 ) // Sub sections var ( REG_BASE uintptr = MEM_IO // Background palette address MEM_PAL_BG = MEM_PAL // Object palette address MEM_PAL_OBJ = MEM_PAL + PAL_BG_SIZE // Front page address MEM_VRAM_FRONT = MEM_VRAM // Back page address MEM_VRAM_BACK = MEM_VRAM + VRAM_PAGE_SIZE // Object VRAM address - BG Mode 0-2 MEM_VRAM_OBJ_MODE0_2 = MEM_VRAM + VRAM_BG_SIZE_MODE_0_2 // Object VRAM address - BG Mode 3-5 MEM_VRAM_OBJ_MODE_3_5 = MEM_VRAM + VRAM_BG_SIZE_MODE_3_5 ) // Display registers type DISP_Type struct { DISPCNT volatile.Register16 _ [2]byte DISPSTAT volatile.Register16 VCOUNT volatile.Register16 } // Background control registers type BGCNT_Type struct { CNT volatile.Register16 } // Regular background scroll registers. (write only!) type BG_Type struct { HOFS volatile.Register16 VOFS volatile.Register16 } // Affine background parameters. (write only!) type BG_AFFINE_Type struct { PA volatile.Register16 PB volatile.Register16 PC volatile.Register16 PD volatile.Register16 X volatile.Register32 Y volatile.Register32 } type WIN_Type struct { // win0 right, left (0xLLRR) WIN0H volatile.Register16 // win1 right, left (0xLLRR) WIN1H volatile.Register16 // win0 bottom, top (0xTTBB) WIN0V volatile.Register16 // win1 bottom, top (0xTTBB) WIN1V volatile.Register16 // win0, win1 control IN volatile.Register16 // winOut, winObj control OUT volatile.Register16 } type GRAPHICS_Type struct { // Mosaic control MOSAIC volatile.Register32 // Alpha control BLDCNT volatile.Register16 // Fade level BLDALPHA volatile.Register16 // Blend levels BLDY volatile.Register16 } type SOUND1_Type struct { // Sweep register CNT_L volatile.Register16 // Duty/Len/Envelope CNT_H volatile.Register16 // Frequency/Control CNT_X volatile.Register16 } type SOUND2_Type struct { // Duty/Len/Envelope CNT_L volatile.Register16 // not used _ volatile.Register16 // Frequency/Control CNT_H volatile.Register16 } type SOUND3_Type struct { // Stop/Wave RAM select CNT_L volatile.Register16 // Length/Volume CNT_H volatile.Register16 // Frequency/Control CNT_X volatile.Register16 } type SOUND4_Type struct { // Length/Envelope CNT_L volatile.Register16 // not used _ volatile.Register16 // Frequency/Control CNT_H volatile.Register16 } type SOUND_Type struct { // Control Stereo/Volume/Enable CNT_L volatile.Register16 // Control Mixing/DMA Control CNT_H volatile.Register16 // Control Sound on/off CNT_X volatile.Register16 } // DMA type DMA_Type struct { SAD_L volatile.Register16 SAD_H volatile.Register16 DAD_L volatile.Register16 DAD_H volatile.Register16 CNT_L volatile.Register16 CNT_H volatile.Register16 } // TIMER type TIMER_Type struct { DATA volatile.Register16 CNT volatile.Register16 } // serial type SIODATA32_Type struct { DATA32_L volatile.Register16 DATA32_H volatile.Register16 _ volatile.Register16 _ volatile.Register16 CNT volatile.Register16 DATA8 volatile.Register16 } type SIOMULTI_Type struct { MULTI0 volatile.Register16 MULTI1 volatile.Register16 MULTI2 volatile.Register16 MULTI3 volatile.Register16 CNT volatile.Register16 MLT_SEND volatile.Register16 } type SIO_Type struct { RCNT volatile.Register16 } // Keypad registers type KEY_Type struct { INPUT volatile.Register16 CNT volatile.Register16 } // TODO: Joybus communication // Interrupt / System registers type INTERRUPT_Type struct { IE volatile.Register16 IF volatile.Register16 WAITCNT volatile.Register16 IME volatile.Register16 PAUSE volatile.Register16 } // LCD OBJ Attributes type OAMOBJ_Type struct { ATT0 volatile.Register16 ATT1 volatile.Register16 ATT2 volatile.Register16 _ volatile.Register16 } // OAM Rotation/Scaling Parameters type OAMROT_Type struct { _ volatile.Register16 _ volatile.Register16 _ volatile.Register16 PA volatile.Register16 _ volatile.Register16 _ volatile.Register16 _ volatile.Register16 PB volatile.Register16 _ volatile.Register16 _ volatile.Register16 _ volatile.Register16 PC volatile.Register16 _ volatile.Register16 _ volatile.Register16 _ volatile.Register16 PD volatile.Register16 } // Constants for DISP: display const ( // BGMODE: background mode. // Position of BGMODE field. DISPCNT_BGMODE_Pos = 0x0 // Bit mask of BGMODE field. DISPCNT_BGMODE_Msk = 0x4 // BG Mode 0. DISPCNT_BGMODE_0 = 0x0 // BG Mode 1. DISPCNT_BGMODE_1 = 0x1 // BG Mode 2. DISPCNT_BGMODE_2 = 0x2 // BG Mode 3. DISPCNT_BGMODE_3 = 0x3 // BG Mode 4. DISPCNT_BGMODE_4 = 0x4 // FRAMESELECT: frame select (mode 4 and 5 only). DISPCNT_FRAMESELECT_Pos = 0x4 DISPCNT_FRAMESELECT_FRAME0 = 0x0 DISPCNT_FRAMESELECT_FRAME1 = 0x1 // HBLANKINTERVAL: 1=Allow access to OAM during H-Blank DISPCNT_HBLANKINTERVAL_Pos = 0x5 DISPCNT_HBLANKINTERVAL_NOALLOW = 0x0 DISPCNT_HBLANKINTERVAL_ALLOW = 0x1 // OBJCHARVRAM: (0=Two dimensional, 1=One dimensional) DISPCNT_OBJCHARVRAM_Pos = 0x6 DISPCNT_OBJCHARVRAM_2D = 0x0 DISPCNT_OBJCHARVRAM_1D = 0x1 // FORCEDBLANK: (1=Allow FAST access to VRAM,Palette,OAM) DISPCNT_FORCEDBLANK_Pos = 0x7 DISPCNT_FORCEDBLANK_NOALLOW = 0x0 DISPCNT_FORCEDBLANK_ALLOW = 0x1 // Screen Display BG0 DISPCNT_SCREENDISPLAY_BG0_Pos = 0x8 DISPCNT_SCREENDISPLAY_BG0_ENABLE = 0x1 DISPCNT_SCREENDISPLAY_BG0_DISABLE = 0x0 // Screen Display BG1 DISPCNT_SCREENDISPLAY_BG1_Pos = 0x9 DISPCNT_SCREENDISPLAY_BG1_ENABLE = 0x1 DISPCNT_SCREENDISPLAY_BG1_DISABLE = 0x0 // Screen Display BG2 DISPCNT_SCREENDISPLAY_BG2_Pos = 0xA DISPCNT_SCREENDISPLAY_BG2_ENABLE = 0x1 DISPCNT_SCREENDISPLAY_BG2_DISABLE = 0x0 // Screen Display BG3 DISPCNT_SCREENDISPLAY_BG3_Pos = 0xB DISPCNT_SCREENDISPLAY_BG3_ENABLE = 0x1 DISPCNT_SCREENDISPLAY_BG3_DISABLE = 0x0 // Screen Display OBJ DISPCNT_SCREENDISPLAY_OBJ_Pos = 0xC DISPCNT_SCREENDISPLAY_OBJ_ENABLE = 0x1 DISPCNT_SCREENDISPLAY_OBJ_DISABLE = 0x0 // Window 0 Display Flag (0=Off, 1=On) DISPCNT_WINDOW0_DISPLAY_Pos = 0xD DISPCNT_WINDOW0_DISPLAY_ENABLE = 0x1 DISPCNT_WINDOW0_DISPLAY_DISABLE = 0x0 // Window 1 Display Flag (0=Off, 1=On) DISPCNT_WINDOW1_DISPLAY_Pos = 0xE DISPCNT_WINDOW1_DISPLAY_ENABLE = 0x1 DISPCNT_WINDOW1_DISPLAY_DISABLE = 0x0 // OBJ Window Display Flag DISPCNT_WINDOWOBJ_DISPLAY_Pos = 0xF DISPCNT_WINDOWOBJ_DISPLAY_ENABLE = 0x1 DISPCNT_WINDOWOBJ_DISPLAY_DISABLE = 0x0 // DISPSTAT: display status. // V-blank DISPSTAT_VBLANK_Pos = 0x0 DISPSTAT_VBLANK_ENABLE = 0x1 DISPSTAT_VBLANK_DISABLE = 0x0 // H-blank DISPSTAT_HBLANK_Pos = 0x1 DISPSTAT_HBLANK_ENABLE = 0x1 DISPSTAT_HBLANK_DISABLE = 0x0 // V-counter match DISPSTAT_VCOUNTER_Pos = 0x2 DISPSTAT_VCOUNTER_MATCH = 0x1 DISPSTAT_VCOUNTER_NOMATCH = 0x0 // V-blank IRQ DISPSTAT_VBLANK_IRQ_Pos = 0x3 DISPSTAT_VBLANK_IRQ_ENABLE = 0x1 DISPSTAT_VBLANK_IRQ_DISABLE = 0x0 // H-blank IRQ DISPSTAT_HBLANK_IRQ_Pos = 0x4 DISPSTAT_HBLANK_IRQ_ENABLE = 0x1 DISPSTAT_HBLANK_IRQ_DISABLE = 0x0 // V-counter IRQ DISPSTAT_VCOUNTER_IRQ_Pos = 0x5 DISPSTAT_VCOUNTER_IRQ_ENABLE = 0x1 DISPSTAT_VCOUNTER_IRQ_DISABLE = 0x0 // V-count setting DISPSTAT_VCOUNT_SETTING_Pos = 0x8 ) const ( BGCNT_PRIORITY_Pos = 0x0 BGCNT_PRIORITY_Msk = 0x3 BGCNT_CHAR_BASE_Pos = 0x2 BGCNT_CHAR_BASE_Msk = 0x3 BGCNT_MOSAIC_Pos = 0x6 BGCNT_MOSAIC_DISABLE = 0x0 BGCNT_MOSAIC_ENABLE = 0x1 BGCNT_COLORS_Pos = 0x7 BGCNT_COLORS_16 = 0x0 BGCNT_COLORS_256 = 0x1 BGCNT_BASE_Pos = 0x8 BGCNT_BASE_Msk = 0x1F BGCNT_OVERFLOW_Pos = 0xD BGCNT_OVERFLOW_TRANS = 0x0 BGCNT_OVERFLOW_WRAP = 0x1 BGCNT_SIZE_Pos = 0xE BGCNT_SIZE_Msk = 0x3 ) const ( BG_HOFS_Pos = 0x0 BG_HOFS_Msk = 0x1FF BG_VOFS_Pos = 0x0 BG_VOFS_Msk = 0x1FF ) // Constants for TIMER const ( // PRESCALER: Prescaler Selection (0=F/1, 1=F/64, 2=F/256, 3=F/1024) // Position of PRESCALER field. TIMERCNT_PRESCALER_Pos = 0x0 // Bit mask of PRESCALER field. TIMERCNT_PRESCALER_Msk = 0x2 // 0=F/1 TIMERCNT_PRESCALER_1 = 0x0 // 1=F/64 TIMERCNT_PRESCALER_64 = 0x1 // 2=F/256 TIMERCNT_PRESCALER_256 = 0x2 // F/1024 TIMERCNT_PRESCALER_1024 = 0x3 // COUNTUP: Count-up Timing (0=Normal, 1=See below) ;Not used in TM0CNT_H // Position of COUNTUP_TIMING field. TIMERCNT_COUNTUP_TIMING_Pos = 0x2 TIMERCNT_COUNTUP_TIMING_NORMAL = 0x0 TIMERCNT_COUNTUP_TIMING_ENABLED = 0x1 TIMERCNT_TIMER_IRQ_ENABLED_Pos = 0x06 TIMERCNT_TIMER_IRQ_ENABLED = 0x01 TIMERCNT_TIMER_IRQ_DISABLED = 0x00 TIMERCNT_TIMER_STARTSTOP_Pos = 0x07 TIMERCNT_TIMER_START = 0x1 TIMERCNT_TIMER_STOP = 0x0 ) const ( // normal mode SIOCNT_NORMAL_SC_Pos = 0x0 SIOCNT_NORMAL_SC_INTERNAL = 0x1 SIOCNT_NORMAL_SC_EXTERNAL = 0x0 SIOCNT_NORMAL_SCSPEED_Pos = 0x1 SIOCNT_NORMAL_SCSPEED_256K = 0x0 SIOCNT_NORMAL_SCSPEED_2M = 0x1 SIOCNT_NORMAL_SCSTATE_Pos = 0x2 SIOCNT_NORMAL_SCSTATE_LOW = 0x0 SIOCNT_NORMAL_SCSTATE_HIGH = 0x1 SIOCNT_NORMAL_SO_INACTIVE_Pos = 0x3 SIOCNT_NORMAL_SO_INACTIVE_LOW = 0x0 SIOCNT_NORMAL_SO_INACTIVE_HIGH = 0x1 SIOCNT_NORMAL_START_Pos = 0x7 SIOCNT_NORMAL_START_READY = 0x0 SIOCNT_NORMAL_START_ACTIVE = 0x1 SIOCNT_NORMAL_LEN_Pos = 0xC SIOCNT_NORMAL_LEN8 = 0x0 SIOCNT_NORMAL_LEN32 = 0x1 SIOCNT_NORMAL_MODE_Pos = 0xD SIOCNT_NORMAL_MODE = 0x0 // multiplayer mode SIOCNT_MULTI_BR_Pos = 0x0 SIOCNT_MULTI_BR_Msk = 0x3 SIOCNT_MULTI_BR_9600 = 0x0 SIOCNT_MULTI_BR_38400 = 0x1 SIOCNT_MULTI_BR_57600 = 0x2 SIOCNT_MULTI_BR_115200 = 0x3 SIOCNT_MULTI_SI_Pos = 0x2 SIOCNT_MULTI_SI_PARENT = 0x0 SIOCNT_MULTI_SI_CHILD = 0x1 SIOCNT_MULTI_SD_Pos = 0x3 SIOCNT_MULTI_SD_BAD = 0x0 SIOCNT_MULTI_SD_READY = 0x1 SIOCNT_MULTI_ID_Pos = 0x4 SIOCNT_MULTI_ID_Msk = 0x3 SIOCNT_MULTI_ID_PARENT = 0x0 SIOCNT_MULTI_ID_CHILD1 = 0x1 SIOCNT_MULTI_ID_CHILD2 = 0x2 SIOCNT_MULTI_ID_CHILD3 = 0x3 SIOCNT_MULTI_ERR_Pos = 0x6 SIOCNT_MULTI_ERR_NORMAL = 0x0 SIOCNT_MULTI_ERR_ERROR = 0x1 SIOCNT_MULTI_STARTBUSY_Pos = 0x7 SIOCNT_MULTI_STARTBUSY_INACTIVE = 0x0 SIOCNT_MULTI_STARTBUSY_STARTBUSY = 0x1 SIOCNT_MULTI_MODE_Pos = 0xC SIOCNT_MULTI_MODE_Msk = 0x3 SIOCNT_MULTI_MODE = 0x01 // uart mode SIOCNT_UART_BR_Pos = 0x0 SIOCNT_UART_BR_Msk = 0x3 SIOCNT_UART_BR_9600 = 0x0 SIOCNT_UART_BR_38400 = 0x1 SIOCNT_UART_BR_57600 = 0x2 SIOCNT_UART_BR_115200 = 0x3 SIOCNT_UART_CTS_Pos = 0x2 SIOCNT_UART_CTS_ALWAYS = 0x0 SIOCNT_UART_CTS_SENDLOW = 0x1 SIOCNT_UART_PARITY_Pos = 0x3 SIOCNT_UART_PARITY_EVEN = 0x0 SIOCNT_UART_PARITY_ODD = 0x1 SIOCNT_UART_SEND_Pos = 0x4 SIOCNT_UART_SEND_NOTFULL = 0x0 SIOCNT_UART_SEND_FULL = 0x1 SIOCNT_UART_REC_Pos = 0x5 SIOCNT_UART_REC_NOTEMPTY = 0x0 SIOCNT_UART_REC_EMPTY = 0x1 SIOCNT_UART_ERR_Pos = 0x6 SIOCNT_UART_ERR_NOERROR = 0x0 SIOCNT_UART_ERR_ERROR = 0x1 SIOCNT_UART_DATALEN_Pos = 0x7 SIOCNT_UART_DATALEN_7 = 0x0 SIOCNT_UART_DATALEN_8 = 0x1 SIOCNT_UART_FIFO_Pos = 0x8 SIOCNT_UART_FIFO_DISABLE = 0x0 SIOCNT_UART_FIFO_ENABLE = 0x1 SIOCNT_UART_PARITY_ENABLE_Pos = 0x9 SIOCNT_UART_PARITY_DISABLE = 0x0 SIOCNT_UART_PARITY_ENABLE = 0x1 SIOCNT_UART_SEND_ENABLE_Pos = 0xA SIOCNT_UART_SEND_DISABLE = 0x0 SIOCNT_UART_SEND_ENABLE = 0x1 SIOCNT_UART_REC_ENABLE_Pos = 0xB SIOCNT_UART_REC_DISABLE = 0x0 SIOCNT_UART_REC_ENABLE = 0x1 SIOCNT_UART_MODE_Pos = 0xC SIOCNT_UART_MODE_Msk = 0x3 SIOCNT_UART_MODE = 0x11 // IRQs used by all SIOCNT_IRQ_Pos = 0xE SIOCNT_IRQ_DISABLE = 0x0 SIOCNT_IRQ_ENABLE = 0x1 ) const ( SIO_RCNT_MODE_Pos = 0xF SIO_RCNT_MODE_NORMAL = 0x0 SIO_RCNT_MODE_MULTI = 0x0 SIO_RCNT_MODE_UART = 0x0 ) // Constants for KEY const ( // KEYINPUT KEYINPUT_PRESSED = 0x0 KEYINPUT_RELEASED = 0x1 KEYINPUT_BUTTON_A_Pos = 0x0 KEYINPUT_BUTTON_B_Pos = 0x1 KEYINPUT_BUTTON_SELECT_Pos = 0x2 KEYINPUT_BUTTON_START_Pos = 0x3 KEYINPUT_BUTTON_RIGHT_Pos = 0x4 KEYINPUT_BUTTON_LEFT_Pos = 0x5 KEYINPUT_BUTTON_UP_Pos = 0x6 KEYINPUT_BUTTON_DOWN_Pos = 0x7 KEYINPUT_BUTTON_R_Pos = 0x8 KEYINPUT_BUTTON_L_Pos = 0x9 // KEYCNT KEYCNT_IGNORE = 0x0 KEYCNT_SELECT = 0x1 KEYCNT_BUTTON_A_Pos = 0x0 KEYCNT_BUTTON_B_Pos = 0x1 KEYCNT_BUTTON_SELECT_Pos = 0x2 KEYCNT_BUTTON_START_Pos = 0x3 KEYCNT_BUTTON_RIGHT_Pos = 0x4 KEYCNT_BUTTON_LEFT_Pos = 0x5 KEYCNT_BUTTON_UP_Pos = 0x6 KEYCNT_BUTTON_DOWN_Pos = 0x7 KEYCNT_BUTTON_R_Pos = 0x8 KEYCNT_BUTTON_L_Pos = 0x9 KEYCNT_BUTTON_IRQ_DISABLE = 0x0 KEYCNT_BUTTON_IRQ_ENABLE = 0x1 KEYCNT_BUTTON_IRQ_ENABLE_Pos = 0xE KEYCNT_BUTTON_IRQ_COND_OR = 0x0 KEYCNT_BUTTON_IRQ_COND_AND = 0x1 KEYCNT_BUTTON_IRQ_COND_Pos = 0xF ) // Constants for INTERRUPT const ( // IE INTERRUPT_IE_ENABLED = 0x1 INTERRUPT_IE_DISABLED = 0x0 INTERRUPT_IE_VBLANK_Pos = 0x0 INTERRUPT_IE_HBLANK_Pos = 0x1 INTERRUPT_IE_VCOUNTER_MATCH_Pos = 0x2 INTERRUPT_IE_TIMER0_OVERFLOW_Pos = 0x3 INTERRUPT_IE_TIMER1_OVERFLOW_Pos = 0x4 INTERRUPT_IE_TIMER2_OVERFLOW_Pos = 0x5 INTERRUPT_IE_TIMER3_OVERFLOW_Pos = 0x6 INTERRUPT_IE_SERIAL_Pos = 0x7 INTERRUPT_IE_DMA0_Pos = 0x8 INTERRUPT_IE_DMA1_Pos = 0x9 INTERRUPT_IE_DMA2_Pos = 0xA INTERRUPT_IE_DMA3_Pos = 0xB INTERRUPT_IE_KEYPAD_Pos = 0xC INTERRUPT_IE_GAMPAK_Pos = 0xD // IF INTERRUPT_IF_ENABLED = 0x1 INTERRUPT_IF_DISABLED = 0x0 INTERRUPT_IF_VBLANK_Pos = 0x0 INTERRUPT_IF_HBLANK_Pos = 0x1 INTERRUPT_IF_VCOUNTER_MATCH_Pos = 0x2 INTERRUPT_IF_TIMER0_OVERFLOW_Pos = 0x3 INTERRUPT_IF_TIMER1_OVERFLOW_Pos = 0x4 INTERRUPT_IF_TIMER2_OVERFLOW_Pos = 0x5 INTERRUPT_IF_TIMER3_OVERFLOW_Pos = 0x6 INTERRUPT_IF_SERIAL_Pos = 0x7 INTERRUPT_IF_DMA0_Pos = 0x8 INTERRUPT_IF_DMA1_Pos = 0x9 INTERRUPT_IF_DMA2_Pos = 0xA INTERRUPT_IF_DMA3_Pos = 0xB INTERRUPT_IF_KEYPAD_Pos = 0xC INTERRUPT_IF_GAMPAK_Pos = 0xD ) const ( OAMOBJ_ATT0_Y_Pos = 0x0 OAMOBJ_ATT0_Y_Msk = 0xFF OAMOBJ_ATT0_OM_Pos = 0x8 OAMOBJ_ATT0_OM_Msk = 0x3 OAMOBJ_ATT0_OM_REG = 0x0 OAMOBJ_ATT0_OM_AFF = 0x1 OAMOBJ_ATT0_OM_HIDE = 0x2 OAMOBJ_ATT0_OM_DBL = 0x3 OAMOBJ_ATT0_GM_Pos = 0xA OAMOBJ_ATT0_GM_Msk = 0x3 OAMOBJ_ATT0_GM_REG = 0x0 OAMOBJ_ATT0_GM_BLEND = 0x1 OAMOBJ_ATT0_GM_WIN = 0x2 OAMOBJ_ATT0_MOSAIC_Pos = 0xC OAMOBJ_ATT0_MOSAIC_DISABLE = 0x0 OAMOBJ_ATT0_MOSAIC_ENABLE = 0x1 OAMOBJ_ATT0_COLOR_Pos = 0xD OAMOBJ_ATT0_COLOR_4BPP = 0x0 OAMOBJ_ATT0_COLOR_8BPP = 0x1 OAMOBJ_ATT0_SH_Pos = 0xE OAMOBJ_ATT0_SH_Msk = 0x3 OAMOBJ_ATT0_SH_SQUARE = 0x0 OAMOBJ_ATT0_SH_WIDE = 0x1 OAMOBJ_ATT0_SH_TALL = 0x2 OAMOBJ_ATT1_X_Pos = 0x0 OAMOBJ_ATT1_X_Msk = 0xFF OAMOBJ_ATT1_AID_Pos = 0x9 OAMOBJ_ATT1_AID_Msk = 0x1F OAMOBJ_ATT1_HF_Pos = 0xC OAMOBJ_ATT1_HF_NOFLIP = 0x0 OAMOBJ_ATT1_HF_FLIP = 0x1 OAMOBJ_ATT1_VF_Pos = 0xD OAMOBJ_ATT1_VF_NOFLIP = 0x0 OAMOBJ_ATT1_VF_FLIP = 0x1 OAMOBJ_ATT1_SZ_Pos = 0xE OAMOBJ_ATT1_SZ_Msk = 0x3 OAMOBJ_ATT2_TID_Pos = 0x0 OAMOBJ_ATT2_TID_Msk = 0xFF OAMOBJ_ATT2_PR_Pos = 0xA OAMOBJ_ATT2_PR_Msk = 0x3 OAMOBJ_ATT2_PB_Pos = 0xC OAMOBJ_ATT2_PB_Msk = 0xF ) ================================================ FILE: src/device/nrf/README.markdown ================================================ # Generated Go files for Nordic Semiconductors devices In this directory, Go register description files are stored that are generated by `gen-device.py` from .svd files provided by Nordic. See the SVD files [over here](https://github.com/NordicSemiconductor/nrfx/tree/master/mdk). The original files are provided under the 3-clause BSD license, see [this post](https://devzone.nordicsemi.com/b/blog/posts/introducing-nordics-new-software-licensing-schemes) for details. As the generated files transform most of the original file, I think they should be licensed under the same license as the original files. Generated files will contain the license statement that is included in the original SVD files. ================================================ FILE: src/device/riscv/csr.go ================================================ package riscv // This file lists constants for CSR operations and defines methods on CSRs that // are implemented as compiler intrinsics. // CSR constants are used for use in CSR (Control and Status Register) compiler // intrinsics. type CSR int16 // Get returns the value of the given CSR. func (csr CSR) Get() uintptr // Set stores a new value in the given CSR. func (csr CSR) Set(uintptr) // SetBits atomically sets the given bits in this ISR and returns the old value. func (csr CSR) SetBits(uintptr) uintptr // ClearBits atomically clears the given bits in this ISR and returns the old // value. func (csr CSR) ClearBits(uintptr) uintptr // CSR values defined in the RISC-V privileged specification. Not all values may // be available on any given chip. // // Source: https://github.com/riscv/riscv-isa-manual/blob/riscv-priv-1.10/src/priv-csrs.tex const ( // User Trap Setup USTATUS CSR = 0x000 // User status register. UIE CSR = 0x004 // User interrupt-enable register. UTVEC CSR = 0x005 // User trap handler base address. // User Trap Handling USCRATCH CSR = 0x040 // Scratch register for user trap handlers. UEPC CSR = 0x041 // User exception program counter. UCAUSE CSR = 0x042 // User trap cause. UTVAL CSR = 0x043 // User bad address or instruction. UIP CSR = 0x044 // User interrupt pending. // User Floating-Point CSRs FFLAGS CSR = 0x001 // Floating-Point Accrued Exceptions. FRM CSR = 0x002 // Floating-Point Dynamic Rounding Mode. FCSR CSR = 0x003 // Floating-Point Control and Status // User Counter/Timers CYCLE CSR = 0xC00 // Cycle counter for RDCYCLE instruction. TIME CSR = 0xC01 // Timer for RDTIME instruction. INSTRET CSR = 0xC02 // Instructions-retired counter for RDINSTRET instruction. HPMCOUNTER3 CSR = 0xC03 // Performance-monitoring counter 3. HPMCOUNTER4 CSR = 0xC04 // Performance-monitoring counter 4. HPMCOUNTER5 CSR = 0xC05 // Performance-monitoring counter 5. HPMCOUNTER6 CSR = 0xC06 // Performance-monitoring counter 6. HPMCOUNTER7 CSR = 0xC07 // Performance-monitoring counter 7. HPMCOUNTER8 CSR = 0xC08 // Performance-monitoring counter 8. HPMCOUNTER9 CSR = 0xC09 // Performance-monitoring counter 9. HPMCOUNTER10 CSR = 0xC0A // Performance-monitoring counter 10. HPMCOUNTER11 CSR = 0xC0B // Performance-monitoring counter 11. HPMCOUNTER12 CSR = 0xC0C // Performance-monitoring counter 12. HPMCOUNTER13 CSR = 0xC0D // Performance-monitoring counter 13. HPMCOUNTER14 CSR = 0xC0E // Performance-monitoring counter 14. HPMCOUNTER15 CSR = 0xC0F // Performance-monitoring counter 15. HPMCOUNTER16 CSR = 0xC10 // Performance-monitoring counter 16. HPMCOUNTER17 CSR = 0xC11 // Performance-monitoring counter 17. HPMCOUNTER18 CSR = 0xC12 // Performance-monitoring counter 18. HPMCOUNTER19 CSR = 0xC13 // Performance-monitoring counter 19. HPMCOUNTER20 CSR = 0xC14 // Performance-monitoring counter 20. HPMCOUNTER21 CSR = 0xC15 // Performance-monitoring counter 21. HPMCOUNTER22 CSR = 0xC16 // Performance-monitoring counter 22. HPMCOUNTER23 CSR = 0xC17 // Performance-monitoring counter 23. HPMCOUNTER24 CSR = 0xC18 // Performance-monitoring counter 24. HPMCOUNTER25 CSR = 0xC19 // Performance-monitoring counter 25. HPMCOUNTER26 CSR = 0xC1A // Performance-monitoring counter 26. HPMCOUNTER27 CSR = 0xC1B // Performance-monitoring counter 27. HPMCOUNTER28 CSR = 0xC1C // Performance-monitoring counter 28. HPMCOUNTER29 CSR = 0xC1D // Performance-monitoring counter 29. HPMCOUNTER30 CSR = 0xC1E // Performance-monitoring counter 30. HPMCOUNTER31 CSR = 0xC1F // Performance-monitoring counter 31. CYCLEH CSR = 0xC80 // Upper 32 bits of CYCLE, RV32I only. TIMEH CSR = 0xC81 // Upper 32 bits of TIME, RV32I only. INSTRETH CSR = 0xC82 // Upper 32 bits of INSTRET, RV32I only. HPMCOUNTER3H CSR = 0xC83 // Upper 32 bits of HPMCOUNTER3, RV32I only. HPMCOUNTER4H CSR = 0xC84 // Upper 32 bits of HPMCOUNTER4, RV32I only. HPMCOUNTER5H CSR = 0xC85 // Upper 32 bits of HPMCOUNTER5, RV32I only. HPMCOUNTER6H CSR = 0xC86 // Upper 32 bits of HPMCOUNTER6, RV32I only. HPMCOUNTER7H CSR = 0xC87 // Upper 32 bits of HPMCOUNTER7, RV32I only. HPMCOUNTER8H CSR = 0xC88 // Upper 32 bits of HPMCOUNTER8, RV32I only. HPMCOUNTER9H CSR = 0xC89 // Upper 32 bits of HPMCOUNTER9, RV32I only. HPMCOUNTER10H CSR = 0xC8A // Upper 32 bits of HPMCOUNTER10, RV32I only. HPMCOUNTER11H CSR = 0xC8B // Upper 32 bits of HPMCOUNTER11, RV32I only. HPMCOUNTER12H CSR = 0xC8C // Upper 32 bits of HPMCOUNTER12, RV32I only. HPMCOUNTER13H CSR = 0xC8D // Upper 32 bits of HPMCOUNTER13, RV32I only. HPMCOUNTER14H CSR = 0xC8E // Upper 32 bits of HPMCOUNTER14, RV32I only. HPMCOUNTER15H CSR = 0xC8F // Upper 32 bits of HPMCOUNTER15, RV32I only. HPMCOUNTER16H CSR = 0xC90 // Upper 32 bits of HPMCOUNTER16, RV32I only. HPMCOUNTER17H CSR = 0xC91 // Upper 32 bits of HPMCOUNTER17, RV32I only. HPMCOUNTER18H CSR = 0xC92 // Upper 32 bits of HPMCOUNTER18, RV32I only. HPMCOUNTER19H CSR = 0xC93 // Upper 32 bits of HPMCOUNTER19, RV32I only. HPMCOUNTER20H CSR = 0xC94 // Upper 32 bits of HPMCOUNTER20, RV32I only. HPMCOUNTER21H CSR = 0xC95 // Upper 32 bits of HPMCOUNTER21, RV32I only. HPMCOUNTER22H CSR = 0xC96 // Upper 32 bits of HPMCOUNTER22, RV32I only. HPMCOUNTER23H CSR = 0xC97 // Upper 32 bits of HPMCOUNTER23, RV32I only. HPMCOUNTER24H CSR = 0xC98 // Upper 32 bits of HPMCOUNTER24, RV32I only. HPMCOUNTER25H CSR = 0xC99 // Upper 32 bits of HPMCOUNTER25, RV32I only. HPMCOUNTER26H CSR = 0xC9A // Upper 32 bits of HPMCOUNTER26, RV32I only. HPMCOUNTER27H CSR = 0xC9B // Upper 32 bits of HPMCOUNTER27, RV32I only. HPMCOUNTER28H CSR = 0xC9C // Upper 32 bits of HPMCOUNTER28, RV32I only. HPMCOUNTER29H CSR = 0xC9D // Upper 32 bits of HPMCOUNTER29, RV32I only. HPMCOUNTER30H CSR = 0xC9E // Upper 32 bits of HPMCOUNTER30, RV32I only. HPMCOUNTER31H CSR = 0xC9F // Upper 32 bits of HPMCOUNTER31, RV32I only. // Supervisor Trap Setup SSTATUS CSR = 0x100 // Supervisor status register. SEDELEG CSR = 0x102 // Supervisor exception delegation register. SIDELEG CSR = 0x103 // Supervisor interrupt delegation register. SIE CSR = 0x104 // Supervisor interrupt-enable register. STVEC CSR = 0x105 // Supervisor trap handler base address. SCOUNTEREN CSR = 0x106 // Supervisor counter enable. // Supervisor Trap Handling SSCRATCH CSR = 0x140 // Scratch register for supervisor trap handlers. SEPC CSR = 0x141 // Supervisor exception program counter. SCAUSE CSR = 0x142 // Supervisor trap cause. STVAL CSR = 0x143 // Supervisor bad address or instruction. SIP CSR = 0x144 // Supervisor interrupt pending. // Supervisor Protection and Translation SATP CSR = 0x180 // Supervisor address translation and protection. // Machine Information Registers MVENDORID CSR = 0xF11 // Vendor ID. MARCHID CSR = 0xF12 // Architecture ID. MIMPID CSR = 0xF13 // Implementation ID. MHARTID CSR = 0xF14 // Hardware thread ID. // Machine Trap Setup MSTATUS CSR = 0x300 // Machine status register. MISA CSR = 0x301 // ISA and extensions MEDELEG CSR = 0x302 // Machine exception delegation register. MIDELEG CSR = 0x303 // Machine interrupt delegation register. MIE CSR = 0x304 // Machine interrupt-enable register. MTVEC CSR = 0x305 // Machine trap-handler base address. MCOUNTEREN CSR = 0x306 // Machine counter enable. // Machine Trap Handling MSCRATCH CSR = 0x340 // Scratch register for machine trap handlers. MEPC CSR = 0x341 // Machine exception program counter. MCAUSE CSR = 0x342 // Machine trap cause. MTVAL CSR = 0x343 // Machine bad address or instruction. MIP CSR = 0x344 // Machine interrupt pending. // Machine Protection and Translation PMPCFG0 CSR = 0x3A0 // Physical memory protection configuration. PMPCFG1 CSR = 0x3A1 // Physical memory protection configuration, RV32 only. PMPCFG2 CSR = 0x3A2 // Physical memory protection configuration. PMPCFG3 CSR = 0x3A3 // Physical memory protection configuration, RV32 only. PMPADDR0 CSR = 0x3B0 // Physical memory protection address register 0. PMPADDR1 CSR = 0x3B1 // Physical memory protection address register 1. PMPADDR2 CSR = 0x3B2 // Physical memory protection address register 2. PMPADDR3 CSR = 0x3B3 // Physical memory protection address register 3. PMPADDR4 CSR = 0x3B4 // Physical memory protection address register 4. PMPADDR5 CSR = 0x3B5 // Physical memory protection address register 5. PMPADDR6 CSR = 0x3B6 // Physical memory protection address register 6. PMPADDR7 CSR = 0x3B7 // Physical memory protection address register 7. PMPADDR8 CSR = 0x3B8 // Physical memory protection address register 8. PMPADDR9 CSR = 0x3B9 // Physical memory protection address register 9. PMPADDR10 CSR = 0x3BA // Physical memory protection address register 10. PMPADDR11 CSR = 0x3BB // Physical memory protection address register 11. PMPADDR12 CSR = 0x3BC // Physical memory protection address register 12. PMPADDR13 CSR = 0x3BD // Physical memory protection address register 13. PMPADDR14 CSR = 0x3BE // Physical memory protection address register 14. PMPADDR15 CSR = 0x3BF // Physical memory protection address register 15. // Machine Counter/Timers mcycle CSR = 0xB00 // Machine cycle counter. minstret CSR = 0xB02 // Machine instructions-retired counter. MHPMCOUNTER3 CSR = 0xB03 // Machine performance-monitoring counter 3. MHPMCOUNTER4 CSR = 0xB04 // Machine performance-monitoring counter 4. MHPMCOUNTER5 CSR = 0xB05 // Machine performance-monitoring counter 5. MHPMCOUNTER6 CSR = 0xB06 // Machine performance-monitoring counter 6. MHPMCOUNTER7 CSR = 0xB07 // Machine performance-monitoring counter 7. MHPMCOUNTER8 CSR = 0xB08 // Machine performance-monitoring counter 8. MHPMCOUNTER9 CSR = 0xB09 // Machine performance-monitoring counter 9. MHPMCOUNTER10 CSR = 0xB0A // Machine performance-monitoring counter 10. MHPMCOUNTER11 CSR = 0xB0B // Machine performance-monitoring counter 11. MHPMCOUNTER12 CSR = 0xB0C // Machine performance-monitoring counter 12. MHPMCOUNTER13 CSR = 0xB0D // Machine performance-monitoring counter 13. MHPMCOUNTER14 CSR = 0xB0E // Machine performance-monitoring counter 14. MHPMCOUNTER15 CSR = 0xB0F // Machine performance-monitoring counter 15. MHPMCOUNTER16 CSR = 0xB10 // Machine performance-monitoring counter 16. MHPMCOUNTER17 CSR = 0xB11 // Machine performance-monitoring counter 17. MHPMCOUNTER18 CSR = 0xB12 // Machine performance-monitoring counter 18. MHPMCOUNTER19 CSR = 0xB13 // Machine performance-monitoring counter 19. MHPMCOUNTER20 CSR = 0xB14 // Machine performance-monitoring counter 20. MHPMCOUNTER21 CSR = 0xB15 // Machine performance-monitoring counter 21. MHPMCOUNTER22 CSR = 0xB16 // Machine performance-monitoring counter 22. MHPMCOUNTER23 CSR = 0xB17 // Machine performance-monitoring counter 23. MHPMCOUNTER24 CSR = 0xB18 // Machine performance-monitoring counter 24. MHPMCOUNTER25 CSR = 0xB19 // Machine performance-monitoring counter 25. MHPMCOUNTER26 CSR = 0xB1A // Machine performance-monitoring counter 26. MHPMCOUNTER27 CSR = 0xB1B // Machine performance-monitoring counter 27. MHPMCOUNTER28 CSR = 0xB1C // Machine performance-monitoring counter 28. MHPMCOUNTER29 CSR = 0xB1D // Machine performance-monitoring counter 29. MHPMCOUNTER30 CSR = 0xB1E // Machine performance-monitoring counter 30. MHPMCOUNTER31 CSR = 0xB1F // Machine performance-monitoring counter 31. MCYCLEH CSR = 0xB80 // Upper 32 bits of MCYCLE, RV32I only. MINSTRETH CSR = 0xB82 // Upper 32 bits of MINSTRET, RV32I only. MHPMCOUNTER3H CSR = 0xB83 // Upper 32 bits of MHPMCOUNTER3, RV32I only. MHPMCOUNTER4H CSR = 0xB84 // Upper 32 bits of MHPMCOUNTER4, RV32I only. MHPMCOUNTER5H CSR = 0xB85 // Upper 32 bits of MHPMCOUNTER5, RV32I only. MHPMCOUNTER6H CSR = 0xB86 // Upper 32 bits of MHPMCOUNTER6, RV32I only. MHPMCOUNTER7H CSR = 0xB87 // Upper 32 bits of MHPMCOUNTER7, RV32I only. MHPMCOUNTER8H CSR = 0xB88 // Upper 32 bits of MHPMCOUNTER8, RV32I only. MHPMCOUNTER9H CSR = 0xB89 // Upper 32 bits of MHPMCOUNTER9, RV32I only. MHPMCOUNTER10H CSR = 0xB8A // Upper 32 bits of MHPMCOUNTER10, RV32I only. MHPMCOUNTER11H CSR = 0xB8B // Upper 32 bits of MHPMCOUNTER11, RV32I only. MHPMCOUNTER12H CSR = 0xB8C // Upper 32 bits of MHPMCOUNTER12, RV32I only. MHPMCOUNTER13H CSR = 0xB8D // Upper 32 bits of MHPMCOUNTER13, RV32I only. MHPMCOUNTER14H CSR = 0xB8E // Upper 32 bits of MHPMCOUNTER14, RV32I only. MHPMCOUNTER15H CSR = 0xB8F // Upper 32 bits of MHPMCOUNTER15, RV32I only. MHPMCOUNTER16H CSR = 0xB90 // Upper 32 bits of MHPMCOUNTER16, RV32I only. MHPMCOUNTER17H CSR = 0xB91 // Upper 32 bits of MHPMCOUNTER17, RV32I only. MHPMCOUNTER18H CSR = 0xB92 // Upper 32 bits of MHPMCOUNTER18, RV32I only. MHPMCOUNTER19H CSR = 0xB93 // Upper 32 bits of MHPMCOUNTER19, RV32I only. MHPMCOUNTER20H CSR = 0xB94 // Upper 32 bits of MHPMCOUNTER20, RV32I only. MHPMCOUNTER21H CSR = 0xB95 // Upper 32 bits of MHPMCOUNTER21, RV32I only. MHPMCOUNTER22H CSR = 0xB96 // Upper 32 bits of MHPMCOUNTER22, RV32I only. MHPMCOUNTER23H CSR = 0xB97 // Upper 32 bits of MHPMCOUNTER23, RV32I only. MHPMCOUNTER24H CSR = 0xB98 // Upper 32 bits of MHPMCOUNTER24, RV32I only. MHPMCOUNTER25H CSR = 0xB99 // Upper 32 bits of MHPMCOUNTER25, RV32I only. MHPMCOUNTER26H CSR = 0xB9A // Upper 32 bits of MHPMCOUNTER26, RV32I only. MHPMCOUNTER27H CSR = 0xB9B // Upper 32 bits of MHPMCOUNTER27, RV32I only. MHPMCOUNTER28H CSR = 0xB9C // Upper 32 bits of MHPMCOUNTER28, RV32I only. MHPMCOUNTER29H CSR = 0xB9D // Upper 32 bits of MHPMCOUNTER29, RV32I only. MHPMCOUNTER30H CSR = 0xB9E // Upper 32 bits of MHPMCOUNTER30, RV32I only. MHPMCOUNTER31H CSR = 0xB9F // Upper 32 bits of MHPMCOUNTER31, RV32I only. // Machine Counter Setup MHPMEVENT4 CSR = 0x324 // Machine performance-monitoring event selector 4. MHPMEVENT5 CSR = 0x325 // Machine performance-monitoring event selector 5. MHPMEVENT6 CSR = 0x326 // Machine performance-monitoring event selector 6. MHPMEVENT7 CSR = 0x327 // Machine performance-monitoring event selector 7. MHPMEVENT8 CSR = 0x328 // Machine performance-monitoring event selector 8. MHPMEVENT9 CSR = 0x329 // Machine performance-monitoring event selector 9. MHPMEVENT10 CSR = 0x32A // Machine performance-monitoring event selector 10. MHPMEVENT11 CSR = 0x32B // Machine performance-monitoring event selector 11. MHPMEVENT12 CSR = 0x32C // Machine performance-monitoring event selector 12. MHPMEVENT13 CSR = 0x32D // Machine performance-monitoring event selector 13. MHPMEVENT14 CSR = 0x32E // Machine performance-monitoring event selector 14. MHPMEVENT15 CSR = 0x32F // Machine performance-monitoring event selector 15. MHPMEVENT16 CSR = 0x330 // Machine performance-monitoring event selector 16. MHPMEVENT17 CSR = 0x331 // Machine performance-monitoring event selector 17. MHPMEVENT18 CSR = 0x332 // Machine performance-monitoring event selector 18. MHPMEVENT19 CSR = 0x333 // Machine performance-monitoring event selector 19. MHPMEVENT20 CSR = 0x334 // Machine performance-monitoring event selector 20. MHPMEVENT21 CSR = 0x335 // Machine performance-monitoring event selector 21. MHPMEVENT22 CSR = 0x336 // Machine performance-monitoring event selector 22. MHPMEVENT23 CSR = 0x337 // Machine performance-monitoring event selector 23. MHPMEVENT24 CSR = 0x338 // Machine performance-monitoring event selector 24. MHPMEVENT25 CSR = 0x339 // Machine performance-monitoring event selector 25. MHPMEVENT26 CSR = 0x33A // Machine performance-monitoring event selector 26. MHPMEVENT27 CSR = 0x33B // Machine performance-monitoring event selector 27. MHPMEVENT28 CSR = 0x33C // Machine performance-monitoring event selector 28. MHPMEVENT29 CSR = 0x33D // Machine performance-monitoring event selector 29. MHPMEVENT30 CSR = 0x33E // Machine performance-monitoring event selector 30. MHPMEVENT31 CSR = 0x33F // Machine performance-monitoring event selector 31. // Debug/Trace Registers (shared with Debug Mode) TSELECT CSR = 0x7A0 // Debug/Trace trigger register select. TDATA1 CSR = 0x7A1 // First Debug/Trace trigger data register. TDATA2 CSR = 0x7A2 // Second Debug/Trace trigger data register. TDATA3 CSR = 0x7A3 // Third Debug/Trace trigger data register. // Debug Mode Registers DCSR CSR = 0x7B0 // Debug control and status register. DPC CSR = 0x7B1 // Debug PC. DSCRATCH CSR = 0x7B2 // Debug scratch register. ) // Bitfields for the CSR registers above. const ( // MSTATUS (common bits between RV32 and RV64) MSTATUS_SIE = 1 << 1 MSTATUS_MIE = 1 << 3 MSTATUS_SPIE = 1 << 5 MSTATUS_UBE = 1 << 6 MSTATUS_MPIE = 1 << 7 MSTATUS_SPP = 1 << 8 MSTATUS_MPRV = 1 << 17 MSTATUS_SUM = 1 << 18 MSTATUS_MXR = 1 << 19 MSTATUS_TVM = 1 << 20 MSTATUS_TW = 1 << 21 MSTATUS_TSR = 1 << 22 MIE_SSIE = 1 << 1 MIE_MSIE = 1 << 3 MIE_STIE = 1 << 5 MIE_MTIE = 1 << 7 MIE_SEIE = 1 << 9 MIE_MEIE = 1 << 11 MIP_SSIP = 1 << 1 MIP_MSIP = 1 << 3 MIP_STIP = 1 << 5 MIP_MTIP = 1 << 7 MIP_SEIP = 1 << 9 MIP_MEIP = 1 << 11 ) // Interrupt constants const ( // MCAUSE values with the topmost bit (interrupt bit) set. SupervisorSoftwareInterrupt = 1 MachineSoftwareInterrupt = 3 SupervisorTimerInterrupt = 5 MachineTimerInterrupt = 7 SupervisorExternalInterrupt = 9 MachineExternalInterrupt = 11 // MCAUSE values with the topmost bit (interrupt bit) clear. InstructionAddressMisaligned = 0 InstructionAccessFault = 1 IllegalInstruction = 2 Breakpoint = 3 LoadAddressMisaligned = 4 LoadAccessFault = 5 StoreOrAMOAddressMisaligned = 6 StoreOrAMOAccessFault = 7 EnvironmentCallFromUMode = 8 EnvironmentCallFromSMode = 9 EnvironmentCallFromMMode = 11 InstructionPageFault = 12 LoadPageFault = 13 StoreOrAMOPageFault = 15 ) ================================================ FILE: src/device/riscv/handleinterrupt.S ================================================ #ifdef __riscv_flen #define NREG 48 #define LFREG flw #define SFREG fsw #else #define NREG 16 #endif #if __riscv_xlen==64 #define REGSIZE 8 #define SREG sd #define LREG ld #else #define REGSIZE 4 #define SREG sw #define LREG lw #endif .section .text.handleInterruptASM .global handleInterruptASM .type handleInterruptASM,@function handleInterruptASM: // Save and restore all registers, because the hardware only saves/restores // the pc. // Note: we have to do this in assembly because the "interrupt"="machine" // attribute is broken in LLVM: https://bugs.llvm.org/show_bug.cgi?id=42984 addi sp, sp, -NREG*REGSIZE SREG ra, 0*REGSIZE(sp) SREG t0, 1*REGSIZE(sp) SREG t1, 2*REGSIZE(sp) SREG t2, 3*REGSIZE(sp) SREG a0, 4*REGSIZE(sp) SREG a1, 5*REGSIZE(sp) SREG a2, 6*REGSIZE(sp) SREG a3, 7*REGSIZE(sp) SREG a4, 8*REGSIZE(sp) SREG a5, 9*REGSIZE(sp) SREG a6, 10*REGSIZE(sp) SREG a7, 11*REGSIZE(sp) SREG t3, 12*REGSIZE(sp) SREG t4, 13*REGSIZE(sp) SREG t5, 14*REGSIZE(sp) SREG t6, 15*REGSIZE(sp) #ifdef __riscv_flen SFREG f0, (0 + 16)*REGSIZE(sp) SFREG f1, (1 + 16)*REGSIZE(sp) SFREG f2, (2 + 16)*REGSIZE(sp) SFREG f3, (3 + 16)*REGSIZE(sp) SFREG f4, (4 + 16)*REGSIZE(sp) SFREG f5, (5 + 16)*REGSIZE(sp) SFREG f6, (6 + 16)*REGSIZE(sp) SFREG f7, (7 + 16)*REGSIZE(sp) SFREG f8, (8 + 16)*REGSIZE(sp) SFREG f9, (9 + 16)*REGSIZE(sp) SFREG f10,(10 + 16)*REGSIZE(sp) SFREG f11,(11 + 16)*REGSIZE(sp) SFREG f12,(12 + 16)*REGSIZE(sp) SFREG f13,(13 + 16)*REGSIZE(sp) SFREG f14,(14 + 16)*REGSIZE(sp) SFREG f15,(15 + 16)*REGSIZE(sp) SFREG f16,(16 + 16)*REGSIZE(sp) SFREG f17,(17 + 16)*REGSIZE(sp) SFREG f18,(18 + 16)*REGSIZE(sp) SFREG f19,(19 + 16)*REGSIZE(sp) SFREG f20,(20 + 16)*REGSIZE(sp) SFREG f21,(21 + 16)*REGSIZE(sp) SFREG f22,(22 + 16)*REGSIZE(sp) SFREG f23,(23 + 16)*REGSIZE(sp) SFREG f24,(24 + 16)*REGSIZE(sp) SFREG f25,(25 + 16)*REGSIZE(sp) SFREG f26,(26 + 16)*REGSIZE(sp) SFREG f27,(27 + 16)*REGSIZE(sp) SFREG f28,(28 + 16)*REGSIZE(sp) SFREG f29,(29 + 16)*REGSIZE(sp) SFREG f30,(30 + 16)*REGSIZE(sp) SFREG f31,(31 + 16)*REGSIZE(sp) #endif call handleInterrupt #ifdef __riscv_flen LFREG f0, (31 + 16)*REGSIZE(sp) LFREG f1, (30 + 16)*REGSIZE(sp) LFREG f2, (29 + 16)*REGSIZE(sp) LFREG f3, (28 + 16)*REGSIZE(sp) LFREG f4, (27 + 16)*REGSIZE(sp) LFREG f5, (26 + 16)*REGSIZE(sp) LFREG f6, (25 + 16)*REGSIZE(sp) LFREG f7, (24 + 16)*REGSIZE(sp) LFREG f8, (23 + 16)*REGSIZE(sp) LFREG f9, (22 + 16)*REGSIZE(sp) LFREG f10,(21 + 16)*REGSIZE(sp) LFREG f11,(20 + 16)*REGSIZE(sp) LFREG f12,(19 + 16)*REGSIZE(sp) LFREG f13,(18 + 16)*REGSIZE(sp) LFREG f14,(17 + 16)*REGSIZE(sp) LFREG f15,(16 + 16)*REGSIZE(sp) LFREG f16,(15 + 16)*REGSIZE(sp) LFREG f17,(14 + 16)*REGSIZE(sp) LFREG f18,(13 + 16)*REGSIZE(sp) LFREG f19,(12 + 16)*REGSIZE(sp) LFREG f20,(11 + 16)*REGSIZE(sp) LFREG f21,(10 + 16)*REGSIZE(sp) LFREG f22,(9 + 16)*REGSIZE(sp) LFREG f23,(8 + 16)*REGSIZE(sp) LFREG f24,(7 + 16)*REGSIZE(sp) LFREG f25,(6 + 16)*REGSIZE(sp) LFREG f26,(5 + 16)*REGSIZE(sp) LFREG f27,(4 + 16)*REGSIZE(sp) LFREG f28,(3 + 16)*REGSIZE(sp) LFREG f29,(2 + 16)*REGSIZE(sp) LFREG f30,(1 + 16)*REGSIZE(sp) LFREG f31,(0 + 16)*REGSIZE(sp) #endif LREG t6, 15*REGSIZE(sp) LREG t5, 14*REGSIZE(sp) LREG t4, 13*REGSIZE(sp) LREG t3, 12*REGSIZE(sp) LREG a7, 11*REGSIZE(sp) LREG a6, 10*REGSIZE(sp) LREG a5, 9*REGSIZE(sp) LREG a4, 8*REGSIZE(sp) LREG a3, 7*REGSIZE(sp) LREG a2, 6*REGSIZE(sp) LREG a1, 5*REGSIZE(sp) LREG a0, 4*REGSIZE(sp) LREG t2, 3*REGSIZE(sp) LREG t1, 2*REGSIZE(sp) LREG t0, 1*REGSIZE(sp) LREG ra, 0*REGSIZE(sp) addi sp, sp, NREG*REGSIZE mret ================================================ FILE: src/device/riscv/riscv.go ================================================ package riscv // Run the given assembly code. The code will be marked as having side effects, // as it doesn't produce output and thus would normally be eliminated by the // optimizer. func Asm(asm string) // Run the given inline assembly. The code will be marked as having side // effects, as it would otherwise be optimized away. The inline assembly string // recognizes template values in the form {name}, like so: // // arm.AsmFull( // "st {value}, [{result}]", // map[string]interface{}{ // "value": 1, // "result": uintptr(unsafe.Pointer(&dest)), // }) // // You can use {} in the asm string (which expands to a register) to set the // return value. func AsmFull(asm string, regs map[string]interface{}) uintptr // DisableInterrupts disables all interrupts, and returns the old interrupt // state. func DisableInterrupts() uintptr { // Note: this can be optimized with a CSRRW instruction, which atomically // swaps the value and returns the old value. mask := MSTATUS.Get() MSTATUS.ClearBits(1 << 3) // clear the MIE bit return mask } // EnableInterrupts enables all interrupts again. The value passed in must be // the mask returned by DisableInterrupts. func EnableInterrupts(mask uintptr) { mask &= 1 << 3 // clear all bits except for the MIE bit MSTATUS.SetBits(mask) // set the MIE bit, if it was previously cleared } ================================================ FILE: src/device/riscv/start.S ================================================ .section .init .global _start .type _start,@function _start: // If we're on a multicore system, we need to wait for hart 0 to wake us up. #if TINYGO_CORES > 1 csrr a0, mhartid // Hart 0 stack bnez a0, 1f la sp, _stack_top 1: // Hart 1 stack li a1, 1 bne a0, a1, 2f la sp, _stack1_top 2: // Hart 2 stack #if TINYGO_CORES >= 3 li a1, 2 bne a0, a1, 3f la sp, _stack2_top #endif 3: // Hart 3 stack #if TINYGO_CORES >= 4 li a1, 3 bne a0, a1, 4f la sp, _stack3_top #endif 4: // done #if TINYGO_CORES > 4 #error only up to 4 cores are supported at the moment! #endif #else // Load the stack pointer. la sp, _stack_top #endif // Load the globals pointer. The program will load pointers relative to this // register, so it must be set to the right value on startup. // See: https://gnu-mcu-eclipse.github.io/arch/riscv/programmer/#the-gp-global-pointer-register // Linker relaxations must be disabled to avoid the initialization beign // relaxed with an uninitialized global pointer: mv gp, gp .option push .option norelax la gp, __global_pointer$ .option pop // Jump to runtime.main call main ================================================ FILE: src/device/tkey/tkey.go ================================================ //go:build tkey // Hand written file based on https://github.com/tillitis/tkey-libs/blob/main/include/tkey/tk1_mem.h package tkey import ( "runtime/volatile" "unsafe" ) // Peripherals var ( TRNG = (*TRNG_Type)(unsafe.Pointer(TK1_MMIO_TRNG_BASE)) TIMER = (*TIMER_Type)(unsafe.Pointer(TK1_MMIO_TIMER_BASE)) UDS = (*UDS_Type)(unsafe.Pointer(TK1_MMIO_UDS_BASE)) UART = (*UART_Type)(unsafe.Pointer(TK1_MMIO_UART_BASE)) TOUCH = (*TOUCH_Type)(unsafe.Pointer(TK1_MMIO_TOUCH_BASE)) TK1 = (*TK1_Type)(unsafe.Pointer(TK1_MMIO_TK1_BASE)) ) // Memory sections const ( TK1_ROM_BASE uintptr = 0x00000000 TK1_RAM_BASE uintptr = 0x40000000 TK1_MMIO_BASE uintptr = 0xc0000000 TK1_MMIO_TRNG_BASE uintptr = 0xc0000000 TK1_MMIO_TIMER_BASE uintptr = 0xc1000000 TK1_MMIO_UDS_BASE uintptr = 0xc2000000 TK1_MMIO_UART_BASE uintptr = 0xc3000000 TK1_MMIO_TOUCH_BASE uintptr = 0xc4000000 TK1_MMIO_FW_RAM_BASE uintptr = 0xd0000000 TK1_MMIO_TK1_BASE uintptr = 0xff000000 ) // Memory section sizes const ( TK1_RAM_SIZE uintptr = 0x20000 TK1_MMIO_SIZE uintptr = 0x3fffffff ) type TRNG_Type struct { _ [36]byte STATUS volatile.Register32 _ [88]byte ENTROPY volatile.Register32 } type TIMER_Type struct { _ [32]byte CTRL volatile.Register32 STATUS volatile.Register32 PRESCALER volatile.Register32 TIMER volatile.Register32 } type UDS_Type struct { _ [64]byte DATA [8]volatile.Register32 } type UART_Type struct { _ [128]byte RX_STATUS volatile.Register32 RX_DATA volatile.Register32 RX_BYTES volatile.Register32 _ [116]byte TX_STATUS volatile.Register32 TX_DATA volatile.Register32 } type TOUCH_Type struct { _ [36]byte STATUS volatile.Register32 } type TK1_Type struct { NAME0 volatile.Register32 NAME1 volatile.Register32 VERSION volatile.Register32 _ [16]byte SWITCH_APP volatile.Register32 _ [4]byte LED volatile.Register32 GPIO volatile.Register16 APP_ADDR volatile.Register32 APP_SIZE volatile.Register32 BLAKE2S volatile.Register32 _ [72]byte CDI_FIRST [8]volatile.Register32 _ [32]byte UDI_FIRST [2]volatile.Register32 _ [62]byte RAM_ADDR_RAND volatile.Register16 _ [2]byte RAM_DATA_RAND volatile.Register16 _ [126]byte CPU_MON_CTRL volatile.Register16 _ [2]byte CPU_MON_FIRST volatile.Register32 CPU_MON_LAST volatile.Register32 _ [60]byte SYSTEM_RESET volatile.Register16 _ [66]byte SPI_EN volatile.Register32 SPI_XFER volatile.Register32 SPI_DATA volatile.Register32 } const ( TK1_MMIO_TIMER_CTRL_START_BIT = 0 TK1_MMIO_TIMER_CTRL_STOP_BIT = 1 TK1_MMIO_TIMER_CTRL_START = 1 << TK1_MMIO_TIMER_CTRL_START_BIT TK1_MMIO_TIMER_CTRL_STOP = 1 << TK1_MMIO_TIMER_CTRL_STOP_BIT TK1_MMIO_TK1_LED_R_BIT = 2 TK1_MMIO_TK1_LED_G_BIT = 1 TK1_MMIO_TK1_LED_B_BIT = 0 TK1_MMIO_TK1_GPIO1_BIT = 0 TK1_MMIO_TK1_GPIO2_BIT = 1 TK1_MMIO_TK1_GPIO3_BIT = 2 TK1_MMIO_TK1_GPIO4_BIT = 3 ) ================================================ FILE: src/examples/adc/adc.go ================================================ package main import ( "machine" "time" ) // This example assumes that an analog sensor such as a rotary dial is connected to pin ADC0. // When the dial is turned past the midway point, the built-in LED will light up. func main() { machine.InitADC() led := machine.LED led.Configure(machine.PinConfig{Mode: machine.PinOutput}) sensor := machine.ADC{machine.ADC2} sensor.Configure(machine.ADCConfig{}) for { val := sensor.Get() if val < 0x8000 { led.Low() } else { led.High() } time.Sleep(time.Millisecond * 100) } } ================================================ FILE: src/examples/bench-goro/bench.go ================================================ package main import ( "machine" "runtime" "sync" "time" ) const N = 500000 const Ngoro = 4 func main() { start := time.Now() var wg sync.WaitGroup wg.Add(Ngoro) for i := 0; i < Ngoro; i++ { go adder(&wg, N) } wg.Wait() elapsed := time.Since(start) goroutineCtxSwitchOverhead := (elapsed / (Ngoro * N)).String() elapsedstr := elapsed.String() machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput}) for { println("bench:", elapsedstr, "goroutine ctx switch:", goroutineCtxSwitchOverhead) machine.LED.High() time.Sleep(elapsed) machine.LED.Low() time.Sleep(elapsed) } } func adder(wg *sync.WaitGroup, num int) { for i := 0; i < num; i++ { runtime.Gosched() } wg.Done() } ================================================ FILE: src/examples/blinkm/blinkm.go ================================================ // Connects to an BlinkM I2C RGB LED. // http://thingm.com/fileadmin/thingm/downloads/BlinkM_datasheet.pdf package main import ( "machine" "time" ) func main() { machine.I2C0.Configure(machine.I2CConfig{}) // Init BlinkM machine.I2C0.WriteRegister(0x09, 'o', nil) version := []byte{0, 0} machine.I2C0.ReadRegister(0x09, 'Z', version) println("Firmware version:", string(version[0]), string(version[1])) count := 0 for { switch count { case 0: // Crimson machine.I2C0.WriteRegister(0x09, 'n', []byte{0xdc, 0x14, 0x3c}) count = 1 case 1: // MediumPurple machine.I2C0.WriteRegister(0x09, 'n', []byte{0x93, 0x70, 0xdb}) count = 2 case 2: // MediumSeaGreen machine.I2C0.WriteRegister(0x09, 'n', []byte{0x3c, 0xb3, 0x71}) count = 0 } time.Sleep(100 * time.Millisecond) } } ================================================ FILE: src/examples/blinky1/blinky1.go ================================================ package main // This is the most minimal blinky example and should run almost everywhere. import ( "machine" "time" ) func main() { led := machine.LED led.Configure(machine.PinConfig{Mode: machine.PinOutput}) for { led.Low() time.Sleep(time.Millisecond * 500) led.High() time.Sleep(time.Millisecond * 500) } } ================================================ FILE: src/examples/blinky2/blinky2.go ================================================ package main // This blinky is a bit more advanced than blink1, with two goroutines running // at the same time and blinking a different LED. The delay of led2 is slightly // less than half of led1, which would be hard to do without some sort of // concurrency. import ( "machine" "time" ) func main() { go led1() led2() } func led1() { led := machine.LED1 led.Configure(machine.PinConfig{Mode: machine.PinOutput}) for { println("+") led.Low() time.Sleep(time.Millisecond * 1000) println("-") led.High() time.Sleep(time.Millisecond * 1000) } } func led2() { led := machine.LED2 led.Configure(machine.PinConfig{Mode: machine.PinOutput}) for { println(" +") led.Low() time.Sleep(time.Millisecond * 420) println(" -") led.High() time.Sleep(time.Millisecond * 420) } } ================================================ FILE: src/examples/button/button.go ================================================ package main import ( "machine" "time" ) const ( led = machine.LED button = machine.BUTTON ) func main() { led.Configure(machine.PinConfig{Mode: machine.PinOutput}) button.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) for { if button.Get() { led.Low() } else { led.High() } time.Sleep(time.Millisecond * 10) } } ================================================ FILE: src/examples/button2/button2.go ================================================ package main import ( "machine" "time" ) // This example assumes that you are using the pca10040 board func main() { led1 := machine.LED1 led1.Configure(machine.PinConfig{Mode: machine.PinOutput}) led2 := machine.LED2 led2.Configure(machine.PinConfig{Mode: machine.PinOutput}) led3 := machine.LED3 led3.Configure(machine.PinConfig{Mode: machine.PinOutput}) led4 := machine.LED4 led4.Configure(machine.PinConfig{Mode: machine.PinOutput}) button1 := machine.BUTTON1 button1.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) button2 := machine.BUTTON2 button2.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) button3 := machine.BUTTON3 button3.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) button4 := machine.BUTTON4 button4.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) for { led1.Set(button1.Get()) led2.Set(button2.Get()) led3.Set(button3.Get()) led4.Set(button4.Get()) time.Sleep(time.Millisecond * 10) } } ================================================ FILE: src/examples/can/feather-m4-can.go ================================================ //go:build feather_m4_can package main import ( "machine" ) func init() { // power on the CAN Transceiver // https://learn.adafruit.com/adafruit-feather-m4-can-express/pinouts#can-bus-3078990-8 boost_en := machine.BOOST_EN boost_en.Configure(machine.PinConfig{Mode: machine.PinOutput}) boost_en.High() } ================================================ FILE: src/examples/can/main.go ================================================ package main import ( "fmt" "machine" "time" ) func main() { can1 := machine.CAN1 can1.Configure(machine.CANConfig{ TransferRate: machine.CANTransferRate500kbps, TransferRateFD: machine.CANTransferRate1000kbps, Rx: machine.CAN1_RX, Tx: machine.CAN1_TX, Standby: machine.CAN1_STANDBY, }) can0 := machine.CAN0 can0.Configure(machine.CANConfig{ TransferRate: machine.CANTransferRate500kbps, TransferRateFD: machine.CANTransferRate1000kbps, Rx: machine.CAN0_RX, Tx: machine.CAN0_TX, Standby: machine.NoPin, }) rxMsg := machine.CANRxBufferElement{} for { can1.Tx(0x123, []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, false, false) can1.Tx(0x789, []byte{0x02, 0x24, 0x46, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, true, false) time.Sleep(time.Millisecond * 1000) sz0 := can0.RxFifoSize() if sz0 > 0 { fmt.Printf("CAN0 %d\r\n", sz0) for i := 0; i < sz0; i++ { can0.RxRaw(&rxMsg) fmt.Printf("-> %08X %X", rxMsg.ID, rxMsg.DLC) for j := byte(0); j < rxMsg.Length(); j++ { fmt.Printf(" %02X", rxMsg.DB[j]) } fmt.Printf("\r\n") } } sz1 := can1.RxFifoSize() if sz1 > 0 { fmt.Printf("CAN1 %d\r\n", sz1) for i := 0; i < sz1; i++ { can1.RxRaw(&rxMsg) fmt.Printf("-> %08X %X", rxMsg.ID, rxMsg.DLC) for j := byte(0); j < rxMsg.Length(); j++ { fmt.Printf(" %02X", rxMsg.DB[j]) } fmt.Printf("\r\n") } } } } ================================================ FILE: src/examples/caninterrupt/feather-m4-can.go ================================================ //go:build feather_m4_can package main import ( "machine" ) func init() { // power on the CAN Transceiver // https://learn.adafruit.com/adafruit-feather-m4-can-express/pinouts#can-bus-3078990-8 boost_en := machine.BOOST_EN boost_en.Configure(machine.PinConfig{Mode: machine.PinOutput}) boost_en.High() } ================================================ FILE: src/examples/caninterrupt/main.go ================================================ package main import ( "device/sam" "fmt" "machine" "time" ) var ( can0 = machine.CAN0 can1 = machine.CAN1 ch = make(chan canMsg, 10) ) type canMsg struct { bus byte can machine.CANRxBufferElement } func main() { go print(ch) can1.Configure(machine.CANConfig{ TransferRate: machine.CANTransferRate500kbps, TransferRateFD: machine.CANTransferRate1000kbps, Rx: machine.CAN1_RX, Tx: machine.CAN1_TX, Standby: machine.CAN1_STANDBY, }) // RF0NE : Rx FIFO 0 New Message Interrupt Enable can1.SetInterrupt(sam.CAN_IE_RF0NE, func(can *machine.CAN) { rxMsg := machine.CANRxBufferElement{} can.RxRaw(&rxMsg) msg := canMsg{ bus: 1, can: rxMsg, } select { case ch <- msg: } }) can0.Configure(machine.CANConfig{ TransferRate: machine.CANTransferRate500kbps, TransferRateFD: machine.CANTransferRate1000kbps, Rx: machine.CAN0_RX, Tx: machine.CAN0_TX, Standby: machine.NoPin, }) // RF0NE : Rx FIFO 0 New Message Interrupt Enable can0.SetInterrupt(sam.CAN_IE_RF0NE, func(can *machine.CAN) { rxMsg := machine.CANRxBufferElement{} can.RxRaw(&rxMsg) msg := canMsg{ bus: 1, can: rxMsg, } select { case ch <- msg: } }) for { can0.Tx(0x123, []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, false, false) time.Sleep(time.Millisecond * 500) can1.Tx(0x456, []byte{0xAA, 0xBB, 0xCC}, false, false) time.Sleep(time.Millisecond * 1000) } } func print(ch <-chan canMsg) { for { select { case m := <-ch: fmt.Printf("%d %03X %X ", m.bus, m.can.ID, m.can.DLC) for _, d := range m.can.DB[:m.can.Length()] { fmt.Printf("%02X ", d) } fmt.Printf("\r\n") } } } ================================================ FILE: src/examples/dac/circuitplay_express.go ================================================ //go:build circuitplay_express package main import ( "machine" ) func init() { enable := machine.PA30 enable.Configure(machine.PinConfig{Mode: machine.PinOutput}) enable.Set(true) } ================================================ FILE: src/examples/dac/dac.go ================================================ // Simplistic example using the DAC on the Circuit Playground Express. // // To actually use the DAC for producing complex waveforms or samples requires a DMA // timer-based playback mechanism which is beyond the scope of this example. package main import ( "machine" "time" ) func main() { speaker := machine.A0 speaker.Configure(machine.PinConfig{Mode: machine.PinOutput}) machine.DAC0.Configure(machine.DACConfig{}) data := []uint16{0xFFFF, 0x8000, 0x4000, 0x2000, 0x1000, 0x0000} for { for _, val := range data { play(val) time.Sleep(500 * time.Millisecond) } } } func play(val uint16) { for i := 0; i < 100; i++ { machine.DAC0.Set(val) time.Sleep(2 * time.Millisecond) machine.DAC0.Set(0) time.Sleep(2 * time.Millisecond) } } ================================================ FILE: src/examples/dac/pyportal.go ================================================ //go:build pyportal package main import ( "machine" ) func init() { enable := machine.SPK_SD enable.Configure(machine.PinConfig{Mode: machine.PinOutput}) enable.Set(true) } ================================================ FILE: src/examples/device-id/main.go ================================================ package main import ( "encoding/hex" "machine" "time" ) func main() { time.Sleep(2 * time.Second) // For efficiency, it's best to get the device ID once and cache it // (e.g. on RP2040 XIP flash and interrupts disabled for period of // retrieving the hardware ID from ROM chip) id := machine.DeviceID() for { println("Device ID:", hex.EncodeToString(id)) time.Sleep(1 * time.Second) } } ================================================ FILE: src/examples/echo/echo.go ================================================ // This is a echo console running on the device UART. // Connect using default baudrate for this hardware, 8-N-1 with your terminal program. package main import ( "machine" "time" ) var ( uart = machine.Serial ) func main() { // use default settings for UART uart.Configure(machine.UARTConfig{}) uart.Write([]byte("Echo console enabled. Type something then press enter:\r\n")) input := make([]byte, 64) i := 0 for { if uart.Buffered() > 0 { data, _ := uart.ReadByte() switch data { case 13: // return key uart.Write([]byte("\r\n")) uart.Write([]byte("You typed: ")) uart.Write(input[:i]) uart.Write([]byte("\r\n")) i = 0 default: // just echo the character uart.WriteByte(data) input[i] = data i++ } } time.Sleep(10 * time.Millisecond) } } ================================================ FILE: src/examples/echo2/echo2.go ================================================ // This is a echo console running on the os.Stdin and os.Stdout. // Stdin and os.Stdout are connected to machine.Serial in the baremetal target. // // Serial can be switched with the -serial option as follows // 1. tinygo flash -target wioterminal -serial usb examples/echo2 // 2. tinygo flash -target wioterminal -serial uart examples/echo2 // // This example will also work with standard Go. package main import ( "bufio" "fmt" "os" ) func main() { fmt.Printf("Echo console enabled. Type something then press enter:\r\n") scanner := bufio.NewScanner(os.Stdin) for { msg := "" fmt.Scanf("%s\n", &msg) fmt.Printf("You typed (scanf) : %s\r\n", msg) if scanner.Scan() { fmt.Printf("You typed (scanner) : %s\r\n", scanner.Text()) } } } ================================================ FILE: src/examples/empty/main.go ================================================ package main import "time" // This is used for smoke tests for chips without an associated board. func main() { for { time.Sleep(time.Second) } } ================================================ FILE: src/examples/flash/main.go ================================================ package main import ( "machine" "time" ) var ( err error message = "1234567887654321123456788765432112345678876543211234567887654321" + "1234567887654321123456788765432112345678876543211234567887654321" + "1234567887654321123456788765432112345678876543211234567887654321" + "1234567887654321123456788765432112345678876543211234567887654321" ) func main() { time.Sleep(3 * time.Second) // Print out general information println("Flash data start: ", machine.FlashDataStart()) println("Flash data end: ", machine.FlashDataEnd()) println("Flash data size, bytes:", machine.Flash.Size()) println("Flash write block size:", machine.Flash.WriteBlockSize()) println("Flash erase block size:", machine.Flash.EraseBlockSize()) println() original := make([]byte, len(message)) saved := make([]byte, len(message)) // Read flash contents on start (data shall survive power off) println("Reading original data from flash:") _, err = machine.Flash.ReadAt(original, 0) checkError(err) println(string(original)) // erase flash println("Erasing flash...") needed := int64(len(message)) / machine.Flash.EraseBlockSize() if needed == 0 { // have to erase at least 1 block needed = 1 } err := machine.Flash.EraseBlocks(0, needed) checkError(err) // Write the message to flash println("Writing new data to flash:") _, err = machine.Flash.WriteAt([]byte(message), 0) checkError(err) println(string(message)) // Read back flash contents after write (verify data is the same as written) println("Reading data back from flash: ") _, err = machine.Flash.ReadAt(saved, 0) checkError(err) if !equal(saved, []byte(message)) { println("data verify error") } println(string(saved)) println("Done.") } func equal(a, b []byte) bool { if len(a) != len(b) { return false } for i, v := range a { if v != b[i] { return false } } return true } func checkError(err error) { if err != nil { for { println(err.Error()) time.Sleep(time.Second) } } } ================================================ FILE: src/examples/gba-display/gba-display.go ================================================ package main // Draw a red square on the GameBoy Advance screen. import ( "image/color" "machine" ) var display = machine.Display func main() { display.Configure() for x := int16(30); x < 50; x++ { for y := int16(80); y < 100; y++ { display.SetPixel(x, y, color.RGBA{255, 0, 0, 255}) } } display.Display() } ================================================ FILE: src/examples/hello-wasm-unknown/main.go ================================================ // this is intended to be used as wasm32-unknown-unknown module. // to compile it, run: // tinygo build -size short -o hello-unknown.wasm -target wasm-unknown -gc=leaking -no-debug ./src/examples/hello-wasm-unknown/ package main // Smoke test: make sure the fmt package can be imported (even if it isn't // really useful for wasm-unknown). import _ "os" var x int32 //go:wasmimport hosted echo_i32 func echo(x int32) //go:export update func update() { x++ echo(x) } func main() { } ================================================ FILE: src/examples/hid-joystick/main.go ================================================ package main import ( "log" "machine/usb/hid/joystick" "math" "time" ) var js = joystick.Port() func main() { log.SetFlags(log.Lmicroseconds) ticker := time.NewTicker(10 * time.Millisecond) cnt := 0 const f = 3.0 for range ticker.C { t := float64(cnt) * 0.01 x := 32767 * math.Sin(2*math.Pi*f*t) button := cnt%100 > 50 js.SetButton(2, button) js.SetButton(3, !button) js.SetAxis(0, int(x)) js.SendState() cnt++ } } ================================================ FILE: src/examples/hid-keyboard/main.go ================================================ // to override the USB Manufacturer or Product names: // // tinygo flash -target circuitplay-express -ldflags="-X main.usbManufacturer='TinyGopher Labs' -X main.usbProduct='GopherKeyboard' -X main.usbSerial='XXXXX'" examples/hid-keyboard // // you can also override the VID/PID. however, only set this if you know what you are doing, // since changing it can make it difficult to reflash some devices. package main import ( "machine" "machine/usb" "machine/usb/hid/keyboard" "strconv" "time" ) var usbVID, usbPID string var usbManufacturer, usbProduct, usbSerial string func main() { button := machine.BUTTON button.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) kb := keyboard.Port() for { if !button.Get() { kb.Write([]byte("tinygo")) time.Sleep(200 * time.Millisecond) } } } func init() { if usbVID != "" { vid, _ := strconv.ParseUint(usbVID, 0, 16) usb.VendorID = uint16(vid) } if usbPID != "" { pid, _ := strconv.ParseUint(usbPID, 0, 16) usb.ProductID = uint16(pid) } if usbManufacturer != "" { usb.Manufacturer = usbManufacturer } if usbProduct != "" { usb.Product = usbProduct } if usbSerial != "" { usb.Serial = usbSerial } } ================================================ FILE: src/examples/hid-mouse/main.go ================================================ package main import ( "machine" "machine/usb/hid/mouse" "time" ) func main() { button := machine.BUTTON button.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) mouse := mouse.Port() for { if !button.Get() { for j := 0; j < 5; j++ { for i := 0; i < 100; i++ { mouse.Move(1, 0) time.Sleep(1 * time.Millisecond) } for i := 0; i < 100; i++ { mouse.Move(0, 1) time.Sleep(1 * time.Millisecond) } for i := 0; i < 100; i++ { mouse.Move(-1, -1) time.Sleep(1 * time.Millisecond) } } time.Sleep(100 * time.Millisecond) } } } ================================================ FILE: src/examples/i2c-target/main.go ================================================ // Example demonstrating I2C controller / target comms. // // To use this example, physically connect I2C0 and I2C1. // I2C0 will be used as the controller and I2C1 used as // the target. // // In this example, the target implements a simple memory // map, with the controller able to read and write the // memory. package main import ( "machine" "time" ) const ( targetAddress = 0x11 maxTxSize = 16 ) func main() { // Delay to enable USB monitor time to attach time.Sleep(3 * time.Second) // Controller uses default I2C pins and controller // mode is default err := controller.Configure(machine.I2CConfig{}) if err != nil { panic("failed to config I2C0 as controller") } // Target uses alternate pins and target mode is // explicit err = target.Configure(machine.I2CConfig{ Mode: machine.I2CModeTarget, SCL: TARGET_SCL, SDA: TARGET_SDA, }) if err != nil { panic("failed to config I2C1 as target") } // Put welcome message directly into target memory copy(mem[0:], []byte("Hello World!")) err = target.Listen(targetAddress) if err != nil { panic("failed to listen as I2C target") } // Start the target go targetMain() // Read welcome message from target over I2C buf := make([]byte, 12) err = controller.Tx(targetAddress, []byte{0}, buf) if err != nil { println("failed to read welcome message over I2C") panic(err) } println("message from target:", string(buf)) // Write (1,2,3) to the target starting at memory address 3 println("writing (1,2,3) @ 0x3") err = controller.Tx(targetAddress, []byte{3, 1, 2, 3}, nil) if err != nil { println("failed to write to I2C target") panic(err) } time.Sleep(100 * time.Millisecond) // Wait for target to process write println("mem:", mem[0], mem[1], mem[2], mem[3], mem[4], mem[5]) // Read memory address 4 from target, which should be the value 2 buf = make([]byte, 1) err = controller.Tx(targetAddress, []byte{4}, buf[:1]) if err != nil { println("failed to read from I2C target") panic(err) } if buf[0] != 2 { panic("read incorrect value from I2C target") } println("all done!") for { time.Sleep(1 * time.Second) } } // -- target --- var ( mem [256]byte ) // targetMain implements the 'main loop' for an I2C target func targetMain() { buf := make([]byte, maxTxSize) var ptr uint8 for { evt, n, err := target.WaitForEvent(buf) if err != nil { panic(err) } switch evt { case machine.I2CReceive: if n > 0 { ptr = buf[0] } for o := 1; o < n; o++ { mem[ptr] = buf[o] ptr++ } case machine.I2CRequest: target.Reply(mem[ptr:256]) case machine.I2CFinish: // nothing to do default: } } } ================================================ FILE: src/examples/i2c-target/main_feather_nrf52840.go ================================================ //go:build feather_nrf52840 package main import "machine" const ( TARGET_SCL = machine.A5 TARGET_SDA = machine.A4 ) var ( controller = machine.I2C0 target = machine.I2C1 ) ================================================ FILE: src/examples/i2c-target/main_feather_rp2040.go ================================================ //go:build rp2040 package main import "machine" const ( TARGET_SCL = machine.GPIO25 TARGET_SDA = machine.GPIO24 ) var ( controller = machine.I2C1 target = machine.I2C0 ) ================================================ FILE: src/examples/i2s/i2s.go ================================================ // Example using the i2s hardware interface on the Adafruit Circuit Playground Express // to read data from the onboard MEMS microphone. package main import ( "machine" ) func main() { machine.I2S0.Configure(machine.I2SConfig{ Mode: machine.I2SModePDM, ClockSource: machine.I2SClockSourceExternal, Stereo: true, }) data := make([]uint16, 64) for { // get the next group of samples machine.I2S0.ReadMono(data) println("data", data[0], data[1], data[2], data[4], "...") } } ================================================ FILE: src/examples/machinetest/machinetest.go ================================================ package main // This is the same as examples/serial, but it also imports the machine package. // It is used as a smoke test for the machine package (for boards that don't // have an on-board LED and therefore can't use examples/blinky1). import ( _ "machine" // smoke test for the machine package "time" ) func main() { for { println("hello world!") time.Sleep(time.Second) } } ================================================ FILE: src/examples/mcp3008/mcp3008.go ================================================ // Connects to an MCP3008 ADC via SPI. // Datasheet: https://www.microchip.com/wwwproducts/en/en010530 package main import ( "errors" "machine" "time" ) // cs is the pin used for Chip Select (CS). Change to whatever is in use on your board. const cs = machine.Pin(3) var ( tx []byte rx []byte val, result uint16 ) func main() { cs.Configure(machine.PinConfig{Mode: machine.PinOutput}) machine.SPI0.Configure(machine.SPIConfig{ Frequency: 4000000, Mode: 3}) tx = make([]byte, 3) rx = make([]byte, 3) for { val, _ = Read(0) println(val) time.Sleep(50 * time.Millisecond) } } // Read analog data from channel func Read(channel int) (uint16, error) { if channel < 0 || channel > 7 { return 0, errors.New("Invalid channel for read") } tx[0] = 0x01 tx[1] = byte(8+channel) << 4 tx[2] = 0x00 cs.Low() machine.SPI0.Tx(tx, rx) result = uint16((rx[1]&0x3))<<8 + uint16(rx[2]) cs.High() return result, nil } ================================================ FILE: src/examples/memstats/memstats.go ================================================ package main import ( "math/rand" "runtime" "time" ) func main() { ms := runtime.MemStats{} for { escapesToHeap() runtime.ReadMemStats(&ms) println("Heap before GC. Used: ", ms.HeapInuse, " Free: ", ms.HeapIdle, " Meta: ", ms.GCSys) runtime.GC() runtime.ReadMemStats(&ms) println("Heap after GC. Used: ", ms.HeapInuse, " Free: ", ms.HeapIdle, " Meta: ", ms.GCSys) time.Sleep(5 * time.Second) } } func escapesToHeap() { n := rand.Intn(100) println("Doing ", n, " iterations") for i := 0; i < n; i++ { s := make([]byte, i) _ = append(s, 42) } } ================================================ FILE: src/examples/microbit-blink/microbit-blink.go ================================================ // blink program for the BBC micro:bit package main import ( "machine" "time" ) // The LED matrix in the micro:bit is a multiplexed display: https://en.wikipedia.org/wiki/Multiplexed_display // Driver for easier control: https://github.com/tinygo-org/drivers/tree/master/microbitmatrix func main() { ledrow := machine.LED_ROW_1 ledrow.Configure(machine.PinConfig{Mode: machine.PinOutput}) ledcol := machine.LED_COL_1 ledcol.Configure(machine.PinConfig{Mode: machine.PinOutput}) ledcol.Low() for { ledrow.Low() time.Sleep(time.Millisecond * 500) ledrow.High() time.Sleep(time.Millisecond * 500) } } ================================================ FILE: src/examples/pdm/pdm.go ================================================ package main import ( "fmt" "machine" ) var ( audio = make([]int16, 16) pdm = machine.PDM{} ) func main() { machine.BUTTONA.Configure(machine.PinConfig{Mode: machine.PinInputPulldown}) err := pdm.Configure(machine.PDMConfig{CLK: machine.PDM_CLK_PIN, DIN: machine.PDM_DIN_PIN}) if err != nil { panic(fmt.Sprintf("Failed to configure PDM:%v", err)) } for { if machine.BUTTONA.Get() { println("Recording new audio clip into memory") pdm.Read(audio) println(fmt.Sprintf("Recorded new audio clip into memory: %v", audio)) } } } ================================================ FILE: src/examples/pininterrupt/arduino.go ================================================ //go:build arduino package main import "machine" const ( button = machine.D2 buttonMode = machine.PinInputPullup buttonPinChange = machine.PinRising ) ================================================ FILE: src/examples/pininterrupt/circuitplay-express.go ================================================ //go:build circuitplay_express package main import "machine" const ( button = machine.BUTTON buttonMode = machine.PinInputPulldown buttonPinChange = machine.PinFalling ) ================================================ FILE: src/examples/pininterrupt/pca10040.go ================================================ //go:build pca10040 package main import "machine" const ( button = machine.BUTTON buttonMode = machine.PinInputPullup buttonPinChange = machine.PinRising ) ================================================ FILE: src/examples/pininterrupt/pininterrupt.go ================================================ package main // This example demonstrates how to use pin change interrupts. // // This is only an example and should not be copied directly in any serious // circuit, because it only naively implements an important feature: debouncing. // See: https://en.wikipedia.org/wiki/Switch#Contact_bounce import ( "machine" "time" ) const ( led = machine.LED ) var lastPress time.Time func main() { // Configure the LED, defaulting to on (usually setting the pin to low will // turn the LED on). led.Configure(machine.PinConfig{Mode: machine.PinOutput}) led.Low() // Make sure the pin is configured as a pullup to avoid floating inputs. // Pullup works for most buttons, as most buttons short to ground when // pressed. button.Configure(machine.PinConfig{Mode: buttonMode}) // Set an interrupt on this pin. err := button.SetInterrupt(buttonPinChange, func(machine.Pin) { // Ignore events that are too close to the last registered press (debouncing) if time.Since(lastPress) < 100*time.Millisecond { return } lastPress = time.Now() led.Set(!led.Get()) }) if err != nil { println("could not configure pin interrupt:", err.Error()) } // Make sure the program won't exit. for { time.Sleep(time.Hour) } } ================================================ FILE: src/examples/pininterrupt/stm32.go ================================================ //go:build stm32 package main import "machine" const ( button = machine.BUTTON buttonMode = machine.PinInputPulldown buttonPinChange = machine.PinRising | machine.PinFalling ) ================================================ FILE: src/examples/pininterrupt/wioterminal.go ================================================ //go:build wioterminal package main import "machine" const ( button = machine.BUTTON buttonMode = machine.PinInput buttonPinChange = machine.PinFalling ) ================================================ FILE: src/examples/pwm/arduino-mega1280.go ================================================ //go:build arduino_mega1280 package main import "machine" var ( // Configuration on an Arduino Uno. pwm = machine.Timer3 pinA = machine.PH3 // pin 6 on the Mega pinB = machine.PH4 // pin 7 on the Mega ) ================================================ FILE: src/examples/pwm/arduino.go ================================================ //go:build arduino package main import "machine" var ( // Configuration on an Arduino Uno. pwm = machine.Timer2 pinA = machine.PB3 // pin 11 on the Uno pinB = machine.PD3 // pin 3 on the Uno ) ================================================ FILE: src/examples/pwm/bluepill.go ================================================ //go:build bluepill package main import "machine" var ( pwm = &machine.TIM2 pinA = machine.PA0 pinB = machine.PA1 ) ================================================ FILE: src/examples/pwm/feather-m4.go ================================================ //go:build feather_m4 package main import "machine" var ( pwm = machine.TCC0 pinA = machine.D12 pinB = machine.D13 ) ================================================ FILE: src/examples/pwm/itsybitsy-m0.go ================================================ //go:build itsybitsy_m0 package main import "machine" var ( pwm = machine.TCC0 pinA = machine.D3 pinB = machine.D4 ) ================================================ FILE: src/examples/pwm/itsybitsy-m4.go ================================================ //go:build itsybitsy_m4 package main import "machine" var ( pwm = machine.TCC0 pinA = machine.D12 pinB = machine.D13 ) ================================================ FILE: src/examples/pwm/nucleo-f722ze.go ================================================ //go:build stm32f7 package main import "machine" var ( pwm = &machine.TIM1 pinA = machine.PA8 pinB = machine.PA9 ) ================================================ FILE: src/examples/pwm/nucleo-l031k6.go ================================================ //go:build stm32l0 package main import "machine" var ( pwm = &machine.TIM2 pinA = machine.PA0 pinB = machine.PB3 ) ================================================ FILE: src/examples/pwm/nucleo-l432kc.go ================================================ //go:build stm32l4 package main import "machine" var ( pwm = &machine.TIM2 pinA = machine.PA0 pinB = machine.PB3 ) ================================================ FILE: src/examples/pwm/nucleo-l552ze.go ================================================ //go:build stm32l5 package main import "machine" var ( pwm = &machine.TIM1 pinA = machine.PA8 pinB = machine.PA9 ) ================================================ FILE: src/examples/pwm/pico.go ================================================ //go:build pico package main import "machine" var ( pwm = machine.PWM4 // Pin 25 (LED on pico) corresponds to PWM4. pinA = machine.LED pinB = machine.GPIO24 ) ================================================ FILE: src/examples/pwm/pwm.go ================================================ package main // This example demonstrates some features of the PWM support. import ( "machine" "time" ) const delayBetweenPeriods = time.Second * 5 func main() { // Delay a bit on startup to easily catch the first messages. time.Sleep(time.Second * 2) // Configure the PWM with the given period. err := pwm.Configure(machine.PWMConfig{ Period: 16384e3, // 16.384ms }) if err != nil { println("failed to configure PWM") return } // The top value is the highest value that can be passed to PWMChannel.Set. // It is usually an even number. println("top:", pwm.Top()) // Configure the two channels we'll use as outputs. channelA, err := pwm.Channel(pinA) if err != nil { println("failed to configure channel A") return } channelB, err := pwm.Channel(pinB) if err != nil { println("failed to configure channel B") return } // Invert one of the channels to demonstrate output polarity. pwm.SetInverting(channelB, true) // Test out various frequencies below, including some edge cases. println("running at 0% duty cycle") pwm.Set(channelA, 0) pwm.Set(channelB, 0) time.Sleep(delayBetweenPeriods) println("running at 1") pwm.Set(channelA, 1) pwm.Set(channelB, 1) time.Sleep(delayBetweenPeriods) println("running at 25% duty cycle") pwm.Set(channelA, pwm.Top()/4) pwm.Set(channelB, pwm.Top()/4) time.Sleep(delayBetweenPeriods) println("running at top-1") pwm.Set(channelA, pwm.Top()-1) pwm.Set(channelB, pwm.Top()-1) time.Sleep(delayBetweenPeriods) println("running at 100% duty cycle") pwm.Set(channelA, pwm.Top()) pwm.Set(channelB, pwm.Top()) time.Sleep(delayBetweenPeriods) for { time.Sleep(time.Second) } } ================================================ FILE: src/examples/pwm/stm32f4disco.go ================================================ //go:build stm32f4disco package main import "machine" var ( // These pins correspond to LEDs on the discovery // board pwm = &machine.TIM4 pinA = machine.PD12 pinB = machine.PD13 ) ================================================ FILE: src/examples/ram-func/main.go ================================================ package main // This example demonstrates how to use go:section to place code into RAM for // execution. The code is present in flash in the `.data` region and copied // into the correct place in RAM early in startup sequence (at the same time // as non-zero global variables are initialized). // // This example should work on any ARM Cortex MCU. // // For Go code use the pragma "//go:section", for cgo use the "section" and // "noinline" attributes. The `.ramfuncs` section is explicitly placed into // the `.data` region by the linker script. // // Running the example should print out the program counter from the functions // below. The program counters should be in different memory regions. // // On RP2040, for example, the output is something like this: // // Go in RAM: 0x20000DB4 // Go in flash: 0x10007610 // cgo in RAM: 0x20000DB8 // cgo in flash: 0x10002C26 // // This can be confirmed using `objdump -t xxx.elf | grep main | sort`: // // 00000000 l df *ABS* 00000000 main // 1000760d l F .text 00000004 main.in_flash // 10007611 l F .text 0000000c __Thumbv6MABSLongThunk_main.in_ram // 1000761d l F .text 0000000c __Thumbv6MABSLongThunk__Cgo_static_eea7585d7291176ad3bb_main_c_in_ram // 1000bdb5 l O .text 00000013 main$string // 1000bdc8 l O .text 00000013 main$string.1 // 1000bddb l O .text 00000013 main$string.2 // 1000bdee l O .text 00000013 main$string.3 // 20000db1 l F .data 00000004 main.in_ram // 20000db5 l F .data 00000004 _Cgo_static_eea7585d7291176ad3bb_main_c_in_ram // import ( "device" "fmt" "time" _ "unsafe" // unsafe is required for "//go:section" ) /* #define ram_func __attribute__((section(".ramfuncs"),noinline)) static ram_func void* main_c_in_ram() { void* p = 0; asm( "MOV %0, PC" : "=r"(p) ); return p; } static void* main_c_in_flash() { void* p = 0; asm( "MOV %0, PC" : "=r"(p) ); return p; } */ import "C" func main() { time.Sleep(2 * time.Second) fmt.Printf("Go in RAM: 0x%X\n", in_ram()) fmt.Printf("Go in flash: 0x%X\n", in_flash()) fmt.Printf("cgo in RAM: 0x%X\n", C.main_c_in_ram()) fmt.Printf("cgo in flash: 0x%X\n", C.main_c_in_flash()) } //go:section .ramfuncs func in_ram() uintptr { return device.AsmFull("MOV {}, PC", nil) } // 'go:noinline' used here to prevent function being 'inlined' into main() // so it appears in objdump output. In normal use, go:inline is not // required for functions running from flash (flash is the default). // //go:noinline func in_flash() uintptr { return device.AsmFull("MOV {}, PC", nil) } ================================================ FILE: src/examples/rand/main.go ================================================ package main import ( "crypto/rand" "encoding/hex" "time" ) func main() { var result [32]byte for { rand.Read(result[:]) encodedString := hex.EncodeToString(result[:]) println(encodedString) time.Sleep(time.Second) } } ================================================ FILE: src/examples/rtcinterrupt/rtcinterrupt.go ================================================ //go:build rp2040 package main // This example demonstrates scheduling a delayed interrupt by real time clock. // // An interrupt may execute user callback function or used for its side effects // like waking up from sleep or dormant states. // // The interrupt can be configured to repeat. // // There is no separate method to disable interrupt, use 0 delay for that. // // Unfortunately, it is not possible to use time.Duration to work with RTC directly, // that would introduce a circular dependency between "machine" and "time" packages. import ( "fmt" "machine" "time" ) func main() { // Schedule and enable recurring interrupt. // The callback function is executed in the context of an interrupt handler, // so regular restrictions for this sort of code apply: no blocking, no memory allocation, etc. // Please check the online documentation for the details about interrupts: // https://tinygo.org/docs/concepts/compiler-internals/interrupts/ delay := time.Minute + 12*time.Second machine.RTC.SetInterrupt(uint32(delay.Seconds()), true, func() { println("Peekaboo!") }) for { fmt.Printf("%v\r\n", time.Now().Format(time.RFC3339)) time.Sleep(1 * time.Second) } } ================================================ FILE: src/examples/serial/serial.go ================================================ package main import "time" func main() { for { println("hello world!") time.Sleep(time.Second) } } ================================================ FILE: src/examples/systick/README.md ================================================ # TinyGo ARM SysTick example This example uses the ARM System Timer to blink an LED. The timer fires an interrupt 10 times per second. The interrupt handler toggles the LED on and off. Many ARM-based chips have this timer feature. If you run the example and the LED blinks, then you have one. The System Timer runs from a cycle counter. The more cycles, the slower the LED will blink. This counter is 24 bits wide, which places an upper bound on the number of cycles, and the slowness of the blinking. ================================================ FILE: src/examples/systick/systick.go ================================================ package main import ( "device/arm" "machine" ) var timerCh = make(chan struct{}, 1) func main() { machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput}) // timer fires 10 times per second arm.SetupSystemTimer(machine.CPUFrequency() / 10) for { machine.LED.Low() <-timerCh machine.LED.High() <-timerCh } } //export SysTick_Handler func timer_isr() { select { case timerCh <- struct{}{}: default: // The consumer is running behind. } } ================================================ FILE: src/examples/temp/temp.go ================================================ // Read the internal temperature sensor of the chip. package main import ( "fmt" "machine" "time" ) type celsius float32 func (c celsius) String() string { return fmt.Sprintf("%4.1f℃", c) } func main() { for { temp := celsius(float32(machine.ReadTemperature()) / 1000) println("temperature:", temp.String()) time.Sleep(time.Second) } } ================================================ FILE: src/examples/time-offset/time-offset.go ================================================ package main // This example demonstrates how to set the system time. // // Usually, boards don't keep time on power cycles and restarts, it resets to Unix epoch. // The system time can be set by calling runtime.AdjustTimeOffset(). // // Possible time sources: an external RTC clock or network (WiFi access point or NTP server) import ( "fmt" "runtime" "time" ) const myTime = "2006-01-02T15:04:05Z" // this is an example time you source somewhere func main() { // measure how many nanoseconds the internal clock is behind timeOfMeasurement := time.Now() actualTime, _ := time.Parse(time.RFC3339, myTime) offset := actualTime.Sub(timeOfMeasurement) // adjust internal clock by adding the offset to the internal clock runtime.AdjustTimeOffset(int64(offset)) for { fmt.Printf("%v\r\n", time.Now().Format(time.RFC3339)) time.Sleep(5 * time.Second) } } ================================================ FILE: src/examples/uart/uart.go ================================================ // This reads from UART1 and outputs to default serial, usually UART0 or USB. // Example of how to work with UARTs other than the default. package main import ( "machine" "time" ) var ( uart = machine.UART1 tx = machine.UART1_TX_PIN rx = machine.UART1_RX_PIN ) func main() { uart.Configure(machine.UARTConfig{TX: tx, RX: rx}) for { if uart.Buffered() > 0 { data, _ := uart.ReadByte() print(string(data)) } time.Sleep(10 * time.Millisecond) } } ================================================ FILE: src/examples/usb-midi/main.go ================================================ package main import ( "machine" "machine/usb/adc/midi" "time" ) // Try it easily by opening the following site in Chrome. // https://www.onlinemusictools.com/kb/ const ( cable = 0 channel = 1 velocity = 0x40 ) func main() { led := machine.LED led.Configure(machine.PinConfig{Mode: machine.PinOutput}) button := machine.BUTTON button.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) m := midi.Port() m.SetRxHandler(func(b []byte) { // blink when we receive a MIDI message led.Set(!led.Get()) }) m.SetTxHandler(func() { // blink when we send a MIDI message led.Set(!led.Get()) }) prev := true chords := []struct { name string notes []midi.Note }{ {name: "C ", notes: []midi.Note{midi.C4, midi.E4, midi.G4}}, {name: "G ", notes: []midi.Note{midi.G3, midi.B3, midi.D4}}, {name: "Am", notes: []midi.Note{midi.A3, midi.C4, midi.E4}}, {name: "F ", notes: []midi.Note{midi.F3, midi.A3, midi.C4}}, } index := 0 for { current := button.Get() if prev != current { if current { for _, note := range chords[index].notes { m.NoteOff(cable, channel, note, velocity) } index = (index + 1) % len(chords) } else { for _, note := range chords[index].notes { m.NoteOn(cable, channel, note, velocity) } } prev = current } time.Sleep(100 * time.Millisecond) } } ================================================ FILE: src/examples/usb-storage/main.go ================================================ package main import ( "machine" "machine/usb/msc" "time" ) func main() { msc.Port(machine.Flash) for { time.Sleep(2 * time.Second) } } ================================================ FILE: src/examples/wasm/.gitignore ================================================ html/* ================================================ FILE: src/examples/wasm/GNUmakefile ================================================ invoke: clean wasm_exec tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./invoke/wasm.go cp ./invoke/wasm.js ./html/ cp ./invoke/index.html ./html/ export: clean wasm_exec tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./export/wasm.go cp ./export/wasm.js ./html/ cp ./export/index.html ./html/ callback: clean wasm_exec tinygo build -o ./html/wasm.wasm -target wasm ./callback/wasm.go cp ./callback/wasm.js ./html/ cp ./callback/index.html ./html/ slices: clean wasm_exec tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./slices/wasm.go cp ./slices/wasm.js ./html/ cp ./slices/index.html ./html/ main: clean wasm_exec tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./main/main.go cp ./main/index.html ./html/ wasm_exec: cp `tinygo env TINYGOROOT`/targets/wasm_exec.js ./html/ clean: rm -rf ./html mkdir ./html ================================================ FILE: src/examples/wasm/README.md ================================================ # TinyGo WebAssembly examples The examples here show two different ways of using WebAssembly with TinyGo: 1. Defining and exporting functions via the `//export ` directive. See [the export folder](./export) for an example of this. Additionally, the Wasm module (which has a default value of `env`) can be specified using `//go:wasm-module `. 1. Defining and executing a `func main()`. This is similar to how the Go standard library implementation works. See [the main folder](./main) for an example of this. ## Building Build using the `tinygo` compiler: ```bash $ tinygo build -o ./wasm.wasm -target wasm ./main/main.go ``` This creates a `wasm.wasm` file, which we can load in JavaScript and execute in a browser. Next, choose which example you want to use: * [callback](callback): Defines and configures callbacks in Wasm. * [export](export): Defines callbacks in Wasm, but configures them in JavaScript. * [invoke](invoke): Invokes a function defined in JavaScript from Wasm. * [main](main): Prints a message to the JavaScript console from Wasm. * [slices](slices): Splits an Array defined in JavaScript from Wasm. Let's say you chose [main](main), you'd build it like so: ```bash $ make main ``` ## Running Start the local web server: ```bash $ go run server.go Serving ./html on http://localhost:8080 ``` Use your web browser to visit http://localhost:8080. * Tip: Open the browser development tools (e.g. Right-click, Inspect in FireFox) to see console output. ## How it works Execution of the contents require a few JavaScript helper functions which are called from WebAssembly. We have defined these in [wasm_exec.js](../../../targets/wasm_exec.js). It is based on `$GOROOT/misc/wasm/wasm_exec.js` from the standard library, but is slightly different. Ensure you are using the same version of `wasm_exec.js` as the version of `tinygo` you are using to compile. The general steps required to run the WebAssembly file in the browser includes loading it into JavaScript with `WebAssembly.instantiateStreaming`, or `WebAssembly.instantiate` in some browsers: ```js const go = new Go(); // Defined in wasm_exec.js const WASM_URL = 'wasm.wasm'; var wasm; if ('instantiateStreaming' in WebAssembly) { WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) { wasm = obj.instance; go.run(wasm); }) } else { fetch(WASM_URL).then(resp => resp.arrayBuffer() ).then(bytes => WebAssembly.instantiate(bytes, go.importObject).then(function (obj) { wasm = obj.instance; go.run(wasm); }) ) } ``` If you have used explicit exports, you can call them by invoking them under the `wasm.exports` namespace. See the [`export`](./export/wasm.js) directory for an example of this. In addition to the JavaScript, it is important the wasm file is served with the [`Content-Type`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) header set to `application/wasm`. Without it, most browsers won't run it. ```go package main import ( "log" "net/http" "strings" ) const dir = "./html" func main() { fs := http.FileServer(http.Dir(dir)) log.Print("Serving " + dir + " on http://localhost:8080") http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { resp.Header().Add("Cache-Control", "no-cache") if strings.HasSuffix(req.URL.Path, ".wasm") { resp.Header().Set("content-type", "application/wasm") } fs.ServeHTTP(resp, req) }))} ``` This simple server serves anything inside the `./html` directory on port `8080`, setting any `*.wasm` files `Content-Type` header appropriately. For development purposes (**only!**), it also sets the `Cache-Control` header so your browser doesn't cache the files. This is useful while developing, to ensure your browser displays the newest wasm when you recompile. In a production environment you **probably wouldn't** want to set the `Cache-Control` header like this. Caching is generally beneficial for end users. Further information on the `Cache-Control` header can be found here: * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control ================================================ FILE: src/examples/wasm/callback/index.html ================================================ Go WebAssembly

WebAssembly

Add two numbers, using WebAssembly:

+ = ================================================ FILE: src/examples/wasm/callback/wasm.go ================================================ package main import ( "strconv" "syscall/js" ) var a, b int func main() { wait := make(chan struct{}, 0) document := js.Global().Get("document") document.Call("getElementById", "a").Set("oninput", updater(&a)) document.Call("getElementById", "b").Set("oninput", updater(&b)) update() <-wait } func updater(n *int) js.Func { return js.FuncOf(func(this js.Value, args []js.Value) interface{} { *n, _ = strconv.Atoi(this.Get("value").String()) update() return nil }) } func update() { js.Global().Get("document").Call("getElementById", "result").Set("value", a+b) } ================================================ FILE: src/examples/wasm/callback/wasm.js ================================================ 'use strict'; const WASM_URL = 'wasm.wasm'; var wasm; function init() { const go = new Go(); if ('instantiateStreaming' in WebAssembly) { WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) { wasm = obj.instance; go.run(wasm); }) } else { fetch(WASM_URL).then(resp => resp.arrayBuffer() ).then(bytes => WebAssembly.instantiate(bytes, go.importObject).then(function (obj) { wasm = obj.instance; go.run(wasm); }) ) } } init(); ================================================ FILE: src/examples/wasm/export/index.html ================================================ Go WebAssembly

WebAssembly

Add two numbers, using WebAssembly:

+ = ================================================ FILE: src/examples/wasm/export/wasm.go ================================================ package main import ( "strconv" "syscall/js" ) func main() { } //export add func add(a, b int) int { return a + b } //export update func update() { document := js.Global().Get("document") aStr := document.Call("getElementById", "a").Get("value").String() bStr := document.Call("getElementById", "b").Get("value").String() a, _ := strconv.Atoi(aStr) b, _ := strconv.Atoi(bStr) result := add(a, b) document.Call("getElementById", "result").Set("value", result) } ================================================ FILE: src/examples/wasm/export/wasm.js ================================================ 'use strict'; const WASM_URL = 'wasm.wasm'; var wasm; function updateResult() { wasm.exports.update(); } function init() { document.querySelector('#a').oninput = updateResult; document.querySelector('#b').oninput = updateResult; const go = new Go(); if ('instantiateStreaming' in WebAssembly) { WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) { wasm = obj.instance; go.run(wasm); updateResult(); }) } else { fetch(WASM_URL).then(resp => resp.arrayBuffer() ).then(bytes => WebAssembly.instantiate(bytes, go.importObject).then(function (obj) { wasm = obj.instance; go.run(wasm); updateResult(); }) ) } } init(); ================================================ FILE: src/examples/wasm/invoke/index.html ================================================ Go WebAssembly

WebAssembly

Edit on either side to mimic values, using WebAssembly:

== ================================================ FILE: src/examples/wasm/invoke/wasm.go ================================================ package main import ( "syscall/js" ) func runner(this js.Value, args []js.Value) interface{} { return args[0].Invoke(args[1]).String() } func main() { wait := make(chan struct{}, 0) js.Global().Set("runner", js.FuncOf(runner)) <-wait } ================================================ FILE: src/examples/wasm/invoke/wasm.js ================================================ 'use strict'; const WASM_URL = 'wasm.wasm'; var wasm; function updateRight() { const value = document.getElementById("a").value; window.runner(function (value) { document.getElementById("b").value = value; }, value); } function updateLeft() { const value = document.getElementById("b").value; window.runner(function (value) { document.getElementById("a").value = value; }, value); } function init() { document.querySelector('#a').oninput = updateRight; document.querySelector('#b').oninput = updateLeft; const go = new Go(); if ('instantiateStreaming' in WebAssembly) { WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) { wasm = obj.instance; go.run(wasm); }) } else { fetch(WASM_URL).then(resp => resp.arrayBuffer() ).then(bytes => WebAssembly.instantiate(bytes, go.importObject).then(function (obj) { wasm = obj.instance; go.run(wasm); }) ) } } init(); ================================================ FILE: src/examples/wasm/main/README.md ================================================ # WebAssembly main execution example A simple hello world that prints to the browser console. ## License Note that `index.html` is copied almost verbatim from the Go 1.12 source at `$GOROOT/misc/wasm/wasm_exec.html`. Its license applies to this file. ================================================ FILE: src/examples/wasm/main/index.html ================================================ Go wasm ================================================ FILE: src/examples/wasm/main/main.go ================================================ package main func main() { println("Hello world!") } ================================================ FILE: src/examples/wasm/server.go ================================================ package main import ( "log" "net/http" "strings" ) const dir = "./html" func main() { fs := http.FileServer(http.Dir(dir)) log.Print("Serving " + dir + " on http://localhost:8080") http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { resp.Header().Add("Cache-Control", "no-cache") if strings.HasSuffix(req.URL.Path, ".wasm") { resp.Header().Set("content-type", "application/wasm") } fs.ServeHTTP(resp, req) })) } ================================================ FILE: src/examples/wasm/slices/index.html ================================================ Go WebAssembly

WebAssembly

type values separated by comma, using WebAssembly:

==
================================================ FILE: src/examples/wasm/slices/wasm.go ================================================ package main import ( "strings" "syscall/js" ) func splitter(this js.Value, args []js.Value) interface{} { values := strings.Split(args[0].String(), ",") result := make([]interface{}, 0) for _, each := range values { result = append(result, each) } return js.ValueOf(result) } func main() { wait := make(chan struct{}, 0) js.Global().Set("splitter", js.FuncOf(splitter)) <-wait } ================================================ FILE: src/examples/wasm/slices/wasm.js ================================================ 'use strict'; const WASM_URL = 'wasm.wasm'; var wasm; function update() { const value = document.getElementById("a").value; document.getElementById("b").innerHTML = JSON.stringify(window.splitter(value)); } function init() { document.querySelector('#a').oninput = update; const go = new Go(); if ('instantiateStreaming' in WebAssembly) { WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) { wasm = obj.instance; go.run(wasm); }) } else { fetch(WASM_URL).then(resp => resp.arrayBuffer() ).then(bytes => WebAssembly.instantiate(bytes, go.importObject).then(function (obj) { wasm = obj.instance; go.run(wasm); }) ) } } init(); ================================================ FILE: src/examples/watchdog/main.go ================================================ package main import ( "fmt" "machine" "time" ) func main() { //sleep for 2 secs for console time.Sleep(2 * time.Second) config := machine.WatchdogConfig{ TimeoutMillis: 1000, } println("configuring watchdog for max 1 second updates") machine.Watchdog.Configure(config) // From this point the watchdog is running and Update must be // called periodically, per the config machine.Watchdog.Start() // This loop should complete because watchdog update is called // every 100ms. start := time.Now() println("updating watchdog for 3 seconds") for i := 0; i < 30; i++ { time.Sleep(100 * time.Millisecond) machine.Watchdog.Update() fmt.Printf("alive @ %v\n", time.Now().Sub(start)) } // This loop should cause a watchdog reset after 1s since // there is no update call. start = time.Now() println("entering tight loop") for { time.Sleep(100 * time.Millisecond) fmt.Printf("alive @ %v\n", time.Now().Sub(start)) } } ================================================ FILE: src/internal/abi/abi.go ================================================ // Package abi exposes low-level details of the Go compiler/runtime package abi ================================================ FILE: src/internal/abi/escape.go ================================================ package abi import "unsafe" // Tell the compiler the given pointer doesn't escape. // The compiler knows about this function and will give the nocapture parameter // attribute. func NoEscape(p unsafe.Pointer) unsafe.Pointer { return p } func Escape[T any](x T) T { // This function is either implemented in the compiler, or left undefined // for some variation of T. The body of this function should not be compiled // as-is. panic("internal/abi.Escape: unreachable (implemented in the compiler)") } ================================================ FILE: src/internal/abi/funcpc.go ================================================ package abi // These two signatures are present to satisfy the expectation of some programs // (in particular internal/syscall/unix on MacOS). They do not currently have an // implementation, in part because TinyGo doesn't use ABI0 or ABIInternal (it // uses a C-like calling convention). // Calls to FuncPCABI0 however are treated specially by the compiler when // compiling for MacOS. func FuncPCABI0(f interface{}) uintptr func FuncPCABIInternal(f interface{}) uintptr ================================================ FILE: src/internal/abi/type.go ================================================ package abi type Type struct { // Intentionally left empty. TinyGo uses a different way to represent types, // so this is unimplementable. The type definition here is purely for // compatibility. } ================================================ FILE: src/internal/binary/binary.go ================================================ // Package binary is a lightweight replacement package for encoding/binary. package binary // This file contains small helper functions for working with binary data. var LittleEndian = littleEndian{} type littleEndian struct{} // Encode data like encoding/binary.LittleEndian.Uint16. func (littleEndian) Uint16(b []byte) uint16 { return uint16(b[0]) | uint16(b[1])<<8 } // Store data like binary.LittleEndian.PutUint16. func (littleEndian) PutUint16(b []byte, v uint16) { b[0] = byte(v) b[1] = byte(v >> 8) } // Append data like binary.LittleEndian.AppendUint16. func (littleEndian) AppendUint16(b []byte, v uint16) []byte { return append(b, byte(v), byte(v>>8), ) } // Encode data like encoding/binary.LittleEndian.Uint32. func (littleEndian) Uint32(b []byte) uint32 { return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 } func (littleEndian) Uint64(b []byte) uint64 { return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 } ================================================ FILE: src/internal/bytealg/bytealg.go ================================================ package bytealg // Some code in this file has been copied from the Go source code, and has // copyright of their original authors: // // Copyright 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // // This is indicated specifically in the file. const ( // Index can search any valid length of string. MaxLen = int(-1) >> 31 MaxBruteForce = MaxLen ) // Compare two byte slices. // Returns -1 if the first differing byte is lower in a, or 1 if the first differing byte is greater in b. // If the byte slices are equal, returns 0. // If the lengths are different and there are no differing bytes, compares based on length. func Compare(a, b []byte) int { // Compare for differing bytes. for i := 0; i < len(a) && i < len(b); i++ { switch { case a[i] < b[i]: return -1 case a[i] > b[i]: return 1 } } // Compare lengths. switch { case len(a) > len(b): return 1 case len(a) < len(b): return -1 default: return 0 } } // This function was copied from the Go 1.23 source tree (with runtime_cmpstring // manually inlined). func CompareString(a, b string) int { l := len(a) if len(b) < l { l = len(b) } for i := 0; i < l; i++ { c1, c2 := a[i], b[i] if c1 < c2 { return -1 } if c1 > c2 { return +1 } } if len(a) < len(b) { return -1 } if len(a) > len(b) { return +1 } return 0 } // Count the number of instances of a byte in a slice. func Count(b []byte, c byte) int { // Use a simple implementation, as there is no intrinsic that does this like we want. n := 0 for _, v := range b { if v == c { n++ } } return n } // Count the number of instances of a byte in a string. func CountString(s string, c byte) int { // Use a simple implementation, as there is no intrinsic that does this like we want. // Currently, the compiler does not generate zero-copy byte-string conversions, so this needs to be separate from Count. n := 0 for i := 0; i < len(s); i++ { if s[i] == c { n++ } } return n } // Cutover is not reachable in TinyGo, but must exist as it is referenced. func Cutover(n int) int { // Setting MaxLen and MaxBruteForce should force a different path to be taken. // This should never be called. panic("cutover is unreachable") } // Equal checks if two byte slices are equal. // It is equivalent to bytes.Equal. func Equal(a, b []byte) bool { if len(a) != len(b) { return false } for i, v := range a { if v != b[i] { return false } } return true } // Index finds the base index of the first instance of the byte sequence b in a. // If a does not contain b, this returns -1. func Index(a, b []byte) int { for i := 0; i <= len(a)-len(b); i++ { if Equal(a[i:i+len(b)], b) { return i } } return -1 } // Index finds the index of the first instance of the specified byte in the slice. // If the byte is not found, this returns -1. func IndexByte(b []byte, c byte) int { for i, v := range b { if v == c { return i } } return -1 } // Index finds the index of the first instance of the specified byte in the string. // If the byte is not found, this returns -1. func IndexByteString(s string, c byte) int { for i := 0; i < len(s); i++ { if s[i] == c { return i } } return -1 } // Index finds the base index of the first instance of a substring in a string. // If the substring is not found, this returns -1. func IndexString(str, sub string) int { for i := 0; i <= len(str)-len(sub); i++ { if str[i:i+len(sub)] == sub { return i } } return -1 } // The following code has been copied from the Go 1.15 release tree. // PrimeRK is the prime base used in Rabin-Karp algorithm. const PrimeRK = 16777619 // HashStrBytes returns the hash and the appropriate multiplicative // factor for use in Rabin-Karp algorithm. // // This function was removed in Go 1.22. func HashStrBytes(sep []byte) (uint32, uint32) { hash := uint32(0) for i := 0; i < len(sep); i++ { hash = hash*PrimeRK + uint32(sep[i]) } var pow, sq uint32 = 1, PrimeRK for i := len(sep); i > 0; i >>= 1 { if i&1 != 0 { pow *= sq } sq *= sq } return hash, pow } // HashStr returns the hash and the appropriate multiplicative // factor for use in Rabin-Karp algorithm. // // This function was removed in Go 1.22. func HashStr[T string | []byte](sep T) (uint32, uint32) { hash := uint32(0) for i := 0; i < len(sep); i++ { hash = hash*PrimeRK + uint32(sep[i]) } var pow, sq uint32 = 1, PrimeRK for i := len(sep); i > 0; i >>= 1 { if i&1 != 0 { pow *= sq } sq *= sq } return hash, pow } // HashStrRevBytes returns the hash of the reverse of sep and the // appropriate multiplicative factor for use in Rabin-Karp algorithm. // // This function was removed in Go 1.22. func HashStrRevBytes(sep []byte) (uint32, uint32) { hash := uint32(0) for i := len(sep) - 1; i >= 0; i-- { hash = hash*PrimeRK + uint32(sep[i]) } var pow, sq uint32 = 1, PrimeRK for i := len(sep); i > 0; i >>= 1 { if i&1 != 0 { pow *= sq } sq *= sq } return hash, pow } // HashStrRev returns the hash of the reverse of sep and the // appropriate multiplicative factor for use in Rabin-Karp algorithm. // // Copied from the Go 1.22rc1 source tree. func HashStrRev[T string | []byte](sep T) (uint32, uint32) { hash := uint32(0) for i := len(sep) - 1; i >= 0; i-- { hash = hash*PrimeRK + uint32(sep[i]) } var pow, sq uint32 = 1, PrimeRK for i := len(sep); i > 0; i >>= 1 { if i&1 != 0 { pow *= sq } sq *= sq } return hash, pow } // IndexRabinKarpBytes uses the Rabin-Karp search algorithm to return the index of the // first occurrence of substr in s, or -1 if not present. // // This function was removed in Go 1.22. func IndexRabinKarpBytes(s, sep []byte) int { // Rabin-Karp search hashsep, pow := HashStrBytes(sep) n := len(sep) var h uint32 for i := 0; i < n; i++ { h = h*PrimeRK + uint32(s[i]) } if h == hashsep && Equal(s[:n], sep) { return 0 } for i := n; i < len(s); { h *= PrimeRK h += uint32(s[i]) h -= pow * uint32(s[i-n]) i++ if h == hashsep && Equal(s[i-n:i], sep) { return i - n } } return -1 } // IndexRabinKarp uses the Rabin-Karp search algorithm to return the index of the // first occurrence of sep in s, or -1 if not present. // // Copied from the Go 1.22rc1 source tree. func IndexRabinKarp[T string | []byte](s, sep T) int { // Rabin-Karp search hashss, pow := HashStr(sep) n := len(sep) var h uint32 for i := 0; i < n; i++ { h = h*PrimeRK + uint32(s[i]) } if h == hashss && string(s[:n]) == string(sep) { return 0 } for i := n; i < len(s); { h *= PrimeRK h += uint32(s[i]) h -= pow * uint32(s[i-n]) i++ if h == hashss && string(s[i-n:i]) == string(sep) { return i - n } } return -1 } // MakeNoZero makes a slice of length and capacity n without zeroing the bytes. // It is the caller's responsibility to ensure uninitialized bytes // do not leak to the end user. func MakeNoZero(n int) []byte { // Note: this does zero the buffer even though that's not necessary. // For performance reasons we might want to change this (similar to the // malloc function implemented in the runtime). return make([]byte, n) } // Copied from the Go 1.22rc1 source tree. func LastIndexByte(s []byte, c byte) int { for i := len(s) - 1; i >= 0; i-- { if s[i] == c { return i } } return -1 } // Copied from the Go 1.22rc1 source tree. func LastIndexByteString(s string, c byte) int { for i := len(s) - 1; i >= 0; i-- { if s[i] == c { return i } } return -1 } // LastIndexRabinKarp uses the Rabin-Karp search algorithm to return the last index of the // occurrence of sep in s, or -1 if not present. // // Copied from the Go 1.22rc1 source tree. func LastIndexRabinKarp[T string | []byte](s, sep T) int { // Rabin-Karp search from the end of the string hashss, pow := HashStrRev(sep) n := len(sep) last := len(s) - n var h uint32 for i := len(s) - 1; i >= last; i-- { h = h*PrimeRK + uint32(s[i]) } if h == hashss && string(s[last:]) == string(sep) { return last } for i := last - 1; i >= 0; i-- { h *= PrimeRK h += uint32(s[i]) h -= pow * uint32(s[i+n]) if h == hashss && string(s[i:i+n]) == string(sep) { return i } } return -1 } ================================================ FILE: src/internal/cm/abi.go ================================================ package cm import "unsafe" // AnyInteger is a type constraint for any integer type. type AnyInteger interface { ~int | ~uint | ~uintptr | ~int8 | ~uint8 | ~int16 | ~uint16 | ~int32 | ~uint32 | ~int64 | ~uint64 } // Reinterpret reinterprets the bits of type From into type T. // Will panic if the size of From is smaller than the size of To. func Reinterpret[T, From any](from From) (to T) { if unsafe.Sizeof(to) > unsafe.Sizeof(from) { panic("reinterpret: size of to > from") } return *(*T)(unsafe.Pointer(&from)) } // LowerString lowers a [string] into a pair of Core WebAssembly types. // // [string]: https://pkg.go.dev/builtin#string func LowerString[S ~string](s S) (*byte, uint32) { return unsafe.StringData(string(s)), uint32(len(s)) } // LiftString lifts Core WebAssembly types into a [string]. func LiftString[T ~string, Data unsafe.Pointer | uintptr | *uint8, Len AnyInteger](data Data, len Len) T { return T(unsafe.String((*uint8)(unsafe.Pointer(data)), int(len))) } // LowerList lowers a [List] into a pair of Core WebAssembly types. func LowerList[L AnyList[T], T any](list L) (*T, uint32) { l := (*List[T])(unsafe.Pointer(&list)) return l.data, uint32(l.len) } // LiftList lifts Core WebAssembly types into a [List]. func LiftList[L AnyList[T], T any, Data unsafe.Pointer | uintptr | *T, Len AnyInteger](data Data, len Len) L { return L(NewList((*T)(unsafe.Pointer(data)), len)) } // BoolToU32 converts a value whose underlying type is [bool] into a [uint32]. // Used to lower a [bool] into a Core WebAssembly i32 as specified in the [Canonical ABI]. // // [bool]: https://pkg.go.dev/builtin#bool // [uint32]: https://pkg.go.dev/builtin#uint32 // [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md func BoolToU32[B ~bool](v B) uint32 { return uint32(*(*uint8)(unsafe.Pointer(&v))) } // U32ToBool converts a [uint32] into a [bool]. // Used to lift a Core WebAssembly i32 into a [bool] as specified in the [Canonical ABI]. // // [uint32]: https://pkg.go.dev/builtin#uint32 // [bool]: https://pkg.go.dev/builtin#bool // [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md func U32ToBool(v uint32) bool { tmp := uint8(v); return *(*bool)(unsafe.Pointer(&tmp)) } // F32ToU32 maps the bits of a [float32] into a [uint32]. // Used to lower a [float32] into a Core WebAssembly i32 as specified in the [Canonical ABI]. // // [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md // [float32]: https://pkg.go.dev/builtin#float32 // [uint32]: https://pkg.go.dev/builtin#uint32 func F32ToU32(v float32) uint32 { return *(*uint32)(unsafe.Pointer(&v)) } // U32ToF32 maps the bits of a [uint32] into a [float32]. // Used to lift a Core WebAssembly i32 into a [float32] as specified in the [Canonical ABI]. // // [uint32]: https://pkg.go.dev/builtin#uint32 // [float32]: https://pkg.go.dev/builtin#float32 // [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md func U32ToF32(v uint32) float32 { return *(*float32)(unsafe.Pointer(&v)) } // F64ToU64 maps the bits of a [float64] into a [uint64]. // Used to lower a [float64] into a Core WebAssembly i64 as specified in the [Canonical ABI]. // // [float64]: https://pkg.go.dev/builtin#float64 // [uint64]: https://pkg.go.dev/builtin#uint64 // [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md // // [uint32]: https://pkg.go.dev/builtin#uint32 func F64ToU64(v float64) uint64 { return *(*uint64)(unsafe.Pointer(&v)) } // U64ToF64 maps the bits of a [uint64] into a [float64]. // Used to lift a Core WebAssembly i64 into a [float64] as specified in the [Canonical ABI]. // // [uint64]: https://pkg.go.dev/builtin#uint64 // [float64]: https://pkg.go.dev/builtin#float64 // [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md func U64ToF64(v uint64) float64 { return *(*float64)(unsafe.Pointer(&v)) } // F32ToU64 maps the bits of a [float32] into a [uint64]. // Used to lower a [float32] into a Core WebAssembly i64 when required by the [Canonical ABI]. // // [float32]: https://pkg.go.dev/builtin#float32 // [uint64]: https://pkg.go.dev/builtin#uint64 // [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md func F32ToU64(v float32) uint64 { return uint64(*(*uint32)(unsafe.Pointer(&v))) } // U64ToF32 maps the bits of a [uint64] into a [float32]. // Used to lift a Core WebAssembly i64 into a [float32] when required by the [Canonical ABI]. // // [uint64]: https://pkg.go.dev/builtin#uint64 // [float32]: https://pkg.go.dev/builtin#float32 // [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md func U64ToF32(v uint64) float32 { truncated := uint32(v) return *(*float32)(unsafe.Pointer(&truncated)) } // PointerToU32 converts a pointer of type *T into a [uint32]. // Used to lower a pointer into a Core WebAssembly i32 as specified in the [Canonical ABI]. // // [uint32]: https://pkg.go.dev/builtin#uint32 // [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md func PointerToU32[T any](v *T) uint32 { return uint32(uintptr(unsafe.Pointer(v))) } // U32ToPointer converts a [uint32] into a pointer of type *T. // Used to lift a Core WebAssembly i32 into a pointer as specified in the [Canonical ABI]. // // [uint32]: https://pkg.go.dev/builtin#uint32 // [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md func U32ToPointer[T any](v uint32) *T { return (*T)(unsafePointer(uintptr(v))) } // PointerToU64 converts a pointer of type *T into a [uint64]. // Used to lower a pointer into a Core WebAssembly i64 as specified in the [Canonical ABI]. // // [uint64]: https://pkg.go.dev/builtin#uint64 // [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md func PointerToU64[T any](v *T) uint64 { return uint64(uintptr(unsafe.Pointer(v))) } // U64ToPointer converts a [uint64] into a pointer of type *T. // Used to lift a Core WebAssembly i64 into a pointer as specified in the [Canonical ABI]. // // [uint64]: https://pkg.go.dev/builtin#uint64 // [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md func U64ToPointer[T any](v uint64) *T { return (*T)(unsafePointer(uintptr(v))) } // Appease vet, see https://github.com/golang/go/issues/58625 func unsafePointer(p uintptr) unsafe.Pointer { return *(*unsafe.Pointer)(unsafe.Pointer(&p)) } ================================================ FILE: src/internal/cm/case.go ================================================ package cm // CaseUnmarshaler returns an function that can unmarshal text into // [variant] or [enum] case T. // // [enum]: https://component-model.bytecodealliance.org/design/wit.html#enums // [variant]: https://component-model.bytecodealliance.org/design/wit.html#variants func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, text []byte) error { if len(cases) <= linearScanThreshold { return func(v *T, text []byte) error { if len(text) == 0 { return &emptyTextError{} } s := string(text) for i := 0; i < len(cases); i++ { if cases[i] == s { *v = T(i) return nil } } return &noMatchingCaseError{} } } m := make(map[string]T, len(cases)) for i, v := range cases { m[v] = T(i) } return func(v *T, text []byte) error { if len(text) == 0 { return &emptyTextError{} } c, ok := m[string(text)] if !ok { return &noMatchingCaseError{} } *v = c return nil } } const linearScanThreshold = 16 type emptyTextError struct{} func (*emptyTextError) Error() string { return "empty text" } type noMatchingCaseError struct{} func (*noMatchingCaseError) Error() string { return "no matching case" } ================================================ FILE: src/internal/cm/docs.go ================================================ // Package cm provides types and functions for interfacing with the WebAssembly Component Model. // // The types in this package (such as [List], [Option], [Result], and [Variant]) are designed to match the memory layout // of [Component Model] types as specified in the [Canonical ABI]. // // [Component Model]: https://component-model.bytecodealliance.org/introduction.html // [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#alignment package cm ================================================ FILE: src/internal/cm/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/cm/error.go ================================================ package cm import "unsafe" // ErrorContext represents the Component Model [error-context] type, // an immutable, non-deterministic, host-defined value meant to aid in debugging. // // [error-context]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#error-context-type type ErrorContext struct { _ HostLayout errorContext } type errorContext uint32 // Error implements the [error] interface. It returns the debug message associated with err. func (err errorContext) Error() string { return err.DebugMessage() } // String implements [fmt.Stringer]. func (err errorContext) String() string { return err.DebugMessage() } // DebugMessage represents the Canonical ABI [error-context.debug-message] function. // // [error-context.debug-message]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#error-contextdebug-message func (err errorContext) DebugMessage() string { var s string wasmimport_errorContextDebugMessage(err, unsafe.Pointer(&s)) return s } // Drop represents the Canonical ABI [error-context.drop] function. // // [error-context.drop]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#error-contextdrop func (err errorContext) Drop() { wasmimport_errorContextDrop(err) } ================================================ FILE: src/internal/cm/error.wasm.go ================================================ package cm import "unsafe" // msg uses unsafe.Pointer for compatibility with go1.23 and lower. // //go:wasmimport canon error-context.debug-message //go:noescape func wasmimport_errorContextDebugMessage(err errorContext, msg unsafe.Pointer) //go:wasmimport canon error-context.drop //go:noescape func wasmimport_errorContextDrop(err errorContext) ================================================ FILE: src/internal/cm/future.go ================================================ package cm // Future represents the Component Model [future] type. // A future is a special case of stream. In non-error cases, // a future delivers exactly one value before being automatically closed. // // [future]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#asynchronous-value-types type Future[T any] struct { _ HostLayout future[T] } type future[T any] uint32 // TODO: implement methods on type future ================================================ FILE: src/internal/cm/hostlayout_go122.go ================================================ //go:build !go1.23 package cm // HostLayout marks a struct as using host memory layout. // See [structs.HostLayout] in Go 1.23 or later. type HostLayout struct { _ hostLayout // prevent accidental conversion with plain struct{} } type hostLayout struct{} ================================================ FILE: src/internal/cm/hostlayout_go123.go ================================================ //go:build go1.23 package cm import "structs" // HostLayout marks a struct as using host memory layout. // See [structs.HostLayout] in Go 1.23 or later. type HostLayout = structs.HostLayout ================================================ FILE: src/internal/cm/list.go ================================================ package cm import ( "unsafe" ) // List represents a Component Model list. // The binary representation of list is similar to a Go slice minus the cap field. type List[T any] struct { _ HostLayout list[T] } // AnyList is a type constraint for generic functions that accept any [List] type. type AnyList[T any] interface { ~struct { _ HostLayout list[T] } } // NewList returns a List[T] from data and len. func NewList[T any, Len AnyInteger](data *T, len Len) List[T] { return List[T]{ list: list[T]{ data: data, len: uintptr(len), }, } } // ToList returns a List[T] equivalent to the Go slice s. // The underlying slice data is not copied, and the resulting List points at the // same array storage as the slice. func ToList[S ~[]T, T any](s S) List[T] { return NewList[T](unsafe.SliceData([]T(s)), uintptr(len(s))) } // list represents the internal representation of a Component Model list. // It is intended to be embedded in a [List], so embedding types maintain // the methods defined on this type. type list[T any] struct { _ HostLayout data *T len uintptr } // Slice returns a Go slice representing the List. func (l list[T]) Slice() []T { return unsafe.Slice(l.data, l.len) } // Data returns the data pointer for the list. func (l list[T]) Data() *T { return l.data } // Len returns the length of the list. // TODO: should this return an int instead of a uintptr? func (l list[T]) Len() uintptr { return l.len } ================================================ FILE: src/internal/cm/option.go ================================================ package cm // Option represents a Component Model [option] type. // // [option]: https://component-model.bytecodealliance.org/design/wit.html#options type Option[T any] struct { _ HostLayout option[T] } // None returns an [Option] representing the none case, // equivalent to the zero value. func None[T any]() Option[T] { return Option[T]{} } // Some returns an [Option] representing the some case. func Some[T any](v T) Option[T] { return Option[T]{ option: option[T]{ isSome: true, some: v, }, } } // option represents the internal representation of a Component Model option type. // The first byte is a bool representing none or some, // followed by storage for the associated type T. type option[T any] struct { _ HostLayout isSome bool some T } // None returns true if o represents the none case. func (o *option[T]) None() bool { return !o.isSome } // Some returns a non-nil *T if o represents the some case, // or nil if o represents the none case. func (o *option[T]) Some() *T { if o.isSome { return &o.some } return nil } // Value returns T if o represents the some case, // or the zero value of T if o represents the none case. // This does not have a pointer receiver, so it can be chained. func (o option[T]) Value() T { if !o.isSome { var zero T return zero } return o.some } ================================================ FILE: src/internal/cm/resource.go ================================================ package cm // Resource represents an opaque Component Model [resource handle]. // It is represented in the [Canonical ABI] as an 32-bit integer. // // [resource handle]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#handle-types // [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md type Resource uint32 // Rep represents a Component Model [resource rep], the core representation type of a resource. // It is represented in the [Canonical ABI] as an 32-bit integer. // // [resource rep]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#canon-resourcerep // [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md type Rep uint32 // ResourceNone is a sentinel value indicating a null or uninitialized resource. // This is a reserved value specified in the [Canonical ABI runtime state]. // // [Canonical ABI runtime state]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#runtime-state const ResourceNone = 0 ================================================ FILE: src/internal/cm/result.go ================================================ package cm import "unsafe" const ( // ResultOK represents the OK case of a result. ResultOK = false // ResultErr represents the error case of a result. ResultErr = true ) // BoolResult represents a result with no OK or error type. // False represents the OK case and true represents the error case. type BoolResult bool // Result represents a result sized to hold the Shape type. // The size of the Shape type must be greater than or equal to the size of OK and Err types. // For results with two zero-length types, use [BoolResult]. type Result[Shape, OK, Err any] struct { _ HostLayout result[Shape, OK, Err] } // AnyResult is a type constraint for generic functions that accept any [Result] type. type AnyResult[Shape, OK, Err any] interface { ~struct { _ HostLayout result[Shape, OK, Err] } } // result represents the internal representation of a Component Model result type. type result[Shape, OK, Err any] struct { _ HostLayout isErr bool _ [0]OK _ [0]Err data Shape // [unsafe.Sizeof(*(*Shape)(unsafe.Pointer(nil)))]byte } // IsOK returns true if r represents the OK case. func (r *result[Shape, OK, Err]) IsOK() bool { r.validate() return !r.isErr } // IsErr returns true if r represents the error case. func (r *result[Shape, OK, Err]) IsErr() bool { r.validate() return r.isErr } // OK returns a non-nil *OK pointer if r represents the OK case. // If r represents an error, then it returns nil. func (r *result[Shape, OK, Err]) OK() *OK { r.validate() if r.isErr { return nil } return (*OK)(unsafe.Pointer(&r.data)) } // Err returns a non-nil *Err pointer if r represents the error case. // If r represents the OK case, then it returns nil. func (r *result[Shape, OK, Err]) Err() *Err { r.validate() if !r.isErr { return nil } return (*Err)(unsafe.Pointer(&r.data)) } // Result returns (OK, zero value of Err, false) if r represents the OK case, // or (zero value of OK, Err, true) if r represents the error case. // This does not have a pointer receiver, so it can be chained. func (r result[Shape, OK, Err]) Result() (ok OK, err Err, isErr bool) { if r.isErr { return ok, *(*Err)(unsafe.Pointer(&r.data)), true } return *(*OK)(unsafe.Pointer(&r.data)), err, false } // This function is sized so it can be inlined and optimized away. func (r *result[Shape, OK, Err]) validate() { var shape Shape var ok OK var err Err // Check if size of Shape is greater than both OK and Err if unsafe.Sizeof(shape) > unsafe.Sizeof(ok) && unsafe.Sizeof(shape) > unsafe.Sizeof(err) { panic("result: size of data type > OK and Err types") } // Check if size of OK is greater than Shape if unsafe.Sizeof(ok) > unsafe.Sizeof(shape) { panic("result: size of OK type > data type") } // Check if size of Err is greater than Shape if unsafe.Sizeof(err) > unsafe.Sizeof(shape) { panic("result: size of Err type > data type") } // Check if Shape is zero-sized, but size of result != 1 if unsafe.Sizeof(shape) == 0 && unsafe.Sizeof(*r) != 1 { panic("result: size of data type == 0, but result size != 1") } } // OK returns an OK result with shape Shape and type OK and Err. // Pass Result[OK, OK, Err] or Result[Err, OK, Err] as the first type argument. func OK[R AnyResult[Shape, OK, Err], Shape, OK, Err any](ok OK) R { var r Result[Shape, OK, Err] r.validate() r.isErr = ResultOK *((*OK)(unsafe.Pointer(&r.data))) = ok return R(r) } // Err returns an error result with shape Shape and type OK and Err. // Pass Result[OK, OK, Err] or Result[Err, OK, Err] as the first type argument. func Err[R AnyResult[Shape, OK, Err], Shape, OK, Err any](err Err) R { var r Result[Shape, OK, Err] r.validate() r.isErr = ResultErr *((*Err)(unsafe.Pointer(&r.data))) = err return R(r) } ================================================ FILE: src/internal/cm/stream.go ================================================ package cm // Stream represents the Component Model [stream] type. // A stream is a special case of stream. In non-error cases, // a stream delivers exactly one value before being automatically closed. // // [stream]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#asynchronous-value-types type Stream[T any] struct { _ HostLayout stream[T] } type stream[T any] uint32 // TODO: implement methods on type stream ================================================ FILE: src/internal/cm/tuple.go ================================================ package cm // Tuple represents a [Component Model tuple] with 2 fields. // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple[T0, T1 any] struct { _ HostLayout F0 T0 F1 T1 } // Tuple3 represents a [Component Model tuple] with 3 fields. // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple3[T0, T1, T2 any] struct { _ HostLayout F0 T0 F1 T1 F2 T2 } // Tuple4 represents a [Component Model tuple] with 4 fields. // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple4[T0, T1, T2, T3 any] struct { _ HostLayout F0 T0 F1 T1 F2 T2 F3 T3 } // Tuple5 represents a [Component Model tuple] with 5 fields. // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple5[T0, T1, T2, T3, T4 any] struct { _ HostLayout F0 T0 F1 T1 F2 T2 F3 T3 F4 T4 } // Tuple6 represents a [Component Model tuple] with 6 fields. // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple6[T0, T1, T2, T3, T4, T5 any] struct { _ HostLayout F0 T0 F1 T1 F2 T2 F3 T3 F4 T4 F5 T5 } // Tuple7 represents a [Component Model tuple] with 7 fields. // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple7[T0, T1, T2, T3, T4, T5, T6 any] struct { _ HostLayout F0 T0 F1 T1 F2 T2 F3 T3 F4 T4 F5 T5 F6 T6 } // Tuple8 represents a [Component Model tuple] with 8 fields. // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple8[T0, T1, T2, T3, T4, T5, T6, T7 any] struct { _ HostLayout F0 T0 F1 T1 F2 T2 F3 T3 F4 T4 F5 T5 F6 T6 F7 T7 } // Tuple9 represents a [Component Model tuple] with 9 fields. // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple9[T0, T1, T2, T3, T4, T5, T6, T7, T8 any] struct { _ HostLayout F0 T0 F1 T1 F2 T2 F3 T3 F4 T4 F5 T5 F6 T6 F7 T7 F8 T8 } // Tuple10 represents a [Component Model tuple] with 10 fields. // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple10[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9 any] struct { _ HostLayout F0 T0 F1 T1 F2 T2 F3 T3 F4 T4 F5 T5 F6 T6 F7 T7 F8 T8 F9 T9 } // Tuple11 represents a [Component Model tuple] with 11 fields. // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple11[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any] struct { _ HostLayout F0 T0 F1 T1 F2 T2 F3 T3 F4 T4 F5 T5 F6 T6 F7 T7 F8 T8 F9 T9 F10 T10 } // Tuple12 represents a [Component Model tuple] with 12 fields. // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple12[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any] struct { _ HostLayout F0 T0 F1 T1 F2 T2 F3 T3 F4 T4 F5 T5 F6 T6 F7 T7 F8 T8 F9 T9 F10 T10 F11 T11 } // Tuple13 represents a [Component Model tuple] with 13 fields. // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple13[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any] struct { _ HostLayout F0 T0 F1 T1 F2 T2 F3 T3 F4 T4 F5 T5 F6 T6 F7 T7 F8 T8 F9 T9 F10 T10 F11 T11 F12 T12 } // Tuple14 represents a [Component Model tuple] with 14 fields. // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple14[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any] struct { _ HostLayout F0 T0 F1 T1 F2 T2 F3 T3 F4 T4 F5 T5 F6 T6 F7 T7 F8 T8 F9 T9 F10 T10 F11 T11 F12 T12 F13 T13 } // Tuple15 represents a [Component Model tuple] with 15 fields. // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple15[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any] struct { _ HostLayout F0 T0 F1 T1 F2 T2 F3 T3 F4 T4 F5 T5 F6 T6 F7 T7 F8 T8 F9 T9 F10 T10 F11 T11 F12 T12 F13 T13 F14 T14 } // Tuple16 represents a [Component Model tuple] with 16 fields. // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple16[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any] struct { _ HostLayout F0 T0 F1 T1 F2 T2 F3 T3 F4 T4 F5 T5 F6 T6 F7 T7 F8 T8 F9 T9 F10 T10 F11 T11 F12 T12 F13 T13 F14 T14 F15 T15 } // MaxTuple specifies the maximum number of fields in a Tuple* type, currently [Tuple16]. // See https://github.com/WebAssembly/component-model/issues/373 for more information. const MaxTuple = 16 ================================================ FILE: src/internal/cm/variant.go ================================================ package cm import "unsafe" // Discriminant is the set of types that can represent the tag or discriminator of a variant. // Use uint8 where there are 256 or fewer cases, uint16 for up to 65,536 cases, or uint32 for anything greater. type Discriminant interface { uint8 | uint16 | uint32 } // Variant represents a loosely-typed Component Model variant. // Shape and Align must be non-zero sized types. To create a variant with no associated // types, use an enum. type Variant[Tag Discriminant, Shape, Align any] struct { _ HostLayout variant[Tag, Shape, Align] } // AnyVariant is a type constraint for generic functions that accept any [Variant] type. type AnyVariant[Tag Discriminant, Shape, Align any] interface { ~struct { _ HostLayout variant[Tag, Shape, Align] } } // NewVariant returns a [Variant] with tag of type Disc, storage and GC shape of type Shape, // aligned to type Align, with a value of type T. func NewVariant[Tag Discriminant, Shape, Align any, T any](tag Tag, data T) Variant[Tag, Shape, Align] { validateVariant[Tag, Shape, Align, T]() var v Variant[Tag, Shape, Align] v.tag = tag *(*T)(unsafe.Pointer(&v.data)) = data return v } // New returns a [Variant] with tag of type Disc, storage and GC shape of type Shape, // aligned to type Align, with a value of type T. func New[V AnyVariant[Tag, Shape, Align], Tag Discriminant, Shape, Align any, T any](tag Tag, data T) V { validateVariant[Tag, Shape, Align, T]() var v variant[Tag, Shape, Align] v.tag = tag *(*T)(unsafe.Pointer(&v.data)) = data return *(*V)(unsafe.Pointer(&v)) } // Case returns a non-nil *T if the [Variant] case is equal to tag, otherwise it returns nil. func Case[T any, V AnyVariant[Tag, Shape, Align], Tag Discriminant, Shape, Align any](v *V, tag Tag) *T { validateVariant[Tag, Shape, Align, T]() v2 := (*variant[Tag, Shape, Align])(unsafe.Pointer(v)) if v2.tag == tag { return (*T)(unsafe.Pointer(&v2.data)) } return nil } // variant is the internal representation of a Component Model variant. // Shape and Align must be non-zero sized types. type variant[Tag Discriminant, Shape, Align any] struct { _ HostLayout tag Tag _ [0]Align data Shape // [unsafe.Sizeof(*(*Shape)(unsafe.Pointer(nil)))]byte } // Tag returns the tag (discriminant) of variant v. func (v *variant[Tag, Shape, Align]) Tag() Tag { return v.tag } // This function is sized so it can be inlined and optimized away. func validateVariant[Disc Discriminant, Shape, Align any, T any]() { var v variant[Disc, Shape, Align] var t T // Check if size of T is greater than Shape if unsafe.Sizeof(t) > unsafe.Sizeof(v.data) { panic("variant: size of requested type > data type") } // Check if Shape is zero-sized, but size of result != 1 if unsafe.Sizeof(v.data) == 0 && unsafe.Sizeof(v) != 1 { panic("variant: size of data type == 0, but variant size != 1") } } ================================================ FILE: src/internal/futex/futex.go ================================================ package futex // Cross platform futex implementation. // Futexes are supported on all major operating systems and on WebAssembly. // // For more information, see: https://outerproduct.net/futex-dictionary.html import ( "sync/atomic" "unsafe" ) // A futex is a way for userspace to wait with the pointer as the key, and for // another thread to wake one or all waiting threads keyed on the same pointer. // // A futex does not change the underlying value, it only reads it before going // to sleep (atomically) to prevent lost wake-ups. type Futex struct { atomic.Uint32 } // Atomically check for cmp to still be equal to the futex value and if so, go // to sleep. Return true if we were definitely awoken by a call to Wake or // WakeAll, and false if we can't be sure of that. func (f *Futex) Wait(cmp uint32) bool { tinygo_futex_wait((*uint32)(unsafe.Pointer(&f.Uint32)), cmp) // We *could* detect a zero return value from the futex system call which // would indicate we got awoken by a Wake or WakeAll call. However, this is // what the manual page has to say: // // > Note that a wake-up can also be caused by common futex usage patterns // > in unrelated code that happened to have previously used the futex // > word's memory location (e.g., typical futex-based implementations of // > Pthreads mutexes can cause this under some conditions). Therefore, // > callers should always conservatively assume that a return value of 0 // > can mean a spurious wake-up, and use the futex word's value (i.e., the // > user-space synchronization scheme) to decide whether to continue to // > block or not. // // I'm not sure whether we do anything like pthread does, so to be on the // safe side we say we don't know whether the wakeup was spurious or not and // return false. return false } // Like Wait, but times out after the number of nanoseconds in timeout. func (f *Futex) WaitUntil(cmp uint32, timeout uint64) { tinygo_futex_wait_timeout((*uint32)(unsafe.Pointer(&f.Uint32)), cmp, timeout) } // Wake a single waiter. func (f *Futex) Wake() { tinygo_futex_wake((*uint32)(unsafe.Pointer(&f.Uint32))) } // Wake all waiters. func (f *Futex) WakeAll() { tinygo_futex_wake_all((*uint32)(unsafe.Pointer(&f.Uint32))) } //export tinygo_futex_wait func tinygo_futex_wait(addr *uint32, cmp uint32) //export tinygo_futex_wait_timeout func tinygo_futex_wait_timeout(addr *uint32, cmp uint32, timeout uint64) //export tinygo_futex_wake func tinygo_futex_wake(addr *uint32) //export tinygo_futex_wake_all func tinygo_futex_wake_all(addr *uint32) ================================================ FILE: src/internal/futex/futex_darwin.c ================================================ //go:build none // This file is manually included, to avoid CGo which would cause a circular // import. #include // This API isn't documented by Apple, but it is used by LLVM libc++ (so should // be stable) and has been documented extensively here: // https://outerproduct.net/futex-dictionary.html int __ulock_wait(uint32_t operation, void *addr, uint64_t value, uint32_t timeout_us); int __ulock_wait2(uint32_t operation, void *addr, uint64_t value, uint64_t timeout_ns, uint64_t value2); int __ulock_wake(uint32_t operation, void *addr, uint64_t wake_value); // Operation code. #define UL_COMPARE_AND_WAIT 1 // Flags to the operation value. #define ULF_WAKE_ALL 0x00000100 #define ULF_NO_ERRNO 0x01000000 void tinygo_futex_wait(uint32_t *addr, uint32_t cmp) { __ulock_wait(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO, addr, (uint64_t)cmp, 0); } void tinygo_futex_wait_timeout(uint32_t *addr, uint32_t cmp, uint64_t timeout) { // Make sure that an accidental use of a zero timeout is not treated as an // infinite timeout. Return if it's zero since it wouldn't be waiting for // any significant time anyway. // Probably unnecessary, but guards against potential bugs. if (timeout == 0) { return; } // Note: __ulock_wait2 is available since MacOS 11. // I think that's fine, since the version before that (MacOS 10.15) is EOL // since 2022. Though if needed, we could certainly use __ulock_wait instead // and deal with the smaller timeout value. __ulock_wait2(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO, addr, (uint64_t)cmp, timeout, 0); } void tinygo_futex_wake(uint32_t *addr) { __ulock_wake(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO, addr, 0); } void tinygo_futex_wake_all(uint32_t *addr) { __ulock_wake(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO|ULF_WAKE_ALL, addr, 0); } ================================================ FILE: src/internal/futex/futex_linux.c ================================================ //go:build none // This file is manually included, to avoid CGo which would cause a circular // import. #include #include #include #include #include #define FUTEX_WAIT 0 #define FUTEX_WAKE 1 #define FUTEX_PRIVATE_FLAG 128 void tinygo_futex_wait(uint32_t *addr, uint32_t cmp) { syscall(SYS_futex, addr, FUTEX_WAIT|FUTEX_PRIVATE_FLAG, cmp, NULL, NULL, 0); } void tinygo_futex_wait_timeout(uint32_t *addr, uint32_t cmp, uint64_t timeout) { struct timespec ts = {0}; ts.tv_sec = timeout / 1000000000; ts.tv_nsec = timeout % 1000000000; syscall(SYS_futex, addr, FUTEX_WAIT|FUTEX_PRIVATE_FLAG, cmp, &ts, NULL, 0); } void tinygo_futex_wake(uint32_t *addr) { syscall(SYS_futex, addr, FUTEX_WAKE|FUTEX_PRIVATE_FLAG, 1, NULL, NULL, 0); } void tinygo_futex_wake_all(uint32_t *addr) { syscall(SYS_futex, addr, FUTEX_WAKE|FUTEX_PRIVATE_FLAG, INT_MAX, NULL, NULL, 0); } ================================================ FILE: src/internal/fuzz/fuzz.go ================================================ // Package fuzz is a shim to allow compilation against Go 1.18. // It only defines a single type to work with testing/internal/testdeps, // and hide all the other new dependencies from this package. package fuzz import ( "context" "errors" "io" "reflect" "time" ) // CorpusEntry represents an individual input for fuzzing. // // We must use an equivalent type in the testing and testing/internal/testdeps // packages, but testing can't import this package directly, and we don't want // to export this type from testing. Instead, we use the same struct type and // use a type alias (not a defined type) for convenience. type CorpusEntry = struct { Parent string // Path is the path of the corpus file, if the entry was loaded from disk. // For other entries, including seed values provided by f.Add, Path is the // name of the test, e.g. seed#0 or its hash. Path string // Data is the raw input data. Data should only be populated for seed // values. For on-disk corpus files, Data will be nil, as it will be loaded // from disk using Path. Data []byte // Values is the unmarshaled values from a corpus file. Values []any Generation int // IsSeed indicates whether this entry is part of the seed corpus. IsSeed bool } // CoordinateFuzzingOpts is a set of arguments for CoordinateFuzzing. // The zero value is valid for each field unless specified otherwise. type CoordinateFuzzingOpts struct { // Log is a writer for logging progress messages and warnings. // If nil, io.Discard will be used instead. Log io.Writer // Timeout is the amount of wall clock time to spend fuzzing after the corpus // has loaded. If zero, there will be no time limit. Timeout time.Duration // Limit is the number of random values to generate and test. If zero, // there will be no limit on the number of generated values. Limit int64 // MinimizeTimeout is the amount of wall clock time to spend minimizing // after discovering a crasher. If zero, there will be no time limit. If // MinimizeTimeout and MinimizeLimit are both zero, then minimization will // be disabled. MinimizeTimeout time.Duration // MinimizeLimit is the maximum number of calls to the fuzz function to be // made while minimizing after finding a crash. If zero, there will be no // limit. Calls to the fuzz function made when minimizing also count toward // Limit. If MinimizeTimeout and MinimizeLimit are both zero, then // minimization will be disabled. MinimizeLimit int64 // parallel is the number of worker processes to run in parallel. If zero, // CoordinateFuzzing will run GOMAXPROCS workers. Parallel int // Seed is a list of seed values added by the fuzz target with testing.F.Add // and in testdata. Seed []CorpusEntry // Types is the list of types which make up a corpus entry. // Types must be set and must match values in Seed. Types []reflect.Type // CorpusDir is a directory where files containing values that crash the // code being tested may be written. CorpusDir must be set. CorpusDir string // CacheDir is a directory containing additional "interesting" values. // The fuzzer may derive new values from these, and may write new values here. CacheDir string } // CoordinateFuzzing creates several worker processes and communicates with // them to test random inputs that could trigger crashes and expose bugs. // The worker processes run the same binary in the same directory with the // same environment variables as the coordinator process. Workers also run // with the same arguments as the coordinator, except with the -test.fuzzworker // flag prepended to the argument list. // // If a crash occurs, the function will return an error containing information // about the crash, which can be reported to the user. func CoordinateFuzzing(ctx context.Context, opts CoordinateFuzzingOpts) (err error) { return errors.New("not implemented") } // ReadCorpus reads the corpus from the provided dir. The returned corpus // entries are guaranteed to match the given types. Any malformed files will // be saved in a MalformedCorpusError and returned, along with the most recent // error. func ReadCorpus(dir string, types []reflect.Type) ([]CorpusEntry, error) { return nil, errors.New("not implemented") } // CheckCorpus verifies that the types in vals match the expected types // provided. func CheckCorpus(vals []any, types []reflect.Type) error { return errors.New("not implemented") } func ResetCoverage() {} func SnapshotCoverage() {} // RunFuzzWorker is called in a worker process to communicate with the // coordinator process in order to fuzz random inputs. RunFuzzWorker loops // until the coordinator tells it to stop. // // fn is a wrapper on the fuzz function. It may return an error to indicate // a given input "crashed". The coordinator will also record a crasher if // the function times out or terminates the process. // // RunFuzzWorker returns an error if it could not communicate with the // coordinator process. func RunFuzzWorker(ctx context.Context, fn func(CorpusEntry) error) error { return errors.New("not implemented") } ================================================ FILE: src/internal/gclayout/gclayout.go ================================================ package gclayout import "unsafe" // Internal constants for gc layout // See runtime/gc_precise.go type Layout uintptr const ( // 16-bit int => bits = 4 // 32-bit int => bits = 5 // 64-bit int => bits = 6 sizeBits = 4 + unsafe.Sizeof(uintptr(0))/4 sizeShift = sizeBits + 1 NoPtrs = Layout(uintptr(0b0< uintptr(addr2) { // Canonicalize order to reduce number of entries in visited. // Assumes non-moving garbage collector. addr1, addr2 = addr2, addr1 } // Short circuit if references are already seen. v := visit{addr1, addr2, v1.typecode} if _, ok := visited[v]; ok { return true } // Remember for later. visited[v] = struct{}{} } switch v1.Kind() { case Array: for i := 0; i < v1.Len(); i++ { if !deepValueEqual(v1.Index(i), v2.Index(i), visited) { return false } } return true case Slice: if v1.IsNil() != v2.IsNil() { return false } if v1.Len() != v2.Len() { return false } if v1.UnsafePointer() == v2.UnsafePointer() { return true } for i := 0; i < v1.Len(); i++ { if !deepValueEqual(v1.Index(i), v2.Index(i), visited) { return false } } return true case Interface: if v1.IsNil() || v2.IsNil() { return v1.IsNil() == v2.IsNil() } return deepValueEqual(v1.Elem(), v2.Elem(), visited) case Ptr: if v1.UnsafePointer() == v2.UnsafePointer() { return true } return deepValueEqual(v1.Elem(), v2.Elem(), visited) case Struct: for i, n := 0, v1.NumField(); i < n; i++ { if !deepValueEqual(v1.Field(i), v2.Field(i), visited) { return false } } return true case Map: if v1.IsNil() != v2.IsNil() { return false } if v1.Len() != v2.Len() { return false } if v1.UnsafePointer() == v2.UnsafePointer() { return true } for _, k := range v1.MapKeys() { val1 := v1.MapIndex(k) val2 := v2.MapIndex(k) if !val1.IsValid() || !val2.IsValid() || !deepValueEqual(val1, val2, visited) { return false } } return true case Func: if v1.IsNil() && v2.IsNil() { return true } // Can't do better than this: return false default: // Normal equality suffices return valueInterfaceUnsafe(v1) == valueInterfaceUnsafe(v2) } } // DeepEqual reports whether x and y are “deeply equal”, defined as follows. // Two values of identical type are deeply equal if one of the following cases applies. // Values of distinct types are never deeply equal. // // Array values are deeply equal when their corresponding elements are deeply equal. // // Struct values are deeply equal if their corresponding fields, // both exported and unexported, are deeply equal. // // Func values are deeply equal if both are nil; otherwise they are not deeply equal. // // Interface values are deeply equal if they hold deeply equal concrete values. // // Map values are deeply equal when all of the following are true: // they are both nil or both non-nil, they have the same length, // and either they are the same map object or their corresponding keys // (matched using Go equality) map to deeply equal values. // // Pointer values are deeply equal if they are equal using Go's == operator // or if they point to deeply equal values. // // Slice values are deeply equal when all of the following are true: // they are both nil or both non-nil, they have the same length, // and either they point to the same initial entry of the same underlying array // (that is, &x[0] == &y[0]) or their corresponding elements (up to length) are deeply equal. // Note that a non-nil empty slice and a nil slice (for example, []byte{} and []byte(nil)) // are not deeply equal. // // Other values - numbers, bools, strings, and channels - are deeply equal // if they are equal using Go's == operator. // // In general DeepEqual is a recursive relaxation of Go's == operator. // However, this idea is impossible to implement without some inconsistency. // Specifically, it is possible for a value to be unequal to itself, // either because it is of func type (uncomparable in general) // or because it is a floating-point NaN value (not equal to itself in floating-point comparison), // or because it is an array, struct, or interface containing // such a value. // On the other hand, pointer values are always equal to themselves, // even if they point at or contain such problematic values, // because they compare equal using Go's == operator, and that // is a sufficient condition to be deeply equal, regardless of content. // DeepEqual has been defined so that the same short-cut applies // to slices and maps: if x and y are the same slice or the same map, // they are deeply equal regardless of content. // // As DeepEqual traverses the data values it may find a cycle. The // second and subsequent times that DeepEqual compares two pointer // values that have been compared before, it treats the values as // equal rather than examining the values to which they point. // This ensures that DeepEqual terminates. func DeepEqual(x, y interface{}) bool { if x == nil || y == nil { return x == y } v1 := ValueOf(x) v2 := ValueOf(y) if v1.typecode != v2.typecode { return false } return deepValueEqual(v1, v2, make(map[visit]struct{})) } ================================================ FILE: src/internal/reflectlite/endian_big.go ================================================ //go:build mips package reflectlite import "unsafe" // loadValue loads a value that may or may not be word-aligned. The number of // bytes given in size are loaded. The biggest possible size it can load is that // of an uintptr. func loadValue(ptr unsafe.Pointer, size uintptr) uintptr { loadedValue := uintptr(0) for i := uintptr(0); i < size; i++ { loadedValue <<= 8 loadedValue |= uintptr(*(*byte)(ptr)) ptr = unsafe.Add(ptr, 1) } return loadedValue } // storeValue is the inverse of loadValue. It stores a value to a pointer that // doesn't need to be aligned. func storeValue(ptr unsafe.Pointer, size, value uintptr) { // This could perhaps be optimized using bits.ReverseBytes32 if needed. value <<= (unsafe.Sizeof(uintptr(0)) - size) * 8 for i := uintptr(0); i < size; i++ { *(*byte)(ptr) = byte(value >> ((unsafe.Sizeof(uintptr(0)) - 1) * 8)) ptr = unsafe.Add(ptr, 1) value <<= 8 } } // maskAndShift cuts out a part of a uintptr. Note that the offset may not be 0. func maskAndShift(value, offset, size uintptr) uintptr { mask := ^uintptr(0) >> ((unsafe.Sizeof(uintptr(0)) - size) * 8) return (uintptr(value) >> ((unsafe.Sizeof(uintptr(0)) - offset - size) * 8)) & mask } ================================================ FILE: src/internal/reflectlite/endian_little.go ================================================ //go:build !mips package reflectlite import "unsafe" // loadValue loads a value that may or may not be word-aligned. The number of // bytes given in size are loaded. The biggest possible size it can load is that // of an uintptr. func loadValue(ptr unsafe.Pointer, size uintptr) uintptr { loadedValue := uintptr(0) shift := uintptr(0) for i := uintptr(0); i < size; i++ { loadedValue |= uintptr(*(*byte)(ptr)) << shift shift += 8 ptr = unsafe.Add(ptr, 1) } return loadedValue } // storeValue is the inverse of loadValue. It stores a value to a pointer that // doesn't need to be aligned. func storeValue(ptr unsafe.Pointer, size, value uintptr) { for i := uintptr(0); i < size; i++ { *(*byte)(ptr) = byte(value) ptr = unsafe.Add(ptr, 1) value >>= 8 } } // maskAndShift cuts out a part of a uintptr. Note that the offset may not be 0. func maskAndShift(value, offset, size uintptr) uintptr { mask := ^uintptr(0) >> ((unsafe.Sizeof(uintptr(0)) - size) * 8) return (uintptr(value) >> (offset * 8)) & mask } ================================================ FILE: src/internal/reflectlite/strconv.go ================================================ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package reflectlite import ( "unicode/utf8" ) // errSyntax indicates that a value does not have the right syntax for the target type. var errSyntax = badSyntax{} type badSyntax struct{} func (badSyntax) Error() string { return "invalid syntax" } func unhex(b byte) (v rune, ok bool) { c := rune(b) switch { case '0' <= c && c <= '9': return c - '0', true case 'a' <= c && c <= 'f': return c - 'a' + 10, true case 'A' <= c && c <= 'F': return c - 'A' + 10, true } return } const ( lowerhex = "0123456789abcef" ) // unquoteChar decodes the first character or byte in the escaped string // or character literal represented by the string s. // It returns four values: // // 1. value, the decoded Unicode code point or byte value; // 2. multibyte, a boolean indicating whether the decoded character requires a multibyte UTF-8 representation; // 3. tail, the remainder of the string after the character; and // 4. an error that will be nil if the character is syntactically valid. // // The second argument, quote, specifies the type of literal being parsed // and therefore which escaped quote character is permitted. // If set to a single quote, it permits the sequence \' and disallows unescaped '. // If set to a double quote, it permits \" and disallows unescaped ". // If set to zero, it does not permit either escape and allows both quote characters to appear unescaped. func unquoteChar(s string, quote byte) (value rune, multibyte bool, tail string, err error) { // easy cases if len(s) == 0 { err = errSyntax return } switch c := s[0]; { case c == quote && (quote == '\'' || quote == '"'): err = errSyntax return case c >= utf8.RuneSelf: r, size := utf8.DecodeRuneInString(s) return r, true, s[size:], nil case c != '\\': return rune(s[0]), false, s[1:], nil } // hard case: c is backslash if len(s) <= 1 { err = errSyntax return } c := s[1] s = s[2:] switch c { case 'a': value = '\a' case 'b': value = '\b' case 'f': value = '\f' case 'n': value = '\n' case 'r': value = '\r' case 't': value = '\t' case 'v': value = '\v' case 'x', 'u', 'U': n := 0 switch c { case 'x': n = 2 case 'u': n = 4 case 'U': n = 8 } var v rune if len(s) < n { err = errSyntax return } for j := 0; j < n; j++ { x, ok := unhex(s[j]) if !ok { err = errSyntax return } v = v<<4 | x } s = s[n:] if c == 'x' { // single-byte string, possibly not UTF-8 value = v break } if v > utf8.MaxRune { err = errSyntax return } value = v multibyte = true case '0', '1', '2', '3', '4', '5', '6', '7': v := rune(c) - '0' if len(s) < 2 { err = errSyntax return } for j := 0; j < 2; j++ { // one digit already; two more x := rune(s[j]) - '0' if x < 0 || x > 7 { err = errSyntax return } v = (v << 3) | x } s = s[2:] if v > 255 { err = errSyntax return } value = v case '\\': value = '\\' case '\'', '"': if c != quote { err = errSyntax return } value = rune(c) default: err = errSyntax return } tail = s return } // unquote interprets s as a single-quoted, double-quoted, // or backquoted Go string literal, returning the string value // that s quotes. (If s is single-quoted, it would be a Go // character literal; unquote returns the corresponding // one-character string.) func unquote(s string) (string, error) { n := len(s) if n < 2 { return "", errSyntax } quote := s[0] if quote != s[n-1] { return "", errSyntax } s = s[1 : n-1] if quote == '`' { if contains(s, '`') { return "", errSyntax } if contains(s, '\r') { // -1 because we know there is at least one \r to remove. buf := make([]byte, 0, len(s)-1) for i := 0; i < len(s); i++ { if s[i] != '\r' { buf = append(buf, s[i]) } } return string(buf), nil } return s, nil } if quote != '"' && quote != '\'' { return "", errSyntax } if contains(s, '\n') { return "", errSyntax } // Is it trivial? Avoid allocation. if !contains(s, '\\') && !contains(s, quote) { switch quote { case '"': if utf8.ValidString(s) { return s, nil } case '\'': r, size := utf8.DecodeRuneInString(s) if size == len(s) && (r != utf8.RuneError || size != 1) { return s, nil } } } var runeTmp [utf8.UTFMax]byte buf := make([]byte, 0, 3*len(s)/2) // Try to avoid more allocations. for len(s) > 0 { c, multibyte, ss, err := unquoteChar(s, quote) if err != nil { return "", err } s = ss if c < utf8.RuneSelf || !multibyte { buf = append(buf, byte(c)) } else { n := utf8.EncodeRune(runeTmp[:], c) buf = append(buf, runeTmp[:n]...) } if quote == '\'' && len(s) != 0 { // single-quoted must be single character return "", errSyntax } } return string(buf), nil } func quote(s string) string { buf := make([]byte, 0, 3*len(s)/2) const quote = '"' buf = append(buf, quote) for width := 0; len(s) > 0; s = s[width:] { r := rune(s[0]) width = 1 if r >= utf8.RuneSelf { r, width = utf8.DecodeRuneInString(s) } if width == 1 && r == utf8.RuneError { buf = append(buf, `\x`...) buf = append(buf, lowerhex[s[0]>>4]) buf = append(buf, lowerhex[s[0]&0xF]) continue } buf = appendEscapedRune(buf, r) } buf = append(buf, quote) return string(buf) } func appendEscapedRune(buf []byte, r rune) []byte { const quote = '"' var runeTmp [utf8.UTFMax]byte if r == rune(quote) || r == '\\' { // always backslashed buf = append(buf, '\\') buf = append(buf, byte(r)) return buf } if isPrint(r) { n := utf8.EncodeRune(runeTmp[:], r) buf = append(buf, runeTmp[:n]...) return buf } switch r { case '\a': buf = append(buf, `\a`...) case '\b': buf = append(buf, `\b`...) case '\f': buf = append(buf, `\f`...) case '\n': buf = append(buf, `\n`...) case '\r': buf = append(buf, `\r`...) case '\t': buf = append(buf, `\t`...) case '\v': buf = append(buf, `\v`...) default: switch { case r < ' ' || r == 0x7f: buf = append(buf, `\x`...) buf = append(buf, lowerhex[byte(r)>>4]) buf = append(buf, lowerhex[byte(r)&0xF]) case !utf8.ValidRune(r): r = 0xFFFD fallthrough case r < 0x10000: buf = append(buf, `\u`...) for s := 12; s >= 0; s -= 4 { buf = append(buf, lowerhex[r>>uint(s)&0xF]) } default: buf = append(buf, `\U`...) for s := 28; s >= 0; s -= 4 { buf = append(buf, lowerhex[r>>uint(s)&0xF]) } } } return buf } // This is only used for struct tags. Assume func isPrint(r rune) bool { if r <= 0xFF { if 0x20 <= r && r <= 0x7E { // All the ASCII is printable from space through DEL-1. return true } if 0xA1 <= r && r <= 0xFF { // Similarly for ¡ through ÿ... return r != 0xAD // ...except for the bizarre soft hyphen. } return false } // TinyGo: Skip all other unicode processing return false } // contains reports whether the string contains the byte c. func contains(s string, c byte) bool { return indexByteString(s, c) != -1 } // Index finds the index of the first instance of the specified byte in the string. // If the byte is not found, this returns -1. func indexByteString(s string, c byte) int { for i := 0; i < len(s); i++ { if s[i] == c { return i } } return -1 } ================================================ FILE: src/internal/reflectlite/swapper.go ================================================ package reflectlite import "unsafe" // Some of code here has been copied from the Go sources: // https://github.com/golang/go/blob/go1.15.2/src/reflect/swapper.go // It has the following copyright note: // // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. func Swapper(slice interface{}) func(i, j int) { v := ValueOf(slice) if v.Kind() != Slice { panic(&ValueError{Method: "Swapper"}) } // Just return Nop func if nothing to swap. if v.Len() < 2 { return func(i, j int) {} } typ := v.typecode.Elem() size := typ.Size() header := (*sliceHeader)(v.value) tmp := unsafe.Pointer(&make([]byte, size)[0]) return func(i, j int) { if uint(i) >= uint(header.len) || uint(j) >= uint(header.len) { panic("reflect: slice index out of range") } val1 := unsafe.Add(header.data, uintptr(i)*size) val2 := unsafe.Add(header.data, uintptr(j)*size) memcpy(tmp, val1, size) memcpy(val1, val2, size) memcpy(val2, tmp, size) } } ================================================ FILE: src/internal/reflectlite/type.go ================================================ package reflectlite import ( "internal/gclayout" "internal/itoa" "unsafe" ) // Flags stored in the first byte of the struct field byte array. Must be kept // up to date with compiler/interface.go. const ( structFieldFlagAnonymous = 1 << iota structFieldFlagHasTag structFieldFlagIsExported structFieldFlagIsEmbedded ) type Kind uint8 // Copied from reflect/type.go // https://golang.org/src/reflect/type.go?s=8302:8316#L217 // These constants must match basicTypes and the typeKind* constants in // compiler/interface.go const ( Invalid Kind = iota Bool Int Int8 Int16 Int32 Int64 Uint Uint8 Uint16 Uint32 Uint64 Uintptr Float32 Float64 Complex64 Complex128 String UnsafePointer Chan Interface Pointer Slice Array Func Map Struct ) // Ptr is the old name for the Pointer kind. const Ptr = Pointer func (k Kind) String() string { switch k { case Invalid: return "invalid" case Bool: return "bool" case Int: return "int" case Int8: return "int8" case Int16: return "int16" case Int32: return "int32" case Int64: return "int64" case Uint: return "uint" case Uint8: return "uint8" case Uint16: return "uint16" case Uint32: return "uint32" case Uint64: return "uint64" case Uintptr: return "uintptr" case Float32: return "float32" case Float64: return "float64" case Complex64: return "complex64" case Complex128: return "complex128" case String: return "string" case UnsafePointer: return "unsafe.Pointer" case Chan: return "chan" case Interface: return "interface" case Pointer: return "ptr" case Slice: return "slice" case Array: return "array" case Func: return "func" case Map: return "map" case Struct: return "struct" default: return "kind" + itoa.Itoa(int(int8(k))) } } // Copied from reflect/type.go // https://go.dev/src/reflect/type.go?#L348 // ChanDir represents a channel type's direction. type ChanDir int const ( RecvDir ChanDir = 1 << iota // <-chan SendDir // chan<- BothDir = RecvDir | SendDir // chan ) // Type represents the minimal interface for a Go type. type Type interface { // These should match the reflectlite.Type implementation in Go. AssignableTo(u Type) bool Comparable() bool Elem() Type Implements(u Type) bool Kind() Kind Name() string PkgPath() string Size() uintptr String() string // Additional methods shared with reflect.Type. Align() int Field(i int) StructField Key() Type Len() int NumField() int NumMethod() int } // Constants for the 'meta' byte. const ( kindMask = 31 // mask to apply to the meta byte to get the Kind value flagNamed = 32 // flag that is set if this is a named type flagComparable = 64 // flag that is set if this type is comparable flagIsBinary = 128 // flag that is set if this type uses the hashmap binary algorithm ) // The base type struct. All type structs start with this. type RawType struct { meta uint8 // metadata byte, contains kind and flags (see constants above) } // All types that have an element type: named, chan, slice, array, map (but not // pointer because it doesn't have ptrTo). type elemType struct { RawType numMethod uint16 ptrTo *RawType elem *RawType } type ptrType struct { RawType numMethod uint16 elem *RawType } type interfaceType struct { RawType ptrTo *RawType // TODO: methods } type arrayType struct { RawType numMethod uint16 ptrTo *RawType elem *RawType arrayLen uintptr slicePtr *RawType } type mapType struct { RawType numMethod uint16 ptrTo *RawType elem *RawType key *RawType } type namedType struct { RawType numMethod uint16 ptrTo *RawType elem *RawType pkg *byte name [1]byte } // Type for struct types. The numField value is intentionally put before ptrTo // for better struct packing on 32-bit and 64-bit architectures. On these // architectures, the ptrTo field still has the same offset as in all the other // type structs. // The fields array isn't necessarily 1 structField long, instead it is as long // as numFields. The array is given a length of 1 to satisfy the Go type // checker. type structType struct { RawType numMethod uint16 ptrTo *RawType pkgpath *byte size uint32 numField uint16 fields [1]structField // the remaining fields are all of type structField } type structField struct { fieldType *RawType data unsafe.Pointer // various bits of information, packed in a byte array } // Equivalent to (go/types.Type).Underlying(): if this is a named type return // the underlying type, else just return the type itself. func (t *RawType) underlying() *RawType { if t.isNamed() { return (*elemType)(unsafe.Pointer(t)).elem } return t } func (t *RawType) ptrtag() uintptr { return uintptr(unsafe.Pointer(t)) & 0b11 } func (t *RawType) isNamed() bool { if tag := t.ptrtag(); tag != 0 { return false } return t.meta&flagNamed != 0 } func TypeOf(i interface{}) Type { if i == nil { return nil } typecode, _ := decomposeInterface(i) return (*RawType)(typecode) } func PtrTo(t Type) Type { return PointerTo(t) } func PointerTo(t Type) Type { return pointerTo(t.(*RawType)) } func pointerTo(t *RawType) *RawType { if t.isNamed() { return (*elemType)(unsafe.Pointer(t)).ptrTo } switch t.Kind() { case Pointer: if tag := t.ptrtag(); tag < 3 { return (*RawType)(unsafe.Add(unsafe.Pointer(t), 1)) } // TODO(dgryski): This is blocking https://github.com/tinygo-org/tinygo/issues/3131 // We need to be able to create types that match existing types to prevent typecode equality. panic("reflect: cannot make *****T type") case Struct: return (*structType)(unsafe.Pointer(t)).ptrTo default: return (*elemType)(unsafe.Pointer(t)).ptrTo } } func (t *RawType) String() string { if t.isNamed() { s := t.name() if s[0] == '.' { return s[1:] } return s } switch t.Kind() { case Chan: elem := t.elem().String() switch t.ChanDir() { case SendDir: return "chan<- " + elem case RecvDir: return "<-chan " + elem case BothDir: if elem[0] == '<' { // typ is recv chan, need parentheses as "<-" associates with leftmost // chan possible, see: // * https://golang.org/ref/spec#Channel_types // * https://github.com/golang/go/issues/39897 return "chan (" + elem + ")" } return "chan " + elem } case Pointer: return "*" + t.elem().String() case Slice: return "[]" + t.elem().String() case Array: return "[" + itoa.Itoa(t.Len()) + "]" + t.elem().String() case Map: return "map[" + t.key().String() + "]" + t.elem().String() case Struct: numField := t.NumField() if numField == 0 { return "struct {}" } s := "struct {" for i := 0; i < numField; i++ { f := t.rawField(i) s += " " + f.Name + " " + f.Type.String() if f.Tag != "" { s += " " + quote(string(f.Tag)) } // every field except the last needs a semicolon if i < numField-1 { s += ";" } } s += " }" return s case Interface: // TODO(dgryski): Needs actual method set info return "interface {}" default: return t.Kind().String() } return t.Kind().String() } func (t *RawType) Kind() Kind { if t == nil { return Invalid } if tag := t.ptrtag(); tag != 0 { return Pointer } return Kind(t.meta & kindMask) } var ( errTypeElem = &TypeError{"Elem"} errTypeKey = &TypeError{"Key"} errTypeField = &TypeError{"Field"} errTypeBits = &TypeError{"Bits"} errTypeLen = &TypeError{"Len"} errTypeNumField = &TypeError{"NumField"} errTypeChanDir = &TypeError{"ChanDir"} errTypeFieldByName = &TypeError{"FieldByName"} errTypeFieldByIndex = &TypeError{"FieldByIndex"} ) // Elem returns the element type for channel, slice and array types, the // pointed-to value for pointer types, and the key type for map types. func (t *RawType) Elem() Type { return t.elem() } func (t *RawType) elem() *RawType { if tag := t.ptrtag(); tag != 0 { return (*RawType)(unsafe.Add(unsafe.Pointer(t), -1)) } underlying := t.underlying() switch underlying.Kind() { case Pointer: return (*ptrType)(unsafe.Pointer(underlying)).elem case Chan, Slice, Array, Map: return (*elemType)(unsafe.Pointer(underlying)).elem default: panic(errTypeElem) } } func (t *RawType) key() *RawType { underlying := t.underlying() if underlying.Kind() != Map { panic(errTypeKey) } return (*mapType)(unsafe.Pointer(underlying)).key } // Field returns the type of the i'th field of this struct type. It panics if t // is not a struct type. func (t *RawType) Field(i int) StructField { field := t.rawField(i) return StructField{ Name: field.Name, PkgPath: field.PkgPath, Type: field.Type, // note: converts RawType to Type Tag: field.Tag, Anonymous: field.Anonymous, Offset: field.Offset, Index: []int{i}, } } func rawStructFieldFromPointer(descriptor *structType, fieldType *RawType, data unsafe.Pointer, flagsByte uint8, name string, offset uint32) rawStructField { // Read the field tag, if there is one. var tag string if flagsByte&structFieldFlagHasTag != 0 { data = unsafe.Add(data, 1) // C: data+1 tagLen := uintptr(*(*byte)(data)) data = unsafe.Add(data, 1) // C: data+1 tag = *(*string)(unsafe.Pointer(&stringHeader{ data: data, len: tagLen, })) } // Set the PkgPath to some (arbitrary) value if the package path is not // exported. pkgPath := "" if flagsByte&structFieldFlagIsExported == 0 { // This field is unexported. pkgPath = readStringZ(unsafe.Pointer(descriptor.pkgpath)) } return rawStructField{ Name: name, PkgPath: pkgPath, Type: fieldType, Tag: StructTag(tag), Anonymous: flagsByte&structFieldFlagAnonymous != 0, Offset: uintptr(offset), } } // rawField returns nearly the same value as Field but without converting the // Type member to an interface. // // For internal use only. func (t *RawType) rawField(n int) rawStructField { if t.Kind() != Struct { panic(errTypeField) } descriptor := (*structType)(unsafe.Pointer(t.underlying())) if uint(n) >= uint(descriptor.numField) { panic("reflect: field index out of range") } // Iterate over all the fields to calculate the offset. // This offset could have been stored directly in the array (to make the // lookup faster), but by calculating it on-the-fly a bit of storage can be // saved. field := (*structField)(unsafe.Add(unsafe.Pointer(&descriptor.fields[0]), uintptr(n)*unsafe.Sizeof(structField{}))) data := field.data // Read some flags of this field, like whether the field is an embedded // field. See structFieldFlagAnonymous and similar flags. flagsByte := *(*byte)(data) data = unsafe.Add(data, 1) offset, lenOffs := uvarint32(unsafe.Slice((*byte)(data), maxVarintLen32)) data = unsafe.Add(data, lenOffs) name := readStringZ(data) data = unsafe.Add(data, len(name)) return rawStructFieldFromPointer(descriptor, field.fieldType, data, flagsByte, name, offset) } // rawFieldByNameFunc returns nearly the same value as FieldByNameFunc but without converting the // Type member to an interface. // // For internal use only. func (t *RawType) rawFieldByNameFunc(match func(string) bool) (rawStructField, []int, bool) { if t.Kind() != Struct { panic(errTypeField) } type fieldWalker struct { t *RawType index []int } queue := make([]fieldWalker, 0, 4) queue = append(queue, fieldWalker{t, nil}) for len(queue) > 0 { type result struct { r rawStructField index []int } var found []result var nextlevel []fieldWalker // For all the structs at this level.. for _, ll := range queue { // Iterate over all the fields looking for the matching name // Also calculate field offset. descriptor := (*structType)(unsafe.Pointer(ll.t.underlying())) field := &descriptor.fields[0] for i := uint16(0); i < descriptor.numField; i++ { data := field.data // Read some flags of this field, like whether the field is an embedded // field. See structFieldFlagAnonymous and similar flags. flagsByte := *(*byte)(data) data = unsafe.Add(data, 1) offset, lenOffs := uvarint32(unsafe.Slice((*byte)(data), maxVarintLen32)) data = unsafe.Add(data, lenOffs) name := readStringZ(data) data = unsafe.Add(data, len(name)) if match(name) { found = append(found, result{ rawStructFieldFromPointer(descriptor, field.fieldType, data, flagsByte, name, offset), append(ll.index[:len(ll.index):len(ll.index)], int(i)), }) } structOrPtrToStruct := field.fieldType.Kind() == Struct || (field.fieldType.Kind() == Pointer && field.fieldType.elem().Kind() == Struct) if flagsByte&structFieldFlagIsEmbedded == structFieldFlagIsEmbedded && structOrPtrToStruct { embedded := field.fieldType if embedded.Kind() == Pointer { embedded = embedded.elem() } nextlevel = append(nextlevel, fieldWalker{ t: embedded, index: append(ll.index[:len(ll.index):len(ll.index)], int(i)), }) } // update offset/field pointer if there *is* a next field if i < descriptor.numField-1 { // Increment pointer to the next field. field = (*structField)(unsafe.Add(unsafe.Pointer(field), unsafe.Sizeof(structField{}))) } } } // found multiple hits at this level if len(found) > 1 { return rawStructField{}, nil, false } // found the field we were looking for if len(found) == 1 { r := found[0] return r.r, r.index, true } // else len(found) == 0, move on to the next level queue = append(queue[:0], nextlevel...) } // didn't find it return rawStructField{}, nil, false } // Bits returns the number of bits that this type uses. It is only valid for // arithmetic types (integers, floats, and complex numbers). For other types, it // will panic. func (t *RawType) Bits() int { kind := t.Kind() if kind >= Int && kind <= Complex128 { return int(t.Size()) * 8 } panic(errTypeBits) } // Len returns the number of elements in this array. It panics of the type kind // is not Array. func (t *RawType) Len() int { if t.Kind() != Array { panic(errTypeLen) } return int((*arrayType)(unsafe.Pointer(t.underlying())).arrayLen) } // NumField returns the number of fields of a struct type. It panics for other // type kinds. func (t *RawType) NumField() int { if t.Kind() != Struct { panic(errTypeNumField) } return int((*structType)(unsafe.Pointer(t.underlying())).numField) } // Size returns the size in bytes of a given type. It is similar to // unsafe.Sizeof. func (t *RawType) Size() uintptr { switch t.Kind() { case Bool, Int8, Uint8: return 1 case Int16, Uint16: return 2 case Int32, Uint32: return 4 case Int64, Uint64: return 8 case Int, Uint: return unsafe.Sizeof(int(0)) case Uintptr: return unsafe.Sizeof(uintptr(0)) case Float32: return 4 case Float64: return 8 case Complex64: return 8 case Complex128: return 16 case String: return unsafe.Sizeof("") case UnsafePointer, Chan, Map, Pointer: return unsafe.Sizeof(uintptr(0)) case Slice: return unsafe.Sizeof([]int{}) case Interface: return unsafe.Sizeof(interface{}(nil)) case Func: var f func() return unsafe.Sizeof(f) case Array: return t.elem().Size() * uintptr(t.Len()) case Struct: u := t.underlying() return uintptr((*structType)(unsafe.Pointer(u)).size) default: panic("unimplemented: size of type") } } // Align returns the alignment of this type. It is similar to calling // unsafe.Alignof. func (t *RawType) Align() int { switch t.Kind() { case Bool, Int8, Uint8: return int(unsafe.Alignof(int8(0))) case Int16, Uint16: return int(unsafe.Alignof(int16(0))) case Int32, Uint32: return int(unsafe.Alignof(int32(0))) case Int64, Uint64: return int(unsafe.Alignof(int64(0))) case Int, Uint: return int(unsafe.Alignof(int(0))) case Uintptr: return int(unsafe.Alignof(uintptr(0))) case Float32: return int(unsafe.Alignof(float32(0))) case Float64: return int(unsafe.Alignof(float64(0))) case Complex64: return int(unsafe.Alignof(complex64(0))) case Complex128: return int(unsafe.Alignof(complex128(0))) case String: return int(unsafe.Alignof("")) case UnsafePointer, Chan, Map, Pointer: return int(unsafe.Alignof(uintptr(0))) case Slice: return int(unsafe.Alignof([]int(nil))) case Interface: return int(unsafe.Alignof(interface{}(nil))) case Func: var f func() return int(unsafe.Alignof(f)) case Struct: numField := t.NumField() alignment := 1 for i := 0; i < numField; i++ { fieldAlignment := t.rawField(i).Type.Align() if fieldAlignment > alignment { alignment = fieldAlignment } } return alignment case Array: return t.elem().Align() default: panic("unimplemented: alignment of type") } } func (r *RawType) gcLayout() unsafe.Pointer { kind := r.Kind() if kind < String { return gclayout.NoPtrs.AsPtr() } switch kind { case Pointer, UnsafePointer, Chan, Map: return gclayout.Pointer.AsPtr() case String: return gclayout.String.AsPtr() case Slice: return gclayout.Slice.AsPtr() } // Unknown (for now); let the conservative pointer scanning handle it return nil } // FieldAlign returns the alignment if this type is used in a struct field. It // is currently an alias for Align() but this might change in the future. func (t *RawType) FieldAlign() int { return t.Align() } // AssignableTo returns whether a value of type t can be assigned to a variable // of type u. func (t *RawType) AssignableTo(u Type) bool { if t == u.(*RawType) { return true } if t.underlying() == u.(*RawType).underlying() && (!t.isNamed() || !u.(*RawType).isNamed()) { return true } if u.Kind() == Interface && u.NumMethod() == 0 { return true } if u.Kind() == Interface { panic("reflect: unimplemented: AssignableTo with interface") } return false } func (t *RawType) Implements(u Type) bool { if u.Kind() != Interface { panic("reflect: non-interface type passed to Type.Implements") } return t.AssignableTo(u) } // Comparable returns whether values of this type can be compared to each other. func (t *RawType) Comparable() bool { return (t.meta & flagComparable) == flagComparable } // isBinary returns if the hashmapAlgorithmBinary functions can be used on this type func (t *RawType) isBinary() bool { return (t.meta & flagIsBinary) == flagIsBinary } func (t *RawType) ChanDir() ChanDir { if t.Kind() != Chan { panic(errTypeChanDir) } dir := int((*elemType)(unsafe.Pointer(t)).numMethod) // nummethod is overloaded for channel to store channel direction return ChanDir(dir) } func (t *RawType) NumMethod() int { if t.isNamed() { return int((*namedType)(unsafe.Pointer(t)).numMethod) } switch t.Kind() { case Pointer: return int((*ptrType)(unsafe.Pointer(t)).numMethod) case Struct: return int((*structType)(unsafe.Pointer(t)).numMethod) case Interface: //FIXME: Use len(methods) return (*interfaceType)(unsafe.Pointer(t)).ptrTo.NumMethod() } // Other types have no methods attached. Note we don't panic here. return 0 } // Read and return a null terminated string starting from data. func readStringZ(data unsafe.Pointer) string { start := data var len uintptr for *(*byte)(data) != 0 { len++ data = unsafe.Add(data, 1) // C: data++ } return *(*string)(unsafe.Pointer(&stringHeader{ data: start, len: len, })) } func (t *RawType) name() string { ntype := (*namedType)(unsafe.Pointer(t)) return readStringZ(unsafe.Pointer(&ntype.name[0])) } func (t *RawType) Name() string { if t.isNamed() { name := t.name() for i := 0; i < len(name); i++ { if name[i] == '.' { return name[i+1:] } } panic("corrupt name data") } if kind := t.Kind(); kind < UnsafePointer { return t.Kind().String() } else if kind == UnsafePointer { return "Pointer" } return "" } func (t *RawType) Key() Type { return t.key() } // OverflowComplex reports whether the complex128 x cannot be represented by type t. // It panics if t's Kind is not Complex64 or Complex128. func (t RawType) OverflowComplex(x complex128) bool { k := t.Kind() switch k { case Complex64: return overflowFloat32(real(x)) || overflowFloat32(imag(x)) case Complex128: return false } panic("reflect: OverflowComplex of non-complex type") } // OverflowFloat reports whether the float64 x cannot be represented by type t. // It panics if t's Kind is not Float32 or Float64. func (t RawType) OverflowFloat(x float64) bool { k := t.Kind() switch k { case Float32: return overflowFloat32(x) case Float64: return false } panic("reflect: OverflowFloat of non-float type") } // OverflowInt reports whether the int64 x cannot be represented by type t. // It panics if t's Kind is not Int, Int8, Int16, Int32, or Int64. func (t RawType) OverflowInt(x int64) bool { k := t.Kind() switch k { case Int, Int8, Int16, Int32, Int64: bitSize := t.Size() * 8 trunc := (x << (64 - bitSize)) >> (64 - bitSize) return x != trunc } panic("reflect: OverflowInt of non-int type") } // OverflowUint reports whether the uint64 x cannot be represented by type t. // It panics if t's Kind is not Uint, Uintptr, Uint8, Uint16, Uint32, or Uint64. func (t RawType) OverflowUint(x uint64) bool { k := t.Kind() switch k { case Uint, Uintptr, Uint8, Uint16, Uint32, Uint64: bitSize := t.Size() * 8 trunc := (x << (64 - bitSize)) >> (64 - bitSize) return x != trunc } panic("reflect: OverflowUint of non-uint type") } func (t *RawType) PkgPath() string { if t.isNamed() { ntype := (*namedType)(unsafe.Pointer(t)) return readStringZ(unsafe.Pointer(ntype.pkg)) } return "" } func (t *RawType) FieldByName(name string) (StructField, bool) { if t.Kind() != Struct { panic(errTypeFieldByName) } field, index, ok := t.rawFieldByNameFunc(func(n string) bool { return n == name }) if !ok { return StructField{}, false } return StructField{ Name: field.Name, PkgPath: field.PkgPath, Type: field.Type, // note: converts RawType to Type Tag: field.Tag, Anonymous: field.Anonymous, Offset: field.Offset, Index: index, }, true } func (t *RawType) FieldByNameFunc(match func(string) bool) (StructField, bool) { if t.Kind() != Struct { panic(TypeError{"FieldByNameFunc"}) } field, index, ok := t.rawFieldByNameFunc(match) if !ok { return StructField{}, false } return StructField{ Name: field.Name, PkgPath: field.PkgPath, Type: field.Type, // note: converts RawType to Type Tag: field.Tag, Anonymous: field.Anonymous, Offset: field.Offset, Index: index, }, true } func (t *RawType) FieldByIndex(index []int) StructField { ftype := t var field rawStructField for _, n := range index { structOrPtrToStruct := ftype.Kind() == Struct || (ftype.Kind() == Pointer && ftype.elem().Kind() == Struct) if !structOrPtrToStruct { panic(errTypeFieldByIndex) } if ftype.Kind() == Pointer { ftype = ftype.elem() } field = ftype.rawField(n) ftype = field.Type } return StructField{ Name: field.Name, PkgPath: field.PkgPath, Type: field.Type, // note: converts RawType to Type Tag: field.Tag, Anonymous: field.Anonymous, Offset: field.Offset, Index: index, } } // A StructField describes a single field in a struct. // This must be kept in sync with [reflect.StructField]. type StructField struct { // Name indicates the field name. Name string // PkgPath is the package path where the struct containing this field is // declared for unexported fields, or the empty string for exported fields. PkgPath string Type Type Tag StructTag // field tag string Offset uintptr Index []int // index sequence for Type.FieldByIndex Anonymous bool } // IsExported reports whether the field is exported. func (f StructField) IsExported() bool { return f.PkgPath == "" } // rawStructField is the same as StructField but with the Type member replaced // with RawType. For internal use only. Avoiding this conversion to the Type // interface improves code size in many cases. type rawStructField struct { Name string PkgPath string Type *RawType Tag StructTag Offset uintptr Anonymous bool } // A StructTag is the tag string in a struct field. type StructTag string // TODO: it would be feasible to do the key/value splitting at compile time, // avoiding the code size cost of doing it at runtime // Get returns the value associated with key in the tag string. func (tag StructTag) Get(key string) string { v, _ := tag.Lookup(key) return v } // Lookup returns the value associated with key in the tag string. func (tag StructTag) Lookup(key string) (value string, ok bool) { for tag != "" { // Skip leading space. i := 0 for i < len(tag) && tag[i] == ' ' { i++ } tag = tag[i:] if tag == "" { break } // Scan to colon. A space, a quote or a control character is a syntax error. // Strictly speaking, control chars include the range [0x7f, 0x9f], not just // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters // as it is simpler to inspect the tag's bytes than the tag's runes. i = 0 for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { i++ } if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { break } name := string(tag[:i]) tag = tag[i+1:] // Scan quoted string to find value. i = 1 for i < len(tag) && tag[i] != '"' { if tag[i] == '\\' { i++ } i++ } if i >= len(tag) { break } qvalue := string(tag[:i+1]) tag = tag[i+1:] if key == name { value, err := unquote(qvalue) if err != nil { break } return value, true } } return "", false } // TypeError is the error that is used in a panic when invoking a method on a // type that is not applicable to that type. type TypeError struct { Method string } func (e *TypeError) Error() string { return "reflect: call of reflect.Type." + e.Method + " on invalid type" } func align(offset uintptr, alignment uintptr) uintptr { return (offset + alignment - 1) &^ (alignment - 1) } func SliceOf(t Type) Type { panic("unimplemented: reflect.SliceOf()") } func ArrayOf(n int, t Type) Type { panic("unimplemented: reflect.ArrayOf()") } func StructOf([]StructField) Type { panic("unimplemented: reflect.StructOf()") } func MapOf(key, value Type) Type { panic("unimplemented: reflect.MapOf()") } func FuncOf(in, out []Type, variadic bool) Type { panic("unimplemented: reflect.FuncOf()") } const maxVarintLen32 = 5 // encoding/binary.Uvarint, specialized for uint32 func uvarint32(buf []byte) (uint32, int) { var x uint32 var s uint for i, b := range buf { if b < 0x80 { return x | uint32(b)< unsafe.Sizeof(uintptr(0)) { return int64(*(*int)(v.value)) } else { return int64(int(uintptr(v.value))) } case Int8: if v.isIndirect() { return int64(*(*int8)(v.value)) } else { return int64(int8(uintptr(v.value))) } case Int16: if v.isIndirect() { return int64(*(*int16)(v.value)) } else { return int64(int16(uintptr(v.value))) } case Int32: if v.isIndirect() || unsafe.Sizeof(int32(0)) > unsafe.Sizeof(uintptr(0)) { return int64(*(*int32)(v.value)) } else { return int64(int32(uintptr(v.value))) } case Int64: if v.isIndirect() || unsafe.Sizeof(int64(0)) > unsafe.Sizeof(uintptr(0)) { return int64(*(*int64)(v.value)) } else { return int64(int64(uintptr(v.value))) } default: panic(&ValueError{Method: "Int", Kind: v.Kind()}) } } // CanUint reports whether Uint can be used without panicking. func (v Value) CanUint() bool { switch v.Kind() { case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: return true default: return false } } func (v Value) Uint() uint64 { switch v.Kind() { case Uintptr: if v.isIndirect() { return uint64(*(*uintptr)(v.value)) } else { return uint64(uintptr(v.value)) } case Uint8: if v.isIndirect() { return uint64(*(*uint8)(v.value)) } else { return uint64(uintptr(v.value)) } case Uint16: if v.isIndirect() { return uint64(*(*uint16)(v.value)) } else { return uint64(uintptr(v.value)) } case Uint: if v.isIndirect() || unsafe.Sizeof(uint(0)) > unsafe.Sizeof(uintptr(0)) { return uint64(*(*uint)(v.value)) } else { return uint64(uintptr(v.value)) } case Uint32: if v.isIndirect() || unsafe.Sizeof(uint32(0)) > unsafe.Sizeof(uintptr(0)) { return uint64(*(*uint32)(v.value)) } else { return uint64(uintptr(v.value)) } case Uint64: if v.isIndirect() || unsafe.Sizeof(uint64(0)) > unsafe.Sizeof(uintptr(0)) { return uint64(*(*uint64)(v.value)) } else { return uint64(uintptr(v.value)) } default: panic(&ValueError{Method: "Uint", Kind: v.Kind()}) } } // CanFloat reports whether Float can be used without panicking. func (v Value) CanFloat() bool { switch v.Kind() { case Float32, Float64: return true default: return false } } func (v Value) Float32() float32 { switch v.Kind() { case Float32: if v.isIndirect() || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) { // The float is stored as an external value on systems with 16-bit // pointers. return *(*float32)(v.value) } else { // The float is directly stored in the interface value on systems // with 32-bit and 64-bit pointers. return *(*float32)(unsafe.Pointer(&v.value)) } case Float64: return float32(v.Float()) } panic(&ValueError{Method: "Float", Kind: v.Kind()}) } func (v Value) Float() float64 { switch v.Kind() { case Float32: if v.isIndirect() || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) { // The float is stored as an external value on systems with 16-bit // pointers. return float64(*(*float32)(v.value)) } else { // The float is directly stored in the interface value on systems // with 32-bit and 64-bit pointers. return float64(*(*float32)(unsafe.Pointer(&v.value))) } case Float64: if v.isIndirect() || unsafe.Sizeof(float64(0)) > unsafe.Sizeof(uintptr(0)) { // For systems with 16-bit and 32-bit pointers. return *(*float64)(v.value) } else { // The float is directly stored in the interface value on systems // with 64-bit pointers. return *(*float64)(unsafe.Pointer(&v.value)) } default: panic(&ValueError{Method: "Float", Kind: v.Kind()}) } } // CanComplex reports whether Complex can be used without panicking. func (v Value) CanComplex() bool { switch v.Kind() { case Complex64, Complex128: return true default: return false } } func (v Value) Complex() complex128 { switch v.Kind() { case Complex64: if v.isIndirect() || unsafe.Sizeof(complex64(0)) > unsafe.Sizeof(uintptr(0)) { // The complex number is stored as an external value on systems with // 16-bit and 32-bit pointers. return complex128(*(*complex64)(v.value)) } else { // The complex number is directly stored in the interface value on // systems with 64-bit pointers. return complex128(*(*complex64)(unsafe.Pointer(&v.value))) } case Complex128: // This is a 128-bit value, which is always stored as an external value. // It may be stored in the pointer directly on very uncommon // architectures with 128-bit pointers, however. return *(*complex128)(v.value) default: panic(&ValueError{Method: "Complex", Kind: v.Kind()}) } } func (v Value) String() string { switch v.Kind() { case String: // A string value is always bigger than a pointer as it is made of a // pointer and a length. return *(*string)(v.value) default: // Special case because of the special treatment of .String() in Go. return "<" + v.typecode.String() + " Value>" } } func (v Value) Bytes() []byte { switch v.Kind() { case Slice: if v.typecode.elem().Kind() != Uint8 { panic(&ValueError{Method: "Bytes", Kind: v.Kind()}) } return *(*[]byte)(v.value) case Array: v.checkAddressable() if v.typecode.elem().Kind() != Uint8 { panic(&ValueError{Method: "Bytes", Kind: v.Kind()}) } // Small inline arrays are not addressable, so we only have to // handle addressable arrays which will be stored as pointers // in v.value return unsafe.Slice((*byte)(v.value), v.Len()) } panic(&ValueError{Method: "Bytes", Kind: v.Kind()}) } func (v Value) Slice(i, j int) Value { switch v.Kind() { case Slice: hdr := *(*sliceHeader)(v.value) i, j := uintptr(i), uintptr(j) if j < i || hdr.cap < j { slicePanic() } elemSize := v.typecode.underlying().elem().Size() hdr.len = j - i hdr.cap = hdr.cap - i hdr.data = unsafe.Add(hdr.data, i*elemSize) return Value{ typecode: v.typecode, value: unsafe.Pointer(&hdr), flags: v.flags, } case Array: v.checkAddressable() buf, length := buflen(v) i, j := uintptr(i), uintptr(j) if j < i || length < j { slicePanic() } elemSize := v.typecode.underlying().elem().Size() var hdr sliceHeader hdr.len = j - i hdr.cap = length - i hdr.data = unsafe.Add(buf, i*elemSize) sliceType := (*arrayType)(unsafe.Pointer(v.typecode.underlying())).slicePtr return Value{ typecode: sliceType, value: unsafe.Pointer(&hdr), flags: v.flags, } case String: i, j := uintptr(i), uintptr(j) str := *(*stringHeader)(v.value) if j < i || str.len < j { slicePanic() } hdr := stringHeader{ data: unsafe.Add(str.data, i), len: j - i, } return Value{ typecode: v.typecode, value: unsafe.Pointer(&hdr), flags: v.flags, } } panic(&ValueError{Method: "Slice", Kind: v.Kind()}) } func (v Value) Slice3(i, j, k int) Value { switch v.Kind() { case Slice: hdr := *(*sliceHeader)(v.value) i, j, k := uintptr(i), uintptr(j), uintptr(k) if j < i || k < j || hdr.len < k { slicePanic() } elemSize := v.typecode.underlying().elem().Size() hdr.len = j - i hdr.cap = k - i hdr.data = unsafe.Add(hdr.data, i*elemSize) return Value{ typecode: v.typecode, value: unsafe.Pointer(&hdr), flags: v.flags, } case Array: v.checkAddressable() buf, length := buflen(v) i, j, k := uintptr(i), uintptr(j), uintptr(k) if j < i || k < j || length < k { slicePanic() } elemSize := v.typecode.underlying().elem().Size() var hdr sliceHeader hdr.len = j - i hdr.cap = k - i hdr.data = unsafe.Add(buf, i*elemSize) sliceType := (*arrayType)(unsafe.Pointer(v.typecode.underlying())).slicePtr return Value{ typecode: sliceType, value: unsafe.Pointer(&hdr), flags: v.flags, } } panic("unimplemented: (reflect.Value).Slice3()") } //go:linkname maplen runtime.hashmapLen func maplen(p unsafe.Pointer) int //go:linkname chanlen runtime.chanLen func chanlen(p unsafe.Pointer) int // Len returns the length of this value for slices, strings, arrays, channels, // and maps. For other types, it panics. func (v Value) Len() int { switch v.typecode.Kind() { case Array: return v.typecode.Len() case Chan: return chanlen(v.pointer()) case Map: return maplen(v.pointer()) case Slice: return int((*sliceHeader)(v.value).len) case String: return int((*stringHeader)(v.value).len) default: panic(&ValueError{Method: "Len", Kind: v.Kind()}) } } //go:linkname chancap runtime.chanCap func chancap(p unsafe.Pointer) int // Cap returns the capacity of this value for arrays, channels and slices. // For other types, it panics. func (v Value) Cap() int { switch v.typecode.Kind() { case Array: return v.typecode.Len() case Chan: return chancap(v.pointer()) case Slice: return int((*sliceHeader)(v.value).cap) default: panic(&ValueError{Method: "Cap", Kind: v.Kind()}) } } //go:linkname mapclear runtime.hashmapClear func mapclear(p unsafe.Pointer) // Clear clears the contents of a map or zeros the contents of a slice // // It panics if v's Kind is not Map or Slice. func (v Value) Clear() { switch v.typecode.Kind() { case Map: mapclear(v.pointer()) case Slice: hdr := (*sliceHeader)(v.value) elemSize := v.typecode.underlying().elem().Size() memzero(hdr.data, elemSize*hdr.len) default: panic(&ValueError{Method: "Clear", Kind: v.Kind()}) } } // NumField returns the number of fields of this struct. It panics for other // value types. func (v Value) NumField() int { return v.typecode.NumField() } func (v Value) Elem() Value { switch v.Kind() { case Ptr: ptr := v.pointer() if ptr == nil { return Value{} } // Don't copy RO flags flags := (v.flags & (valueFlagIndirect | valueFlagExported)) | valueFlagIndirect return Value{ typecode: v.typecode.elem(), value: ptr, flags: flags, } case Interface: typecode, value := decomposeInterface(*(*interface{})(v.value)) return Value{ typecode: (*RawType)(typecode), value: value, flags: v.flags &^ valueFlagIndirect, } default: panic(&ValueError{Method: "Elem", Kind: v.Kind()}) } } // Field returns the value of the i'th field of this struct. func (v Value) Field(i int) Value { if v.Kind() != Struct { panic(&ValueError{Method: "Field", Kind: v.Kind()}) } structField := v.typecode.rawField(i) // Copy flags but clear EmbedRO; we're not an embedded field anymore flags := v.flags & ^valueFlagEmbedRO if structField.PkgPath != "" { // No PkgPath => not exported. // Clear exported flag even if the parent was exported. flags &^= valueFlagExported // Update the RO flag if structField.Anonymous { // Embedded field flags |= valueFlagEmbedRO } else { flags |= valueFlagStickyRO } } else { // Parent field may not have been exported but we are flags |= valueFlagExported } size := v.typecode.Size() fieldType := structField.Type fieldSize := fieldType.Size() if v.isIndirect() || fieldSize > unsafe.Sizeof(uintptr(0)) { // v.value was already a pointer to the value and it should stay that // way. return Value{ flags: flags, typecode: fieldType, value: unsafe.Add(v.value, structField.Offset), } } // The fieldSize is smaller than uintptr, which means that the value will // have to be stored directly in the interface value. if fieldSize == 0 { // The struct field is zero sized. // This is a rare situation, but because it's undefined behavior // to shift the size of the value (zeroing the value), handle this // situation explicitly. return Value{ flags: flags, typecode: fieldType, value: unsafe.Pointer(nil), } } if size > unsafe.Sizeof(uintptr(0)) { // The value was not stored in the interface before but will be // afterwards, so load the value (from the correct offset) and return // it. ptr := unsafe.Add(v.value, structField.Offset) value := unsafe.Pointer(loadValue(ptr, fieldSize)) return Value{ flags: flags &^ valueFlagIndirect, typecode: fieldType, value: value, } } // The value was already stored directly in the interface and it still // is. Cut out the part of the value that we need. value := maskAndShift(uintptr(v.value), structField.Offset, fieldSize) return Value{ flags: flags, typecode: fieldType, value: unsafe.Pointer(value), } } var uint8Type = TypeOf(uint8(0)).(*RawType) func (v Value) Index(i int) Value { switch v.Kind() { case Slice: // Extract an element from the slice. slice := *(*sliceHeader)(v.value) if uint(i) >= uint(slice.len) { panic("reflect: slice index out of range") } flags := (v.flags & (valueFlagExported | valueFlagIndirect)) | valueFlagIndirect | v.flags.ro() elem := Value{ typecode: v.typecode.elem(), flags: flags, } elem.value = unsafe.Add(slice.data, elem.typecode.Size()*uintptr(i)) // pointer to new value return elem case String: // Extract a character from a string. // A string is never stored directly in the interface, but always as a // pointer to the string value. // Keeping valueFlagExported if set, but don't set valueFlagIndirect // otherwise CanSet will return true for string elements (which is bad, // strings are read-only). s := *(*stringHeader)(v.value) if uint(i) >= uint(s.len) { panic("reflect: string index out of range") } return Value{ typecode: uint8Type, value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Add(s.data, i)))), flags: v.flags & valueFlagExported, } case Array: // Extract an element from the array. elemType := v.typecode.elem() elemSize := elemType.Size() size := v.typecode.Size() if size == 0 { // The element size is 0 and/or the length of the array is 0. return Value{ typecode: v.typecode.elem(), flags: v.flags, } } if elemSize > unsafe.Sizeof(uintptr(0)) { // The resulting value doesn't fit in a pointer so must be // indirect. Also, because size != 0 this implies that the array // length must be != 0, and thus that the total size is at least // elemSize. addr := unsafe.Add(v.value, elemSize*uintptr(i)) // pointer to new value return Value{ typecode: v.typecode.elem(), flags: v.flags, value: addr, } } if size > unsafe.Sizeof(uintptr(0)) || v.isIndirect() { // The element fits in a pointer, but the array is not stored in the pointer directly. // Load the value from the pointer. addr := unsafe.Add(v.value, elemSize*uintptr(i)) // pointer to new value value := addr if !v.isIndirect() { // Use a pointer to the value (don't load the value) if the // 'indirect' flag is set. value = unsafe.Pointer(loadValue(addr, elemSize)) } return Value{ typecode: v.typecode.elem(), flags: v.flags, value: value, } } // The value fits in a pointer, so extract it with some shifting and // masking. offset := elemSize * uintptr(i) value := maskAndShift(uintptr(v.value), offset, elemSize) return Value{ typecode: v.typecode.elem(), flags: v.flags, value: unsafe.Pointer(value), } default: panic(&ValueError{Method: "Index", Kind: v.Kind()}) } } func (v Value) NumMethod() int { if v.typecode == nil { panic(&ValueError{Method: "reflect.Value.NumMethod", Kind: Invalid}) } return v.typecode.NumMethod() } // OverflowFloat reports whether the float64 x cannot be represented by v's type. // It panics if v's Kind is not Float32 or Float64. func (v Value) OverflowFloat(x float64) bool { k := v.Kind() switch k { case Float32: return overflowFloat32(x) case Float64: return false } panic(&ValueError{Method: "reflect.Value.OverflowFloat", Kind: v.Kind()}) } func overflowFloat32(x float64) bool { if x < 0 { x = -x } return math.MaxFloat32 < x && x <= math.MaxFloat64 } func (v Value) MapKeys() []Value { if v.Kind() != Map { panic(&ValueError{Method: "MapKeys", Kind: v.Kind()}) } // empty map if v.Len() == 0 { return nil } keys := make([]Value, 0, v.Len()) it := hashmapNewIterator() k := New(v.typecode.Key()) e := New(v.typecode.Elem()) keyType := v.typecode.key() keyTypeIsEmptyInterface := keyType.Kind() == Interface && keyType.NumMethod() == 0 shouldUnpackInterface := !keyTypeIsEmptyInterface && keyType.Kind() != String && !keyType.isBinary() for hashmapNext(v.pointer(), it, k.value, e.value) { if shouldUnpackInterface { intf := *(*interface{})(k.value) v := ValueOf(intf) keys = append(keys, v) } else { keys = append(keys, k.Elem()) } k = New(v.typecode.Key()) } return keys } //go:linkname hashmapStringGet runtime.hashmapStringGet func hashmapStringGet(m unsafe.Pointer, key string, value unsafe.Pointer, valueSize uintptr) bool //go:linkname hashmapBinaryGet runtime.hashmapBinaryGet func hashmapBinaryGet(m unsafe.Pointer, key, value unsafe.Pointer, valueSize uintptr) bool //go:linkname hashmapInterfaceGet runtime.hashmapInterfaceGet func hashmapInterfaceGet(m unsafe.Pointer, key interface{}, value unsafe.Pointer, valueSize uintptr) bool func (v Value) MapIndex(key Value) Value { if v.Kind() != Map { panic(&ValueError{Method: "MapIndex", Kind: v.Kind()}) } vkey := v.typecode.key() // compare key type with actual key type of map if !key.typecode.AssignableTo(vkey) { // type error? panic("reflect.Value.MapIndex: incompatible types for key") } elemType := v.typecode.Elem() elem := New(elemType) if vkey.Kind() == String { if ok := hashmapStringGet(v.pointer(), *(*string)(key.value), elem.value, elemType.Size()); !ok { return Value{} } return elem.Elem() } else if vkey.isBinary() { var keyptr unsafe.Pointer if key.isIndirect() || key.typecode.Size() > unsafe.Sizeof(uintptr(0)) { keyptr = key.value } else { keyptr = unsafe.Pointer(&key.value) } //TODO(dgryski): zero out padding bytes in key, if any if ok := hashmapBinaryGet(v.pointer(), keyptr, elem.value, elemType.Size()); !ok { return Value{} } return elem.Elem() } else { if ok := hashmapInterfaceGet(v.pointer(), key.Interface(), elem.value, elemType.Size()); !ok { return Value{} } return elem.Elem() } } //go:linkname hashmapNewIterator runtime.hashmapNewIterator func hashmapNewIterator() unsafe.Pointer //go:linkname hashmapNext runtime.hashmapNext func hashmapNext(m unsafe.Pointer, it unsafe.Pointer, key, value unsafe.Pointer) bool func (v Value) MapRange() *MapIter { iter := &MapIter{} iter.Reset(v) return iter } type MapIter struct { m Value it unsafe.Pointer key Value val Value valid bool unpackKeyInterface bool } func (it *MapIter) Key() Value { if !it.valid { panic("reflect.MapIter.Key called on invalid iterator") } if it.unpackKeyInterface { intf := *(*interface{})(it.key.value) v := ValueOf(intf) return v } return it.key.Elem() } func (v Value) SetIterKey(iter *MapIter) { v.Set(iter.Key()) } func (it *MapIter) Value() Value { if !it.valid { panic("reflect.MapIter.Value called on invalid iterator") } return it.val.Elem() } func (v Value) SetIterValue(iter *MapIter) { v.Set(iter.Value()) } func (it *MapIter) Next() bool { it.key = New(it.m.typecode.Key()) it.val = New(it.m.typecode.Elem()) it.valid = hashmapNext(it.m.pointer(), it.it, it.key.value, it.val.value) return it.valid } func (iter *MapIter) Reset(v Value) { if v.Kind() != Map { panic(&ValueError{Method: "MapRange", Kind: v.Kind()}) } keyType := v.typecode.key() keyTypeIsEmptyInterface := keyType.Kind() == Interface && keyType.NumMethod() == 0 shouldUnpackInterface := !keyTypeIsEmptyInterface && keyType.Kind() != String && !keyType.isBinary() *iter = MapIter{ m: v, it: hashmapNewIterator(), unpackKeyInterface: shouldUnpackInterface, } } func (v Value) Set(x Value) { v.checkAddressable() v.checkRO() if !x.typecode.AssignableTo(v.typecode) { panic("reflect.Value.Set: value of type " + x.typecode.String() + " cannot be assigned to type " + v.typecode.String()) } if v.typecode.Kind() == Interface && x.typecode.Kind() != Interface { // move the value of x back into the interface, if possible if x.isIndirect() && x.typecode.Size() <= unsafe.Sizeof(uintptr(0)) { x.value = unsafe.Pointer(loadValue(x.value, x.typecode.Size())) } intf := composeInterface(unsafe.Pointer(x.typecode), x.value) x = Value{ typecode: v.typecode, value: unsafe.Pointer(&intf), } } size := v.typecode.Size() if size <= unsafe.Sizeof(uintptr(0)) && !x.isIndirect() { storeValue(v.value, size, uintptr(x.value)) } else { memcpy(v.value, x.value, size) } } func (v Value) SetZero() { v.checkAddressable() v.checkRO() size := v.typecode.Size() memzero(v.value, size) } func (v Value) SetBool(x bool) { v.checkAddressable() v.checkRO() switch v.Kind() { case Bool: *(*bool)(v.value) = x default: panic(&ValueError{Method: "SetBool", Kind: v.Kind()}) } } func (v Value) SetInt(x int64) { v.checkAddressable() v.checkRO() switch v.Kind() { case Int: *(*int)(v.value) = int(x) case Int8: *(*int8)(v.value) = int8(x) case Int16: *(*int16)(v.value) = int16(x) case Int32: *(*int32)(v.value) = int32(x) case Int64: *(*int64)(v.value) = x default: panic(&ValueError{Method: "SetInt", Kind: v.Kind()}) } } func (v Value) SetUint(x uint64) { v.checkAddressable() v.checkRO() switch v.Kind() { case Uint: *(*uint)(v.value) = uint(x) case Uint8: *(*uint8)(v.value) = uint8(x) case Uint16: *(*uint16)(v.value) = uint16(x) case Uint32: *(*uint32)(v.value) = uint32(x) case Uint64: *(*uint64)(v.value) = x case Uintptr: *(*uintptr)(v.value) = uintptr(x) default: panic(&ValueError{Method: "SetUint", Kind: v.Kind()}) } } func (v Value) SetFloat(x float64) { v.checkAddressable() v.checkRO() switch v.Kind() { case Float32: *(*float32)(v.value) = float32(x) case Float64: *(*float64)(v.value) = x default: panic(&ValueError{Method: "SetFloat", Kind: v.Kind()}) } } func (v Value) SetComplex(x complex128) { v.checkAddressable() v.checkRO() switch v.Kind() { case Complex64: *(*complex64)(v.value) = complex64(x) case Complex128: *(*complex128)(v.value) = x default: panic(&ValueError{Method: "SetComplex", Kind: v.Kind()}) } } func (v Value) SetString(x string) { v.checkAddressable() v.checkRO() switch v.Kind() { case String: *(*string)(v.value) = x default: panic(&ValueError{Method: "SetString", Kind: v.Kind()}) } } func (v Value) SetBytes(x []byte) { v.checkAddressable() v.checkRO() if v.typecode.Kind() != Slice || v.typecode.elem().Kind() != Uint8 { panic("reflect.Value.SetBytes called on not []byte") } // copy the header contents over *(*[]byte)(v.value) = x } func (v Value) SetCap(n int) { panic("unimplemented: (reflect.Value).SetCap()") } func (v Value) SetLen(n int) { if v.typecode.Kind() != Slice { panic(&ValueError{Method: "reflect.Value.SetLen", Kind: v.Kind()}) } v.checkAddressable() hdr := (*sliceHeader)(v.value) if int(uintptr(n)) != n || uintptr(n) > hdr.cap { panic("reflect.Value.SetLen: slice length out of range") } hdr.len = uintptr(n) } func (v Value) checkAddressable() { if !v.isIndirect() { panic("reflect: value is not addressable") } } // OverflowInt reports whether the int64 x cannot be represented by v's type. // It panics if v's Kind is not Int, Int8, Int16, Int32, or Int64. func (v Value) OverflowInt(x int64) bool { switch v.Kind() { case Int, Int8, Int16, Int32, Int64: bitSize := v.typecode.Size() * 8 trunc := (x << (64 - bitSize)) >> (64 - bitSize) return x != trunc } panic(&ValueError{Method: "reflect.Value.OverflowInt", Kind: v.Kind()}) } // OverflowUint reports whether the uint64 x cannot be represented by v's type. // It panics if v's Kind is not Uint, Uintptr, Uint8, Uint16, Uint32, or Uint64. func (v Value) OverflowUint(x uint64) bool { k := v.Kind() switch k { case Uint, Uintptr, Uint8, Uint16, Uint32, Uint64: bitSize := v.typecode.Size() * 8 trunc := (x << (64 - bitSize)) >> (64 - bitSize) return x != trunc } panic(&ValueError{Method: "reflect.Value.OverflowUint", Kind: v.Kind()}) } func (v Value) CanConvert(t Type) bool { // TODO: Optimize this to not actually perform a conversion _, ok := convertOp(v, t) return ok } func (v Value) Convert(t Type) Value { if v, ok := convertOp(v, t); ok { return v } panic("reflect.Value.Convert: value of type " + v.typecode.String() + " cannot be converted to type " + t.String()) } func convertOp(src Value, typ Type) (Value, bool) { // Easy check first. Do we even need to do anything? if src.typecode.underlying() == typ.(*RawType).underlying() { return Value{ typecode: typ.(*RawType), value: src.value, flags: src.flags, }, true } if rtype := typ.(*RawType); rtype.Kind() == Interface && rtype.NumMethod() == 0 { iface := composeInterface(unsafe.Pointer(src.typecode), src.value) return Value{ typecode: rtype, value: unsafe.Pointer(&iface), flags: valueFlagExported, }, true } switch src.Kind() { case Int, Int8, Int16, Int32, Int64: switch rtype := typ.(*RawType); rtype.Kind() { case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: return cvtInt(src, rtype), true case Float32, Float64: return cvtIntFloat(src, rtype), true case String: return cvtIntString(src, rtype), true } case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: switch rtype := typ.(*RawType); rtype.Kind() { case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: return cvtUint(src, rtype), true case Float32, Float64: return cvtUintFloat(src, rtype), true case String: return cvtUintString(src, rtype), true } case Float32, Float64: switch rtype := typ.(*RawType); rtype.Kind() { case Int, Int8, Int16, Int32, Int64: return cvtFloatInt(src, rtype), true case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: return cvtFloatUint(src, rtype), true case Float32, Float64: return cvtFloat(src, rtype), true } /* case Complex64, Complex128: switch src.Kind() { case Complex64, Complex128: return cvtComplex } */ case Slice: switch rtype := typ.(*RawType); rtype.Kind() { case Array: if src.typecode.elem() == rtype.elem() && rtype.Len() <= src.Len() { return Value{ typecode: rtype, value: (*sliceHeader)(src.value).data, flags: src.flags | valueFlagIndirect, }, true } case Pointer: if rtype.Elem().Kind() == Array { if src.typecode.elem() == rtype.elem().elem() && rtype.elem().Len() <= src.Len() { return Value{ typecode: rtype, value: (*sliceHeader)(src.value).data, flags: src.flags & (valueFlagExported | valueFlagRO), }, true } } case String: if !src.typecode.elem().isNamed() { switch src.Type().Elem().Kind() { case Uint8: return cvtBytesString(src, rtype), true case Int32: return cvtRunesString(src, rtype), true } } } case String: rtype := typ.(*RawType) if typ.Kind() == Slice && !rtype.elem().isNamed() { switch typ.Elem().Kind() { case Uint8: return cvtStringBytes(src, rtype), true case Int32: return cvtStringRunes(src, rtype), true } } } // TODO(dgryski): Unimplemented: // Chan // Non-defined pointers types with same underlying base type // Interface <-> Type conversions return Value{}, false } func cvtInt(v Value, t *RawType) Value { return makeInt(v.flags, uint64(v.Int()), t) } func cvtUint(v Value, t *RawType) Value { return makeInt(v.flags, v.Uint(), t) } func cvtIntFloat(v Value, t *RawType) Value { return makeFloat(v.flags, float64(v.Int()), t) } func cvtUintFloat(v Value, t *RawType) Value { return makeFloat(v.flags, float64(v.Uint()), t) } func cvtFloatInt(v Value, t *RawType) Value { return makeInt(v.flags, uint64(int64(v.Float())), t) } func cvtFloatUint(v Value, t *RawType) Value { return makeInt(v.flags, uint64(v.Float()), t) } func cvtFloat(v Value, t *RawType) Value { if v.Type().Kind() == Float32 && t.Kind() == Float32 { // Don't do any conversion if both types have underlying type float32. // This avoids converting to float64 and back, which will // convert a signaling NaN to a quiet NaN. See issue 36400. return makeFloat32(v.flags, v.Float32(), t) } return makeFloat(v.flags, v.Float(), t) } //go:linkname stringToBytes runtime.stringToBytes func stringToBytes(x string) []byte func cvtStringBytes(v Value, t *RawType) Value { b := stringToBytes(*(*string)(v.value)) return Value{ typecode: t, value: unsafe.Pointer(&b), flags: v.flags, } } //go:linkname stringFromBytes runtime.stringFromBytes func stringFromBytes(x []byte) string func cvtBytesString(v Value, t *RawType) Value { s := stringFromBytes(*(*[]byte)(v.value)) return Value{ typecode: t, value: unsafe.Pointer(&s), flags: v.flags, } } func makeInt(flags valueFlags, bits uint64, t *RawType) Value { size := t.Size() v := Value{ typecode: t, flags: flags, } ptr := unsafe.Pointer(&v.value) if size > unsafe.Sizeof(uintptr(0)) { ptr = alloc(size, nil) v.value = ptr } switch size { case 1: *(*uint8)(ptr) = uint8(bits) case 2: *(*uint16)(ptr) = uint16(bits) case 4: *(*uint32)(ptr) = uint32(bits) case 8: *(*uint64)(ptr) = bits } return v } func makeFloat(flags valueFlags, f float64, t *RawType) Value { size := t.Size() v := Value{ typecode: t, flags: flags, } ptr := unsafe.Pointer(&v.value) if size > unsafe.Sizeof(uintptr(0)) { ptr = alloc(size, nil) v.value = ptr } switch size { case 4: *(*float32)(ptr) = float32(f) case 8: *(*float64)(ptr) = f } return v } func makeFloat32(flags valueFlags, f float32, t *RawType) Value { v := Value{ typecode: t, flags: flags, } *(*float32)(unsafe.Pointer(&v.value)) = float32(f) return v } func cvtIntString(src Value, t *RawType) Value { panic("cvtUintString: unimplemented") } func cvtUintString(src Value, t *RawType) Value { panic("cvtUintString: unimplemented") } func cvtStringRunes(src Value, t *RawType) Value { panic("cvsStringRunes: unimplemented") } func cvtRunesString(src Value, t *RawType) Value { panic("cvsRunesString: unimplemented") } //go:linkname slicePanic runtime.slicePanic func slicePanic() func MakeSlice(typ Type, len, cap int) Value { if typ.Kind() != Slice { panic("reflect.MakeSlice of non-slice type") } rtype := typ.(*RawType) ulen := uint(len) ucap := uint(cap) maxSize := (^uintptr(0)) / 2 elem := rtype.elem() elementSize := elem.Size() if elementSize > 1 { maxSize /= uintptr(elementSize) } if ulen > ucap || ucap > uint(maxSize) { slicePanic() } // This can't overflow because of the above checks. size := uintptr(ucap) * elementSize var slice sliceHeader slice.cap = uintptr(ucap) slice.len = uintptr(ulen) layout := elem.gcLayout() slice.data = alloc(size, layout) return Value{ typecode: rtype, value: unsafe.Pointer(&slice), flags: valueFlagExported, } } var zerobuffer unsafe.Pointer const zerobufferLen = 32 func init() { // 32 characters of zero bytes zerobufferStr := "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" s := (*stringHeader)(unsafe.Pointer(&zerobufferStr)) zerobuffer = s.data } func Zero(typ Type) Value { size := typ.Size() if size <= unsafe.Sizeof(uintptr(0)) { return Value{ typecode: typ.(*RawType), value: nil, flags: valueFlagExported | valueFlagRO, } } if size <= zerobufferLen { return Value{ typecode: typ.(*RawType), value: unsafe.Pointer(zerobuffer), flags: valueFlagExported | valueFlagRO, } } return Value{ typecode: typ.(*RawType), value: alloc(size, nil), flags: valueFlagExported | valueFlagRO, } } // New is the reflect equivalent of the new(T) keyword, returning a pointer to a // new value of the given type. func New(typ Type) Value { return Value{ typecode: pointerTo(typ.(*RawType)), value: alloc(typ.Size(), nil), flags: valueFlagExported, } } type funcHeader struct { Context unsafe.Pointer Code unsafe.Pointer } // Slice header that matches the underlying structure. Used for when we switch // to a precise GC, which needs to know exactly where pointers live. type sliceHeader struct { data unsafe.Pointer len uintptr cap uintptr } // Like sliceHeader, this type is used internally to make sure pointer and // non-pointer fields match those of actual strings. type stringHeader struct { data unsafe.Pointer len uintptr } // Verify SliceHeader and StringHeader sizes. // See https://github.com/tinygo-org/tinygo/pull/4156 // and https://github.com/tinygo-org/tinygo/issues/1284. var ( _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(sliceHeader{})]byte{} _ [unsafe.Sizeof("")]byte = [unsafe.Sizeof(stringHeader{})]byte{} ) type ValueError struct { Method string Kind Kind } func (e *ValueError) Error() string { if e.Kind == 0 { return "reflect: call of " + e.Method + " on zero Value" } return "reflect: call of " + e.Method + " on " + e.Kind.String() + " Value" } //go:linkname memcpy runtime.memcpy func memcpy(dst, src unsafe.Pointer, size uintptr) //go:linkname memzero runtime.memzero func memzero(ptr unsafe.Pointer, size uintptr) //go:linkname alloc runtime.alloc func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer //go:linkname sliceAppend runtime.sliceAppend func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen uintptr, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) //go:linkname sliceCopy runtime.sliceCopy func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr) int // Copy copies the contents of src into dst until either // dst has been filled or src has been exhausted. func Copy(dst, src Value) int { compatibleTypes := false || // dst and src are both slices or arrays with equal types ((dst.typecode.Kind() == Slice || dst.typecode.Kind() == Array) && (src.typecode.Kind() == Slice || src.typecode.Kind() == Array) && (dst.typecode.elem() == src.typecode.elem())) || // dst is array or slice of uint8 and src is string ((dst.typecode.Kind() == Slice || dst.typecode.Kind() == Array) && dst.typecode.elem().Kind() == Uint8 && src.typecode.Kind() == String) if !compatibleTypes { panic("Copy: type mismatch: " + dst.typecode.String() + "/" + src.typecode.String()) } // Can read from an unaddressable array but not write to one. if dst.typecode.Kind() == Array && !dst.isIndirect() { panic("reflect.Copy: unaddressable array value") } dstbuf, dstlen := buflen(dst) srcbuf, srclen := buflen(src) if srclen > 0 { dst.checkRO() } return sliceCopy(dstbuf, srcbuf, dstlen, srclen, dst.typecode.elem().Size()) } func buflen(v Value) (unsafe.Pointer, uintptr) { var buf unsafe.Pointer var len uintptr switch v.typecode.Kind() { case Slice: hdr := (*sliceHeader)(v.value) buf = hdr.data len = hdr.len case Array: if v.isIndirect() || v.typecode.Size() > unsafe.Sizeof(uintptr(0)) { buf = v.value } else { buf = unsafe.Pointer(&v.value) } len = uintptr(v.Len()) case String: hdr := (*stringHeader)(v.value) buf = hdr.data len = hdr.len default: // This shouldn't happen panic("reflect.Copy: not slice or array or string") } return buf, len } //go:linkname sliceGrow runtime.sliceGrow func sliceGrow(buf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) // extend slice to hold n new elements func extendSlice(v Value, n int) sliceHeader { if v.Kind() != Slice { panic(&ValueError{Method: "extendSlice", Kind: v.Kind()}) } var old sliceHeader if v.value != nil { old = *(*sliceHeader)(v.value) } nbuf, nlen, ncap := sliceGrow(old.data, old.len, old.cap, old.len+uintptr(n), v.typecode.elem().Size()) return sliceHeader{ data: nbuf, len: nlen + uintptr(n), cap: ncap, } } // Append appends the values x to a slice s and returns the resulting slice. // As in Go, each x's value must be assignable to the slice's element type. func Append(v Value, x ...Value) Value { if v.Kind() != Slice { panic(&ValueError{Method: "Append", Kind: v.Kind()}) } oldLen := v.Len() newslice := extendSlice(v, len(x)) v.flags = valueFlagExported v.value = (unsafe.Pointer)(&newslice) for i, xx := range x { v.Index(oldLen + i).Set(xx) } return v } // AppendSlice appends a slice t to a slice s and returns the resulting slice. // The slices s and t must have the same element type. func AppendSlice(s, t Value) Value { if s.typecode.Kind() != Slice || t.typecode.Kind() != Slice || s.typecode != t.typecode { // Not a very helpful error message, but shortened to just one error to // keep code size down. panic("reflect.AppendSlice: invalid types") } if !s.isExported() || !t.isExported() { // One of the sides was not exported, so can't access the data. panic("reflect.AppendSlice: unexported") } sSlice := (*sliceHeader)(s.value) tSlice := (*sliceHeader)(t.value) elemSize := s.typecode.elem().Size() ptr, len, cap := sliceAppend(sSlice.data, tSlice.data, sSlice.len, sSlice.cap, tSlice.len, elemSize) result := &sliceHeader{ data: ptr, len: len, cap: cap, } return Value{ typecode: s.typecode, value: unsafe.Pointer(result), flags: valueFlagExported, } } // Grow increases the slice's capacity, if necessary, to guarantee space for // another n elements. After Grow(n), at least n elements can be appended // to the slice without another allocation. // // It panics if v's Kind is not a Slice or if n is negative or too large to // allocate the memory. func (v Value) Grow(n int) { v.checkAddressable() if n < 0 { panic("reflect.Grow: negative length") } if v.Kind() != Slice { panic(&ValueError{Method: "Grow", Kind: v.Kind()}) } slice := (*sliceHeader)(v.value) newslice := extendSlice(v, n) // Only copy the new data and cap: the len remains unchanged. slice.data = newslice.data slice.cap = newslice.cap } //go:linkname hashmapStringSet runtime.hashmapStringSet func hashmapStringSet(m unsafe.Pointer, key string, value unsafe.Pointer) //go:linkname hashmapBinarySet runtime.hashmapBinarySet func hashmapBinarySet(m unsafe.Pointer, key, value unsafe.Pointer) //go:linkname hashmapInterfaceSet runtime.hashmapInterfaceSet func hashmapInterfaceSet(m unsafe.Pointer, key interface{}, value unsafe.Pointer) //go:linkname hashmapStringDelete runtime.hashmapStringDelete func hashmapStringDelete(m unsafe.Pointer, key string) //go:linkname hashmapBinaryDelete runtime.hashmapBinaryDelete func hashmapBinaryDelete(m unsafe.Pointer, key unsafe.Pointer) //go:linkname hashmapInterfaceDelete runtime.hashmapInterfaceDelete func hashmapInterfaceDelete(m unsafe.Pointer, key interface{}) func (v Value) SetMapIndex(key, elem Value) { v.checkRO() if v.Kind() != Map { panic(&ValueError{Method: "SetMapIndex", Kind: v.Kind()}) } vkey := v.typecode.key() // compare key type with actual key type of map if !key.typecode.AssignableTo(vkey) { panic("reflect.Value.SetMapIndex: incompatible types for key") } // if elem is the zero Value, it means delete del := elem == Value{} if !del && !elem.typecode.AssignableTo(v.typecode.elem()) { panic("reflect.Value.SetMapIndex: incompatible types for value") } // make elem an interface if it needs to be converted if v.typecode.elem().Kind() == Interface && elem.typecode.Kind() != Interface { intf := composeInterface(unsafe.Pointer(elem.typecode), elem.value) elem = Value{ typecode: v.typecode.elem(), value: unsafe.Pointer(&intf), } } if key.Kind() == String { if del { hashmapStringDelete(v.pointer(), *(*string)(key.value)) } else { var elemptr unsafe.Pointer if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) { elemptr = elem.value } else { elemptr = unsafe.Pointer(&elem.value) } hashmapStringSet(v.pointer(), *(*string)(key.value), elemptr) } } else if key.typecode.isBinary() { var keyptr unsafe.Pointer if key.isIndirect() || key.typecode.Size() > unsafe.Sizeof(uintptr(0)) { keyptr = key.value } else { keyptr = unsafe.Pointer(&key.value) } if del { hashmapBinaryDelete(v.pointer(), keyptr) } else { var elemptr unsafe.Pointer if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) { elemptr = elem.value } else { elemptr = unsafe.Pointer(&elem.value) } hashmapBinarySet(v.pointer(), keyptr, elemptr) } } else { if del { hashmapInterfaceDelete(v.pointer(), key.Interface()) } else { var elemptr unsafe.Pointer if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) { elemptr = elem.value } else { elemptr = unsafe.Pointer(&elem.value) } hashmapInterfaceSet(v.pointer(), key.Interface(), elemptr) } } } // FieldByIndex returns the nested field corresponding to index. func (v Value) FieldByIndex(index []int) Value { if len(index) == 1 { return v.Field(index[0]) } if v.Kind() != Struct { panic(&ValueError{"FieldByIndex", v.Kind()}) } for i, x := range index { if i > 0 { if v.Kind() == Pointer && v.typecode.elem().Kind() == Struct { if v.IsNil() { panic("reflect: indirection through nil pointer to embedded struct") } v = v.Elem() } } v = v.Field(x) } return v } // FieldByIndexErr returns the nested field corresponding to index. func (v Value) FieldByIndexErr(index []int) (Value, error) { return Value{}, &ValueError{Method: "FieldByIndexErr"} } func (v Value) FieldByName(name string) Value { if v.Kind() != Struct { panic(&ValueError{"FieldByName", v.Kind()}) } if field, ok := v.typecode.FieldByName(name); ok { return v.FieldByIndex(field.Index) } return Value{} } func (v Value) FieldByNameFunc(match func(string) bool) Value { if v.Kind() != Struct { panic(&ValueError{"FieldByName", v.Kind()}) } if field, ok := v.typecode.FieldByNameFunc(match); ok { return v.FieldByIndex(field.Index) } return Value{} } //go:linkname hashmapMake runtime.hashmapMake func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) unsafe.Pointer // MakeMapWithSize creates a new map with the specified type and initial space // for approximately n elements. func MakeMapWithSize(typ Type, n int) Value { // TODO(dgryski): deduplicate these? runtime and reflect both need them. const ( hashmapAlgorithmBinary uint8 = iota hashmapAlgorithmString hashmapAlgorithmInterface ) if typ.Kind() != Map { panic(&ValueError{Method: "MakeMap", Kind: typ.Kind()}) } if n < 0 { panic("reflect.MakeMapWithSize: negative size hint") } key := typ.Key().(*RawType) val := typ.Elem().(*RawType) var alg uint8 if key.Kind() == String { alg = hashmapAlgorithmString } else if key.isBinary() { alg = hashmapAlgorithmBinary } else { alg = hashmapAlgorithmInterface } m := hashmapMake(key.Size(), val.Size(), uintptr(n), alg) return Value{ typecode: typ.(*RawType), value: m, flags: valueFlagExported, } } // MakeMap creates a new map with the specified type. func MakeMap(typ Type) Value { return MakeMapWithSize(typ, 8) } func (v Value) Call(in []Value) []Value { panic("unimplemented: (reflect.Value).Call()") } func (v Value) CallSlice(in []Value) []Value { panic("unimplemented: (reflect.Value).CallSlice()") } func (v Value) Method(i int) Value { panic("unimplemented: (reflect.Value).Method()") } func (v Value) MethodByName(name string) Value { panic("unimplemented: (reflect.Value).MethodByName()") } func (v Value) Recv() (x Value, ok bool) { panic("unimplemented: (reflect.Value).Recv()") } func NewAt(typ Type, p unsafe.Pointer) Value { panic("unimplemented: reflect.New()") } ================================================ FILE: src/internal/reflectlite/visiblefields.go ================================================ // Copyright 2021 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package reflectlite // VisibleFields returns all the visible fields in t, which must be a // struct type. A field is defined as visible if it's accessible // directly with a FieldByName call. The returned fields include fields // inside anonymous struct members and unexported fields. They follow // the same order found in the struct, with anonymous fields followed // immediately by their promoted fields. // // For each element e of the returned slice, the corresponding field // can be retrieved from a value v of type t by calling v.FieldByIndex(e.Index). func VisibleFields(t Type) []StructField { if t == nil { panic("reflect: VisibleFields(nil)") } if t.Kind() != Struct { panic("reflect.VisibleFields of non-struct type") } w := &visibleFieldsWalker{ byName: make(map[string]int), visiting: make(map[Type]bool), fields: make([]StructField, 0, t.NumField()), index: make([]int, 0, 2), } w.walk(t) // Remove all the fields that have been hidden. // Use an in-place removal that avoids copying in // the common case that there are no hidden fields. j := 0 for i := range w.fields { f := &w.fields[i] if f.Name == "" { continue } if i != j { // A field has been removed. We need to shuffle // all the subsequent elements up. w.fields[j] = *f } j++ } return w.fields[:j] } type visibleFieldsWalker struct { byName map[string]int visiting map[Type]bool fields []StructField index []int } // walk walks all the fields in the struct type t, visiting // fields in index preorder and appending them to w.fields // (this maintains the required ordering). // Fields that have been overridden have their // Name field cleared. func (w *visibleFieldsWalker) walk(t Type) { if w.visiting[t] { return } w.visiting[t] = true for i := 0; i < t.NumField(); i++ { f := t.Field(i) w.index = append(w.index, i) add := true if oldIndex, ok := w.byName[f.Name]; ok { old := &w.fields[oldIndex] if len(w.index) == len(old.Index) { // Fields with the same name at the same depth // cancel one another out. Set the field name // to empty to signify that has happened, and // there's no need to add this field. old.Name = "" add = false } else if len(w.index) < len(old.Index) { // The old field loses because it's deeper than the new one. old.Name = "" } else { // The old field wins because it's shallower than the new one. add = false } } if add { // Copy the index so that it's not overwritten // by the other appends. f.Index = append([]int(nil), w.index...) w.byName[f.Name] = len(w.fields) w.fields = append(w.fields, f) } if f.Anonymous { if f.Type.Kind() == Pointer { f.Type = f.Type.Elem() } if f.Type.Kind() == Struct { w.walk(f.Type) } } w.index = w.index[:len(w.index)-1] } delete(w.visiting, t) } ================================================ FILE: src/internal/syscall/unix/constants.go ================================================ package unix const ( R_OK = 0x4 W_OK = 0x2 X_OK = 0x1 ) ================================================ FILE: src/internal/syscall/unix/eaccess.go ================================================ package unix import "syscall" func Eaccess(path string, mode uint32) error { // We don't support this syscall on baremetal or wasm. // Callers are generally able to deal with this since unix.Eaccess also // isn't available on Android. return syscall.ENOSYS } ================================================ FILE: src/internal/syscall/unix/getrandom.go ================================================ package unix type GetRandomFlag uintptr const ( GRND_NONBLOCK GetRandomFlag = 0x0001 GRND_RANDOM GetRandomFlag = 0x0002 ) func GetRandom(p []byte, flags GetRandomFlag) (n int, err error) { panic("todo: unix.GetRandom") } ================================================ FILE: src/internal/task/atomic-cooperative.go ================================================ //go:build tinygo.unicore package task // Atomics implementation for cooperative systems. The atomic types here aren't // actually atomic, they assume that accesses cannot be interrupted by a // different goroutine or interrupt happening at the same time. type atomicIntegerType interface { uintptr | uint32 | uint64 } type pseudoAtomic[T atomicIntegerType] struct { v T } func (x *pseudoAtomic[T]) Add(delta T) T { x.v += delta; return x.v } func (x *pseudoAtomic[T]) Load() T { return x.v } func (x *pseudoAtomic[T]) Store(val T) { x.v = val } func (x *pseudoAtomic[T]) CompareAndSwap(old, new T) (swapped bool) { if x.v != old { return false } x.v = new return true } func (x *pseudoAtomic[T]) Swap(new T) (old T) { old = x.v x.v = new return } // Uintptr is an atomic uintptr when multithreading is enabled, and a plain old // uintptr otherwise. type Uintptr = pseudoAtomic[uintptr] // Uint32 is an atomic uint32 when multithreading is enabled, and a plain old // uint32 otherwise. type Uint32 = pseudoAtomic[uint32] // Uint64 is an atomic uint64 when multithreading is enabled, and a plain old // uint64 otherwise. type Uint64 = pseudoAtomic[uint64] ================================================ FILE: src/internal/task/atomic-preemptive.go ================================================ //go:build !tinygo.unicore package task // Atomics implementation for non-cooperative systems (multithreaded, etc). // These atomic types use real atomic instructions. import "sync/atomic" type ( Uintptr = atomic.Uintptr Uint32 = atomic.Uint32 Uint64 = atomic.Uint64 ) ================================================ FILE: src/internal/task/darwin.go ================================================ //go:build darwin package task import "unsafe" // MacOS uses a pointer so unsafe.Pointer should be fine: // // typedef struct _opaque_pthread_t *__darwin_pthread_t; // typedef __darwin_pthread_t pthread_t; type threadID unsafe.Pointer ================================================ FILE: src/internal/task/futex-cooperative.go ================================================ //go:build tinygo.unicore package task // A futex is a way for userspace to wait with the pointer as the key, and for // another thread to wake one or all waiting threads keyed on the same pointer. // // A futex does not change the underlying value, it only reads it before to prevent // lost wake-ups. type Futex struct { Uint32 waiters Stack } // Atomically check for cmp to still be equal to the futex value and if so, go // to sleep. Return true if we were definitely awoken by a call to Wake or // WakeAll, and false if we can't be sure of that. func (f *Futex) Wait(cmp uint32) (awoken bool) { if f.Uint32.v != cmp { return false } // Push the current goroutine onto the waiter stack. f.waiters.Push(Current()) // Pause until the waiters are awoken by Wake/WakeAll. Pause() // We were awoken by a call to Wake or WakeAll. There is no chance for // spurious wakeups. return true } // Wake a single waiter. func (f *Futex) Wake() { if t := f.waiters.Pop(); t != nil { scheduleTask(t) } } // Wake all waiters. func (f *Futex) WakeAll() { for t := f.waiters.Pop(); t != nil; t = f.waiters.Pop() { scheduleTask(t) } } ================================================ FILE: src/internal/task/futex-cores.go ================================================ //go:build scheduler.cores package task import "runtime/interrupt" // A futex is a way for userspace to wait with the pointer as the key, and for // another thread to wake one or all waiting threads keyed on the same pointer. // // A futex does not change the underlying value, it only reads it before to prevent // lost wake-ups. type Futex struct { Uint32 waiters Stack } // Atomically check for cmp to still be equal to the futex value and if so, go // to sleep. Return true if we were definitely awoken by a call to Wake or // WakeAll, and false if we can't be sure of that. func (f *Futex) Wait(cmp uint32) (awoken bool) { mask := lockFutex() if f.Uint32.Load() != cmp { unlockFutex(mask) return false } // Push the current goroutine onto the waiter stack. f.waiters.Push(Current()) unlockFutex(mask) // Pause until this task is awoken by Wake/WakeAll. Pause() // We were awoken by a call to Wake or WakeAll. There is no chance for // spurious wakeups. return true } // Wake a single waiter. func (f *Futex) Wake() { mask := lockFutex() if t := f.waiters.Pop(); t != nil { scheduleTask(t) } unlockFutex(mask) } // Wake all waiters. func (f *Futex) WakeAll() { mask := lockFutex() for t := f.waiters.Pop(); t != nil; t = f.waiters.Pop() { scheduleTask(t) } unlockFutex(mask) } //go:linkname lockFutex runtime.lockFutex func lockFutex() interrupt.State //go:linkname unlockFutex runtime.unlockFutex func unlockFutex(interrupt.State) ================================================ FILE: src/internal/task/futex-threads.go ================================================ //go:build scheduler.threads package task import "internal/futex" type Futex = futex.Futex ================================================ FILE: src/internal/task/gc_stack_chain.go ================================================ //go:build (gc.conservative || gc.custom || gc.precise) && tinygo.wasm package task import "unsafe" //go:linkname swapStackChain runtime.swapStackChain func swapStackChain(dst *unsafe.Pointer) type gcData struct { stackChain unsafe.Pointer } func (gcd *gcData) swap() { swapStackChain(&gcd.stackChain) } ================================================ FILE: src/internal/task/gc_stack_noop.go ================================================ //go:build !(gc.conservative || gc.custom || gc.precise) || !tinygo.wasm package task type gcData struct{} func (gcd *gcData) swap() { } ================================================ FILE: src/internal/task/linux.go ================================================ //go:build linux && !baremetal package task import "unsafe" // Musl uses a pointer (or unsigned long for C++) so unsafe.Pointer should be // fine. type threadID unsafe.Pointer ================================================ FILE: src/internal/task/mutex-cooperative.go ================================================ //go:build tinygo.unicore package task type Mutex struct { locked bool blocked Stack } func (m *Mutex) Lock() { if m.locked { // Push self onto stack of blocked tasks, and wait to be resumed. m.blocked.Push(Current()) Pause() return } m.locked = true } func (m *Mutex) Unlock() { if !m.locked { panic("sync: unlock of unlocked Mutex") } // Wake up a blocked task, if applicable. if t := m.blocked.Pop(); t != nil { scheduleTask(t) } else { m.locked = false } } // TryLock tries to lock m and reports whether it succeeded. // // Note that while correct uses of TryLock do exist, they are rare, // and use of TryLock is often a sign of a deeper problem // in a particular use of mutexes. func (m *Mutex) TryLock() bool { if m.locked { return false } m.Lock() return true } ================================================ FILE: src/internal/task/mutex-preemptive.go ================================================ //go:build !tinygo.unicore package task // Futex-based mutex. // This is largely based on the paper "Futexes are Tricky" by Ulrich Drepper. // It describes a few ways to implement mutexes using a futex, and how some // seemingly-obvious implementations don't exactly work as intended. // Unfortunately, Go atomic operations work slightly differently so we can't // copy the algorithm verbatim. // // The implementation works like this. The futex can have 3 different values, // depending on the state: // // - 0: the futex is currently unlocked. // - 1: the futex is locked, but is uncontended. There is one special case: if // a contended futex is unlocked, it is set to 0. It is possible for another // thread to lock the futex before the next waiter is woken. But because a // waiter will be woken (if there is one), it will always change to 2 // regardless. So this is not a problem. // - 2: the futex is locked, and is contended. At least one thread is trying // to obtain the lock (and is in the contended loop, see below). // // For the paper, see: // https://dept-info.labri.fr/~denis/Enseignement/2008-IR/Articles/01-futex.pdf) type Mutex struct { futex Futex } func (m *Mutex) Lock() { // Fast path: try to take an uncontended lock. if m.futex.CompareAndSwap(0, 1) { // We obtained the mutex. return } // The futex is contended, so we enter the contended loop. // If we manage to change the futex from 0 to 2, we managed to take the // lock. Else, we have to wait until a call to Unlock unlocks this mutex. // (Unlock will wake one waiter when it finds the futex is set to 2 when // unlocking). for m.futex.Swap(2) != 0 { // Wait until we get resumed in Unlock. m.futex.Wait(2) } } func (m *Mutex) Unlock() { if old := m.futex.Swap(0); old == 0 { // Mutex wasn't locked before. panic("sync: unlock of unlocked Mutex") } else if old == 2 { // Mutex was a contended lock, so we need to wake the next waiter. m.futex.Wake() } } // TryLock tries to lock m and reports whether it succeeded. // // Note that while correct uses of TryLock do exist, they are rare, // and use of TryLock is often a sign of a deeper problem // in a particular use of mutexes. func (m *Mutex) TryLock() bool { // Fast path: try to take an uncontended lock. if m.futex.CompareAndSwap(0, 1) { // We obtained the mutex. return true } return false } ================================================ FILE: src/internal/task/pmutex-cooperative.go ================================================ //go:build tinygo.unicore package task // PMutex is a real mutex on systems that can be either preemptive or threaded, // and a dummy lock on other (purely cooperative) systems. // // It is mainly useful for short operations that need a lock when threading may // be involved, but which do not need a lock with a purely cooperative // scheduler. type PMutex struct { } func (m *PMutex) Lock() { } func (m *PMutex) Unlock() { } ================================================ FILE: src/internal/task/pmutex-preemptive.go ================================================ //go:build !tinygo.unicore package task // PMutex is a real mutex on systems that can be either preemptive or threaded, // and a dummy lock on other (purely cooperative) systems. // // It is mainly useful for short operations that need a lock when threading may // be involved, but which do not need a lock with a purely cooperative // scheduler. type PMutex = Mutex ================================================ FILE: src/internal/task/queue.go ================================================ package task import "runtime/interrupt" const asserts = false // Queue is a FIFO container of tasks. // The zero value is an empty queue. type Queue struct { head, tail *Task } // Push a task onto the queue. func (q *Queue) Push(t *Task) { mask := lockAtomics() if asserts && t.Next != nil { unlockAtomics(mask) panic("runtime: pushing a task to a queue with a non-nil Next pointer") } if q.tail != nil { q.tail.Next = t } q.tail = t t.Next = nil if q.head == nil { q.head = t } unlockAtomics(mask) } // Pop a task off of the queue. func (q *Queue) Pop() *Task { mask := lockAtomics() t := q.head if t == nil { unlockAtomics(mask) return nil } q.head = t.Next if q.tail == t { q.tail = nil } t.Next = nil unlockAtomics(mask) return t } // Append pops the contents of another queue and pushes them onto the end of this queue. func (q *Queue) Append(other *Queue) { mask := lockAtomics() if q.head == nil { q.head = other.head } else { q.tail.Next = other.head } q.tail = other.tail other.head, other.tail = nil, nil unlockAtomics(mask) } // Empty checks if the queue is empty. func (q *Queue) Empty() bool { mask := lockAtomics() empty := q.head == nil unlockAtomics(mask) return empty } // Stack is a LIFO container of tasks. // The zero value is an empty stack. // This is slightly cheaper than a queue, so it can be preferable when strict ordering is not necessary. type Stack struct { top *Task } // Push a task onto the stack. func (s *Stack) Push(t *Task) { mask := lockAtomics() if asserts && t.Next != nil { unlockAtomics(mask) panic("runtime: pushing a task to a stack with a non-nil Next pointer") } s.top, t.Next = t, s.top unlockAtomics(mask) } // Pop a task off of the stack. func (s *Stack) Pop() *Task { mask := lockAtomics() t := s.top if t != nil { s.top = t.Next t.Next = nil } unlockAtomics(mask) return t } // tail follows the chain of tasks. // If t is nil, returns nil. // Otherwise, returns the task in the chain where the Next field is nil. func (t *Task) tail() *Task { if t == nil { return nil } for t.Next != nil { t = t.Next } return t } // Queue moves the contents of the stack into a queue. // Elements can be popped from the queue in the same order that they would be popped from the stack. func (s *Stack) Queue() Queue { mask := lockAtomics() head := s.top s.top = nil q := Queue{ head: head, tail: head.tail(), } unlockAtomics(mask) return q } // Use runtime.lockAtomics and runtime.unlockAtomics so that Queue and Stack // work correctly even on multicore systems. These functions are normally used // to implement atomic operations, but the same spinlock can also be used for // Queue/Stack operations which are very fast. // These functions are just plain old interrupt disable/restore on non-multicore // systems. //go:linkname lockAtomics runtime.lockAtomics func lockAtomics() interrupt.State //go:linkname unlockAtomics runtime.unlockAtomics func unlockAtomics(mask interrupt.State) ================================================ FILE: src/internal/task/semaphore.go ================================================ package task // Barebones semaphore implementation. // The main limitation is that if there are multiple waiters, a single Post() // call won't do anything. Only when Post() has been called to awaken all // waiters will the waiters proceed. // This limitation is not a problem when there will only be a single waiter. type Semaphore struct { futex Futex } // Post (unlock) the semaphore, incrementing the value in the semaphore. func (s *Semaphore) Post() { newValue := s.futex.Add(1) if newValue == 0 { s.futex.WakeAll() } } // Wait (lock) the semaphore, decrementing the value in the semaphore. func (s *Semaphore) Wait() { delta := int32(-1) value := s.futex.Add(uint32(delta)) for { if int32(value) >= 0 { // Semaphore unlocked! return } s.futex.Wait(value) value = s.futex.Load() } } ================================================ FILE: src/internal/task/task.go ================================================ package task import ( "unsafe" ) // Task is a state of goroutine for scheduling purposes. type Task struct { // Next is a field which can be used to make a linked list of tasks. Next *Task // Ptr is a field which can be used for storing a pointer. Ptr unsafe.Pointer // Data is a field which can be used for storing state information. Data uint64 // gcData holds data for the GC. gcData gcData // state is the underlying running state of the task. state state // This is needed for some crypto packages. FipsIndicator uint8 // State of the goroutine: running, paused, or must-resume-next-pause. // This extra field doesn't increase memory usage on 32-bit CPUs and above, // since it falls into the padding of the FipsIndicator bit above. RunState uint8 // DeferFrame stores a pointer to the (stack allocated) defer frame of the // goroutine that is used for the recover builtin. DeferFrame unsafe.Pointer } const ( // Initial state: the goroutine state is saved on the stack. RunStatePaused = iota // The goroutine is running right now. RunStateRunning // The goroutine is running, but already marked as "can resume". // The next call to Pause() won't actually pause the goroutine. RunStateResuming ) // DataUint32 returns the Data field as a uint32. The value is only valid after // setting it through SetDataUint32 or by storing to it using DataAtomicUint32. func (t *Task) DataUint32() uint32 { return *(*uint32)(unsafe.Pointer(&t.Data)) } // SetDataUint32 updates the uint32 portion of the Data field (which could be // the first 4 or last 4 bytes depending on the architecture endianness). func (t *Task) SetDataUint32(val uint32) { *(*uint32)(unsafe.Pointer(&t.Data)) = val } // DataAtomicUint32 returns the Data field as an atomic-if-needed Uint32 value. func (t *Task) DataAtomicUint32() *Uint32 { return (*Uint32)(unsafe.Pointer(&t.Data)) } // getGoroutineStackSize is a compiler intrinsic that returns the stack size for // the given function and falls back to the default stack size. It is replaced // with a load from a special section just before codegen. func getGoroutineStackSize(fn uintptr) uintptr //go:linkname runtime_alloc runtime.alloc func runtime_alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer //go:linkname scheduleTask runtime.scheduleTask func scheduleTask(*Task) ================================================ FILE: src/internal/task/task_asyncify.go ================================================ //go:build scheduler.asyncify package task import ( "unsafe" ) // Stack canary, to detect a stack overflow. The number is a random number // generated by random.org. The bit fiddling dance is necessary because // otherwise Go wouldn't allow the cast to a smaller integer size. const stackCanary = uintptr(uint64(0x670c1333b83bf575) & uint64(^uintptr(0))) //go:linkname runtimePanic runtime.runtimePanic func runtimePanic(str string) // state is a structure which holds a reference to the state of the task. // When the task is suspended, the stack pointers are saved here. type state struct { // entry is the entry function of the task. // This is needed every time the function is invoked so that asyncify knows what to rewind. entry uintptr // args are a pointer to a struct holding the arguments of the function. args unsafe.Pointer // stackState is the state of the stack while unwound. stackState launched bool } // stackState is the saved state of a stack while unwound. // The stack is arranged with asyncify at the bottom, C stack at the top, and a gap of available stack space between the two. type stackState struct { // asyncify is the stack pointer of the asyncify stack. // This starts from the bottom and grows upwards. asyncifysp unsafe.Pointer // asyncify is stack pointer of the C stack. // This starts from the top and grows downwards. csp unsafe.Pointer // Pointer to the first (lowest address) of the stack. It must never be // overwritten. It can be checked from time to time to see whether a stack // overflow happened in the past. canaryPtr *uintptr } // start creates and starts a new goroutine with the given function and arguments. // The new goroutine is immediately started. func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) { t := &Task{} t.state.initialize(fn, args, stackSize) scheduleTask(t) } //export tinygo_launch func (*state) launch() //go:linkname align runtime.align func align(p uintptr) uintptr // initialize the state and prepare to call the specified function with the specified argument bundle. func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) { // Save the entry call. s.entry = fn s.args = args // Create a stack. stack := runtime_alloc(stackSize, nil) // Set up the stack canary, a random number that should be checked when // switching from the task back to the scheduler. The stack canary pointer // points to the first word of the stack. If it has changed between now and // the next stack switch, there was a stack overflow. s.canaryPtr = (*uintptr)(stack) *s.canaryPtr = stackCanary // Calculate stack base addresses. s.asyncifysp = unsafe.Add(stack, unsafe.Sizeof(uintptr(0))) s.csp = unsafe.Add(stack, stackSize) } // currentTask is the current running task, or nil if currently in the scheduler. var currentTask *Task // Current returns the current active task. func Current() *Task { return currentTask } // Pause suspends the current task and returns to the scheduler. // This function may only be called when running on a goroutine stack, not when running on the system stack. func Pause() { if *currentTask.state.canaryPtr != stackCanary { runtimePanic("stack overflow") } currentTask.state.unwind() } //export tinygo_unwind func (*stackState) unwind() // Resume the task until it pauses or completes. // This may only be called from the scheduler. func (t *Task) Resume() { // The current task must be saved and restored because this can nest on WASM with JS. prevTask := currentTask if prevTask == nil { // Save the system stack pointer. saveStackPointer() } t.gcData.swap() currentTask = t if !t.state.launched { t.state.launch() t.state.launched = true } else { t.state.rewind() } currentTask = prevTask t.gcData.swap() if uintptr(t.state.asyncifysp) > uintptr(t.state.csp) { runtimePanic("stack overflow") } } //go:linkname saveStackPointer runtime.saveStackPointer func saveStackPointer() //export tinygo_rewind func (*state) rewind() // OnSystemStack returns whether the caller is running on the system stack. func OnSystemStack() bool { // If there is not an active goroutine, then this must be running on the system stack. return Current() == nil } ================================================ FILE: src/internal/task/task_asyncify_wasm.S ================================================ .globaltype __stack_pointer, i32 .functype start_unwind (i32) -> () .import_module start_unwind, asyncify .import_name start_unwind, start_unwind .functype stop_unwind () -> () .import_module stop_unwind, asyncify .import_name stop_unwind, stop_unwind .functype start_rewind (i32) -> () .import_module start_rewind, asyncify .import_name start_rewind, start_rewind .functype stop_rewind () -> () .import_module stop_rewind, asyncify .import_name stop_rewind, stop_rewind .global tinygo_unwind .hidden tinygo_unwind .type tinygo_unwind,@function tinygo_unwind: // func (state *stackState) unwind() .functype tinygo_unwind (i32) -> () // Check if we are rewinding. i32.const 0 i32.load8_u tinygo_rewinding if // if tinygo_rewinding { // Stop rewinding. call stop_rewind i32.const 0 i32.const 0 i32.store8 tinygo_rewinding // tinygo_rewinding = false; else // Save the C stack pointer (destination structure pointer is in local 0). local.get 0 global.get __stack_pointer i32.store 4 // state.csp = getCurrentStackPointer() // Ask asyncify to unwind. // When resuming, asyncify will return this function with tinygo_rewinding set to true. local.get 0 call start_unwind // asyncify.start_unwind(state) end_if return end_function .global tinygo_launch .hidden tinygo_launch .type tinygo_launch,@function tinygo_launch: // func (state *state) launch() .functype tinygo_launch (i32) -> () // Switch to the goroutine's C stack. global.get __stack_pointer // prev := getCurrentStackPointer() local.get 0 i32.load 12 global.set __stack_pointer // setStackPointer(state.csp) // Get the argument pack and entry pointer. local.get 0 i32.load 4 // args := state.args local.get 0 i32.load 0 // fn := state.entry // Launch the entry function. call_indirect (i32) -> () // fn(args) // Stop unwinding. call stop_unwind // Restore the C stack. global.set __stack_pointer // setStackPointer(prev) return end_function .global tinygo_rewind .hidden tinygo_rewind .type tinygo_rewind,@function tinygo_rewind: // func (state *state) rewind() .functype tinygo_rewind (i32) -> () // Switch to the goroutine's C stack. global.get __stack_pointer // prev := getCurrentStackPointer() local.get 0 i32.load 12 global.set __stack_pointer // setStackPointer(state.csp) // Get the argument pack and entry pointer. local.get 0 i32.load 4 // args := state.args local.get 0 i32.load 0 // fn := state.entry // Prepare to rewind. i32.const 0 i32.const 1 i32.store8 tinygo_rewinding // tinygo_rewinding = true; local.get 0 i32.const 8 i32.add call start_rewind // asyncify.start_rewind(&state.stackState) // Launch the entry function. // This will actually rewind the call stack. call_indirect (i32) -> () // fn(args) // Stop unwinding. call stop_unwind // Restore the C stack. global.set __stack_pointer // setStackPointer(prev) return end_function .hidden tinygo_rewinding # @tinygo_rewinding .type tinygo_rewinding,@object .section .bss.tinygo_rewinding,"",@ .globl tinygo_rewinding tinygo_rewinding: .int8 0 # 0x0 .size tinygo_rewinding, 1 ================================================ FILE: src/internal/task/task_none.go ================================================ //go:build scheduler.none package task import "unsafe" // There is only one goroutine so the task struct can be a global. var mainTask Task //go:linkname runtimePanic runtime.runtimePanic func runtimePanic(str string) func Pause() { runtimePanic("scheduler is disabled") } func Current() *Task { // Return a task struct, which is used for the recover builtin for example. return &mainTask } //go:noinline func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) { // The compiler will error if this is reachable. runtimePanic("scheduler is disabled") } type state struct{} func (t *Task) Resume() { runtimePanic("scheduler is disabled") } // OnSystemStack returns whether the caller is running on the system stack. func OnSystemStack() bool { // This scheduler does not do any stack switching. return true } func SystemStack() uintptr { // System stack is the current stack, so this shouldn't be called. runtimePanic("scheduler is disabled") return 0 // unreachable } ================================================ FILE: src/internal/task/task_stack.go ================================================ //go:build scheduler.tasks || scheduler.cores package task import ( "unsafe" ) //go:linkname runtimePanic runtime.runtimePanic func runtimePanic(str string) // Stack canary, to detect a stack overflow. The number is a random number // generated by random.org. The bit fiddling dance is necessary because // otherwise Go wouldn't allow the cast to a smaller integer size. const stackCanary = uintptr(uint64(0x670c1333b83bf575) & uint64(^uintptr(0))) // state is a structure which holds a reference to the state of the task. // When the task is suspended, the registers are stored onto the stack and the stack pointer is stored into sp. type state struct { // sp is the stack pointer of the saved state. // When the task is inactive, the saved registers are stored at the top of the stack. // Note: this should ideally be a unsafe.Pointer for the precise GC. The GC // will find the stack through canaryPtr though so it's not currently a // problem to store this value as uintptr. sp uintptr // canaryPtr points to the top word of the stack (the lowest address). // This is used to detect stack overflows. // When initializing the goroutine, the stackCanary constant is stored there. // If the stack overflowed, the word will likely no longer equal stackCanary. canaryPtr *uintptr } //export tinygo_task_exit func taskExit() { // TODO: explicitly free the stack after switching back to the scheduler. Pause() } // initialize the state and prepare to call the specified function with the specified argument bundle. func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) { // Create a stack. stack := runtime_alloc(stackSize, nil) // Set up the stack canary, a random number that should be checked when // switching from the task back to the scheduler. The stack canary pointer // points to the first word of the stack. If it has changed between now and // the next stack switch, there was a stack overflow. s.canaryPtr = (*uintptr)(stack) *s.canaryPtr = stackCanary // Get a pointer to the top of the stack, where the initial register values // are stored. They will be popped off the stack on the first stack switch // to the goroutine, and will start running tinygo_startTask (this setup // happens in archInit). r := (*calleeSavedRegs)(unsafe.Add(stack, stackSize-unsafe.Sizeof(calleeSavedRegs{}))) // Invoke architecture-specific initialization. s.archInit(r, fn, args) } //export tinygo_swapTask func swapTask(oldStack uintptr, newStack *uintptr) // startTask is a small wrapper function that sets up the first (and only) // argument to the new goroutine and makes sure it is exited when the goroutine // finishes. // //go:extern tinygo_startTask var startTask [0]uint8 // start creates and starts a new goroutine with the given function and arguments. // The new goroutine is scheduled to run later. func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) { t := &Task{} t.state.initialize(fn, args, stackSize) scheduleTask(t) } // OnSystemStack returns whether the caller is running on the system stack. func OnSystemStack() bool { // If there is not an active goroutine, then this must be running on the system stack. return Current() == nil } ================================================ FILE: src/internal/task/task_stack_386.S ================================================ #ifdef _WIN32 .global _tinygo_startTask _tinygo_startTask: #else // Linux etc .section .text.tinygo_startTask .global tinygo_startTask .type tinygo_startTask, %function tinygo_startTask: #endif .cfi_startproc // Small assembly stub for starting a goroutine. This is already run on the // new stack, with the callee-saved registers already loaded. // Most importantly, EBX contain the pc of the to-be-started function and // ESI contain the only argument it is given. Multiple arguments are packed // into one by storing them in a new allocation. // Indicate to the unwinder that there is nothing to unwind, this is the // root frame. It avoids bogus extra frames in GDB. .cfi_undefined eip // Set the first argument of the goroutine start wrapper, which contains all // the arguments. pushl %esi // Branch to the "goroutine start" function. calll *%ebx // Rebalance the stack (to undo the above push). addl $4, %esp // After return, exit this goroutine. This is a tail call. #ifdef _WIN32 jmp _tinygo_task_exit #else jmp tinygo_task_exit #endif .cfi_endproc #ifdef _WIN32 .global _tinygo_swapTask _tinygo_swapTask: #else .global tinygo_swapTask .type tinygo_swapTask, %function tinygo_swapTask: #endif // This function gets the following parameters: movl 4(%esp), %eax // newStack uintptr movl 8(%esp), %ecx // oldStack *uintptr // More information on the calling convention: // https://wiki.osdev.org/System_V_ABI#i386 // Save all callee-saved registers: pushl %ebp pushl %edi pushl %esi pushl %ebx // Save the current stack pointer in oldStack. movl %esp, (%ecx) // Switch to the new stack pointer. movl %eax, %esp // Load saved register from the new stack. popl %ebx popl %esi popl %edi popl %ebp // Return into the new task, as if tinygo_swapTask was a regular call. ret ================================================ FILE: src/internal/task/task_stack_386.go ================================================ //go:build scheduler.tasks && 386 package task import "unsafe" var systemStack uintptr // calleeSavedRegs is the list of registers that must be saved and restored when // switching between tasks. Also see task_stack_386.S that relies on the exact // layout of this struct. type calleeSavedRegs struct { ebx uintptr esi uintptr edi uintptr ebp uintptr pc uintptr // Pad this struct so that tasks start on a 16-byte aligned stack. _ [3]uintptr } // archInit runs architecture-specific setup for the goroutine startup. func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) { // Store the initial sp for the startTask function (implemented in assembly). s.sp = uintptr(unsafe.Pointer(r)) // Initialize the registers. // These will be popped off of the stack on the first resume of the goroutine. // Start the function at tinygo_startTask (defined in // src/internal/task/task_stack_386.S). This assembly code calls a function // (passed in EBX) with a single argument (passed in ESI). After the // function returns, it calls Pause(). r.pc = uintptr(unsafe.Pointer(&startTask)) // Pass the function to call in EBX. // This function is a compiler-generated wrapper which loads arguments out // of a struct pointer. See createGoroutineStartWrapper (defined in // compiler/goroutine.go) for more information. r.ebx = fn // Pass the pointer to the arguments struct in ESI. r.esi = uintptr(args) } func (s *state) resume() { swapTask(s.sp, &systemStack) } func (s *state) pause() { newStack := systemStack systemStack = 0 swapTask(newStack, &s.sp) } // SystemStack returns the system stack pointer when called from a task stack. // When called from the system stack, it returns 0. func SystemStack() uintptr { return systemStack } ================================================ FILE: src/internal/task/task_stack_amd64.S ================================================ #ifdef __MACH__ // Darwin .global _tinygo_startTask _tinygo_startTask: #else // Linux etc .section .text.tinygo_startTask .global tinygo_startTask tinygo_startTask: #endif .cfi_startproc // Small assembly stub for starting a goroutine. This is already run on the // new stack, with the callee-saved registers already loaded. // Most importantly, r12 contain the pc of the to-be-started function and // r13 contain the only argument it is given. Multiple arguments are packed // into one by storing them in a new allocation. // Indicate to the unwinder that there is nothing to unwind, this is the // root frame. It avoids bogus extra frames in GDB like here: // #10 0x00000000004277b6 in () at [...] // #11 0x00000000004278f3 in tinygo_startTask () at [...] // #12 0x0000000000002030 in ?? () // #13 0x0000000000000071 in ?? () .cfi_undefined rip // Set the first argument of the goroutine start wrapper, which contains all // the arguments. movq %r13, %rdi // Branch to the "goroutine start" function. callq *%r12 // After return, exit this goroutine. This is a tail call. #ifdef __MACH__ jmp _tinygo_task_exit #else jmp tinygo_task_exit #endif .cfi_endproc #ifdef __MACH__ // Darwin .global _tinygo_swapTask _tinygo_swapTask: #else // Linux etc .global tinygo_swapTask .section .text.tinygo_swapTask tinygo_swapTask: #endif // This function gets the following parameters: // %rdi = newStack uintptr // %rsi = oldStack *uintptr // Save all callee-saved registers: pushq %r15 pushq %r14 pushq %r13 pushq %r12 pushq %rbp pushq %rbx // Save the current stack pointer in oldStack. movq %rsp, (%rsi) // Switch to the new stack pointer. movq %rdi, %rsp // Load saved register from the new stack. popq %rbx popq %rbp popq %r12 popq %r13 popq %r14 popq %r15 // Return into the new task, as if tinygo_swapTask was a regular call. ret #ifdef __MACH__ // Darwin // allow these symbols to stripped as dead code .subsections_via_symbols #endif ================================================ FILE: src/internal/task/task_stack_amd64.go ================================================ //go:build scheduler.tasks && amd64 && !windows package task import "unsafe" var systemStack uintptr // calleeSavedRegs is the list of registers that must be saved and restored when // switching between tasks. Also see task_stack_amd64.S that relies on the exact // layout of this struct. type calleeSavedRegs struct { rbx uintptr rbp uintptr r12 uintptr r13 uintptr r14 uintptr r15 uintptr pc uintptr } // archInit runs architecture-specific setup for the goroutine startup. func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) { // Store the initial sp for the startTask function (implemented in assembly). s.sp = uintptr(unsafe.Pointer(r)) // Initialize the registers. // These will be popped off of the stack on the first resume of the goroutine. // Start the function at tinygo_startTask (defined in // src/internal/task/task_stack_amd64.S). This assembly code calls a // function (passed in r12) with a single argument (passed in r13). After // the function returns, it calls Pause(). r.pc = uintptr(unsafe.Pointer(&startTask)) // Pass the function to call in r12. // This function is a compiler-generated wrapper which loads arguments out // of a struct pointer. See createGoroutineStartWrapper (defined in // compiler/goroutine.go) for more information. r.r12 = fn // Pass the pointer to the arguments struct in r13. r.r13 = uintptr(args) } func (s *state) resume() { swapTask(s.sp, &systemStack) } func (s *state) pause() { newStack := systemStack systemStack = 0 swapTask(newStack, &s.sp) } // SystemStack returns the system stack pointer when called from a task stack. // When called from the system stack, it returns 0. func SystemStack() uintptr { return systemStack } ================================================ FILE: src/internal/task/task_stack_amd64_windows.S ================================================ // Windows on amd64 has a slightly different ABI than other (*nix) systems. // Therefore, assembly functions need to be tweaked slightly. // // The calling convention is described here: // https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170 .section .text.tinygo_startTask,"ax" .global tinygo_startTask tinygo_startTask: // Small assembly stub for starting a goroutine. This is already run on the // new stack, with the callee-saved registers already loaded. // Most importantly, r12 contain the pc of the to-be-started function and // r13 contain the only argument it is given. Multiple arguments are packed // into one by storing them in a new allocation. // Set the first argument of the goroutine start wrapper, which contains all // the arguments. movq %r13, %rcx // Branch to the "goroutine start" function. callq *%r12 // After return, exit this goroutine. // This has to be a call, not a jump, to keep the stack correctly aligned. callq tinygo_task_exit .global tinygo_swapTask .section .text.tinygo_swapTask,"ax" tinygo_swapTask: // This function gets the following parameters: // %rcx = newStack uintptr // %rdx = oldStack *uintptr // Save all callee-saved registers: pushq %r15 pushq %r14 pushq %r13 pushq %r12 pushq %rsi pushq %rdi pushq %rbp sub $160, %rsp movaps %xmm6, 144(%rsp) movaps %xmm7, 128(%rsp) movaps %xmm8, 112(%rsp) movaps %xmm9, 96(%rsp) movaps %xmm10, 80(%rsp) movaps %xmm11, 64(%rsp) movaps %xmm12, 48(%rsp) movaps %xmm13, 32(%rsp) movaps %xmm14, 16(%rsp) movaps %xmm15, 0(%rsp) pushq %rbx // Save the current stack pointer in oldStack. movq %rsp, (%rdx) // Switch to the new stack pointer. movq %rcx, %rsp // Load saved register from the new stack. popq %rbx movaps 0(%rsp), %xmm15 movaps 16(%rsp), %xmm14 movaps 32(%rsp), %xmm13 movaps 48(%rsp), %xmm12 movaps 64(%rsp), %xmm11 movaps 80(%rsp), %xmm10 movaps 96(%rsp), %xmm9 movaps 112(%rsp), %xmm8 movaps 128(%rsp), %xmm7 movaps 144(%rsp), %xmm6 add $160, %rsp popq %rbp popq %rdi popq %rsi popq %r12 popq %r13 popq %r14 popq %r15 // Return into the new task, as if tinygo_swapTask was a regular call. ret ================================================ FILE: src/internal/task/task_stack_amd64_windows.go ================================================ //go:build scheduler.tasks && amd64 && windows package task // This is almost the same as task_stack_amd64.go, but with the extra rdi and // rsi registers saved: Windows has a slightly different calling convention. import "unsafe" var systemStack uintptr // calleeSavedRegs is the list of registers that must be saved and restored when // switching between tasks. Also see task_stack_amd64_windows.S that relies on // the exact layout of this struct. // The calling convention is described here: // https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170 // Most importantly, these are the registers we need to save/restore: // // > The x64 ABI considers registers RBX, RBP, RDI, RSI, RSP, R12, R13, R14, // > R15, and XMM6-XMM15 nonvolatile. They must be saved and restored by a // > function that uses them. type calleeSavedRegs struct { // Note: rbx is placed here so that the stack is correctly aligned when // loading/storing the xmm registers. rbx uintptr xmm15 [2]uint64 xmm14 [2]uint64 xmm13 [2]uint64 xmm12 [2]uint64 xmm11 [2]uint64 xmm10 [2]uint64 xmm9 [2]uint64 xmm8 [2]uint64 xmm7 [2]uint64 xmm6 [2]uint64 rbp uintptr rdi uintptr rsi uintptr r12 uintptr r13 uintptr r14 uintptr r15 uintptr pc uintptr } // archInit runs architecture-specific setup for the goroutine startup. func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) { // Store the initial sp for the startTask function (implemented in assembly). s.sp = uintptr(unsafe.Pointer(r)) // Initialize the registers. // These will be popped off of the stack on the first resume of the goroutine. // Start the function at tinygo_startTask (defined in // src/internal/task/task_stack_amd64_windows.S). This assembly code calls a // function (passed in r12) with a single argument (passed in r13). After // the function returns, it calls Pause(). r.pc = uintptr(unsafe.Pointer(&startTask)) // Pass the function to call in r12. // This function is a compiler-generated wrapper which loads arguments out // of a struct pointer. See createGoroutineStartWrapper (defined in // compiler/goroutine.go) for more information. r.r12 = fn // Pass the pointer to the arguments struct in r13. r.r13 = uintptr(args) } func (s *state) resume() { swapTask(s.sp, &systemStack) } func (s *state) pause() { newStack := systemStack systemStack = 0 swapTask(newStack, &s.sp) } // SystemStack returns the system stack pointer when called from a task stack. // When called from the system stack, it returns 0. func SystemStack() uintptr { return systemStack } ================================================ FILE: src/internal/task/task_stack_arm.S ================================================ //go:build tinygo // Only generate .debug_frame, don't generate .eh_frame. .cfi_sections .debug_frame .section .text.tinygo_startTask .global tinygo_startTask .type tinygo_startTask, %function tinygo_startTask: .cfi_startproc // Small assembly stub for starting a goroutine. This is already run on the // new stack, with the callee-saved registers already loaded. // Most importantly, r4 contains the pc of the to-be-started function and r5 // contains the only argument it is given. Multiple arguments are packed // into one by storing them in a new allocation. // Indicate to the unwinder that there is nothing to unwind, this is the // root frame. It avoids the following (bogus) error message in GDB: // Backtrace stopped: previous frame identical to this frame (corrupt stack?) .cfi_undefined lr // Set the first argument of the goroutine start wrapper, which contains all // the arguments. mov r0, r5 // Branch to the "goroutine start" function. By using blx instead of bx, // we'll return here instead of tail calling. blx r4 // After return, exit this goroutine. This is a tail call. bl tinygo_task_exit .cfi_endproc .size tinygo_startTask, .-tinygo_startTask .global tinygo_swapTask .type tinygo_swapTask, %function tinygo_swapTask: // This function gets the following parameters: // r0 = newStack uintptr // r1 = oldStack *uintptr // Save all callee-saved registers: push {r4-r11, lr} // Save the current stack pointer in oldStack. str sp, [r1] // Switch to the new stack pointer. mov sp, r0 // Load state from new task and branch to the previous position in the // program. pop {r4-r11, pc} ================================================ FILE: src/internal/task/task_stack_arm.go ================================================ //go:build scheduler.tasks && arm && !cortexm && !avr && !xtensa && !tinygo.riscv package task import "unsafe" var systemStack uintptr // calleeSavedRegs is the list of registers that must be saved and restored when // switching between tasks. Also see task_stack_arm.S that relies on the exact // layout of this struct. type calleeSavedRegs struct { r4 uintptr r5 uintptr r6 uintptr r7 uintptr r8 uintptr r9 uintptr r10 uintptr r11 uintptr pc uintptr } // archInit runs architecture-specific setup for the goroutine startup. func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) { // Store the initial sp for the startTask function (implemented in assembly). s.sp = uintptr(unsafe.Pointer(r)) // Initialize the registers. // These will be popped off of the stack on the first resume of the goroutine. // Start the function at tinygo_startTask (defined in src/internal/task/task_stack_arm.S). // This assembly code calls a function (passed in r4) with a single argument // (passed in r5). After the function returns, it calls Pause(). r.pc = uintptr(unsafe.Pointer(&startTask)) // Pass the function to call in r4. // This function is a compiler-generated wrapper which loads arguments out of a struct pointer. // See createGoroutineStartWrapper (defined in compiler/goroutine.go) for more information. r.r4 = fn // Pass the pointer to the arguments struct in r5. r.r5 = uintptr(args) } func (s *state) resume() { swapTask(s.sp, &systemStack) } func (s *state) pause() { newStack := systemStack systemStack = 0 swapTask(newStack, &s.sp) } // SystemStack returns the system stack pointer when called from a task stack. // When called from the system stack, it returns 0. func SystemStack() uintptr { return systemStack } ================================================ FILE: src/internal/task/task_stack_arm64.S ================================================ #ifdef __MACH__ .global _tinygo_startTask _tinygo_startTask: #else .global tinygo_startTask tinygo_startTask: #endif .cfi_startproc // Small assembly stub for starting a goroutine. This is already run on the // new stack, with the callee-saved registers already loaded. // Most importantly, x19 contains the pc of the to-be-started function and // x20 contains the only argument it is given. Multiple arguments are packed // into one by storing them in a new allocation. // Indicate to the unwinder that there is nothing to unwind, this is the // root frame. It avoids the following (bogus) error message in GDB: // Backtrace stopped: previous frame identical to this frame (corrupt stack?) .cfi_undefined lr // Set the first argument of the goroutine start wrapper, which contains all // the arguments. mov x0, x20 // Branch to the "goroutine start" function. By using blx instead of bx, // we'll return here instead of tail calling. blr x19 // After return, exit this goroutine. This is a tail call. #ifdef __MACH__ b _tinygo_task_exit #else b tinygo_task_exit #endif .cfi_endproc #ifndef __MACH__ #endif #ifdef __MACH__ .global _tinygo_swapTask _tinygo_swapTask: #else .global tinygo_swapTask tinygo_swapTask: #endif // This function gets the following parameters: // x0 = newStack uintptr // x1 = oldStack *uintptr // Save all callee-saved registers: stp x19, x20, [sp, #-160]! stp x21, x22, [sp, #16] stp x23, x24, [sp, #32] stp x25, x26, [sp, #48] stp x27, x28, [sp, #64] stp x29, x30, [sp, #80] stp d8, d9, [sp, #96] stp d10, d11, [sp, #112] stp d12, d13, [sp, #128] stp d14, d15, [sp, #144] // Save the current stack pointer in oldStack. mov x8, sp str x8, [x1] // Switch to the new stack pointer. mov sp, x0 // Restore stack state and return. ldp d14, d15, [sp, #144] ldp d12, d13, [sp, #128] ldp d10, d11, [sp, #112] ldp d8, d9, [sp, #96] ldp x29, x30, [sp, #80] ldp x27, x28, [sp, #64] ldp x25, x26, [sp, #48] ldp x23, x24, [sp, #32] ldp x21, x22, [sp, #16] ldp x19, x20, [sp], #160 ret ================================================ FILE: src/internal/task/task_stack_arm64.go ================================================ //go:build scheduler.tasks && arm64 package task import "unsafe" var systemStack uintptr // calleeSavedRegs is the list of registers that must be saved and restored when // switching between tasks. Also see task_stack_arm64.S that relies on the exact // layout of this struct. type calleeSavedRegs struct { x19 uintptr x20 uintptr x21 uintptr x22 uintptr x23 uintptr x24 uintptr x25 uintptr x26 uintptr x27 uintptr x28 uintptr x29 uintptr pc uintptr // aka x30 aka LR d8 uintptr d9 uintptr d10 uintptr d11 uintptr d12 uintptr d13 uintptr d14 uintptr d15 uintptr } // archInit runs architecture-specific setup for the goroutine startup. func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) { // Store the initial sp for the startTask function (implemented in assembly). s.sp = uintptr(unsafe.Pointer(r)) // Initialize the registers. // These will be popped off of the stack on the first resume of the goroutine. // Start the function at tinygo_startTask (defined in src/internal/task/task_stack_arm64.S). // This assembly code calls a function (passed in x19) with a single argument // (passed in x20). After the function returns, it calls Pause(). r.pc = uintptr(unsafe.Pointer(&startTask)) // Pass the function to call in x19. // This function is a compiler-generated wrapper which loads arguments out of a struct pointer. // See createGoroutineStartWrapper (defined in compiler/goroutine.go) for more information. r.x19 = fn // Pass the pointer to the arguments struct in x20. r.x20 = uintptr(args) } func (s *state) resume() { swapTask(s.sp, &systemStack) } func (s *state) pause() { newStack := systemStack systemStack = 0 swapTask(newStack, &s.sp) } // SystemStack returns the system stack pointer when called from a task stack. // When called from the system stack, it returns 0. func SystemStack() uintptr { return systemStack } ================================================ FILE: src/internal/task/task_stack_avr.S ================================================ //go:build tinygo .section .bss.tinygo_systemStack .global tinygo_systemStack .type tinygo_systemStack, %object tinygo_systemStack: .short 0 .section .text.tinygo_startTask .global tinygo_startTask .type tinygo_startTask, %function tinygo_startTask: // Small assembly stub for starting a goroutine. This is already run on the // new stack, with the callee-saved registers already loaded. // Most importantly, r2r3 contain the pc of the to-be-started function and // r4r5 contain the only argument it is given. Multiple arguments are packed // into one by storing them in a new allocation. // Set the first argument of the goroutine start wrapper, which contains all // the arguments. movw r24, r4 // Branch to the "goroutine start" function. Note that the Z register is // call-clobbered, so does not need to be restored after use. movw Z, r2 icall // After return, exit this goroutine. This is a tail call. #if __AVR_ARCH__ == 2 || __AVR_ARCH__ == 25 // Small memory devices (≤8kB flash) that do not have the long call // instruction available will need to use rcall instead. // Note that they will probably not be able to run more than the main // goroutine anyway, but this file is compiled for all AVRs so it needs to // compile at least. rcall tinygo_task_exit #else // Other devices can (and must) use the regular call instruction. call tinygo_task_exit #endif .global tinygo_swapTask .type tinygo_swapTask, %function tinygo_swapTask: // This function gets the following parameters: // r24:r25 = newStack uintptr // r22:r23 = oldStack *uintptr // Save all call-saved registers: // https://gcc.gnu.org/wiki/avr-gcc#Call-Saved_Registers push r29 // Y push r28 // Y push r17 push r16 push r15 push r14 push r13 push r12 push r11 push r10 push r9 push r8 push r7 push r6 push r5 push r4 push r3 push r2 // Save the current stack pointer in oldStack. in r2, 0x3d; SPL in r3, 0x3e; SPH movw Y, r22 std Y+0, r2 std Y+1, r3 // Switch to the new stack pointer. in r0, 0x3f ; SREG cli out 0x3d, r24; SPL out 0x3f, r0 ; SREG, restore interrupts (after the next instruction) out 0x3e, r25; SPH // Load saved register from the new stack. pop r2 pop r3 pop r4 pop r5 pop r6 pop r7 pop r8 pop r9 pop r10 pop r11 pop r12 pop r13 pop r14 pop r15 pop r16 pop r17 pop r28 // Y pop r29 // Y // Return into the new task, as if tinygo_swapTask was a regular call. ret ================================================ FILE: src/internal/task/task_stack_avr.go ================================================ //go:build scheduler.tasks && avr package task import "unsafe" //go:extern tinygo_systemStack var systemStack uintptr // calleeSavedRegs is the list of registers that must be saved and restored when // switching between tasks. Also see task_stack_avr.S that relies on the exact // layout of this struct. // // https://gcc.gnu.org/wiki/avr-gcc#Call-Saved_Registers type calleeSavedRegs struct { r2r3 uintptr r4r5 uintptr r6r7 uintptr r8r9 uintptr r10r11 uintptr r12r13 uintptr r14r15 uintptr r16r17 uintptr r28r29 uintptr // Y register pc uintptr } // archInit runs architecture-specific setup for the goroutine startup. // Note: adding //go:noinline to work around an AVR backend bug. // //go:noinline func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) { // Store the initial sp for the startTask function (implemented in assembly). s.sp = uintptr(unsafe.Pointer(r)) - 1 // Initialize the registers. // These will be popped off of the stack on the first resume of the goroutine. // Start the function at tinygo_startTask. startTask := uintptr(unsafe.Pointer(&startTask)) r.pc = startTask/2>>8 | startTask/2<<8 // Pass the function to call in r2:r3. // This function is a compiler-generated wrapper which loads arguments out // of a struct pointer. See createGoroutineStartWrapper (defined in // compiler/goroutine.go) for more information. r.r2r3 = fn // Pass the pointer to the arguments struct in r4:45. r.r4r5 = uintptr(args) } func (s *state) resume() { swapTask(s.sp, &systemStack) } func (s *state) pause() { newStack := systemStack systemStack = 0 swapTask(newStack, &s.sp) } // SystemStack returns the system stack pointer when called from a task stack. // When called from the system stack, it returns 0. func SystemStack() uintptr { return systemStack } ================================================ FILE: src/internal/task/task_stack_cortexm.S ================================================ //go:build tinygo // Only generate .debug_frame, don't generate .eh_frame. .cfi_sections .debug_frame .section .text.tinygo_startTask .global tinygo_startTask .type tinygo_startTask, %function tinygo_startTask: .cfi_startproc // Small assembly stub for starting a goroutine. This is already run on the // new stack, with the callee-saved registers already loaded. // Most importantly, r4 contains the pc of the to-be-started function and r5 // contains the only argument it is given. Multiple arguments are packed // into one by storing them in a new allocation. // Indicate to the unwinder that there is nothing to unwind, this is the // root frame. It avoids the following (bogus) error message in GDB: // Backtrace stopped: previous frame identical to this frame (corrupt stack?) .cfi_undefined lr // Set the first argument of the goroutine start wrapper, which contains all // the arguments. mov r0, r5 // Branch to the "goroutine start" function. By using blx instead of bx, // we'll return here instead of tail calling. blx r4 // After return, exit this goroutine. This is a tail call. bl tinygo_task_exit .cfi_endproc .size tinygo_startTask, .-tinygo_startTask .section .text.tinygo_switchToScheduler .global tinygo_switchToScheduler .type tinygo_switchToScheduler, %function tinygo_switchToScheduler: .cfi_startproc // r0 = sp *uintptr // Currently on the task stack (SP=PSP). We need to store the position on // the stack where the in-use registers will be stored. mov r1, sp subs r1, #36 str r1, [r0] b tinygo_swapTask .cfi_endproc .size tinygo_switchToScheduler, .-tinygo_switchToScheduler .section .text.tinygo_switchToTask .global tinygo_switchToTask .type tinygo_switchToTask, %function tinygo_switchToTask: .cfi_startproc // r0 = sp uintptr // Currently on the scheduler stack (SP=MSP). We'll have to update the PSP, // and then we can invoke swapTask. msr PSP, r0 b.n tinygo_swapTask .cfi_endproc .size tinygo_switchToTask, .-tinygo_switchToTask .section .text.tinygo_swapTask .global tinygo_swapTask .type tinygo_swapTask, %function tinygo_swapTask: .cfi_startproc // This function stores the current register state to the stack, switches to // the other stack (MSP/PSP), and loads the register state from the other // stack. Apart from saving and restoring all relevant callee-saved // registers, it also ends with branching to the last program counter (saved // as the lr register, to follow the ARM calling convention). // On pre-Thumb2 CPUs (Cortex-M0 in particular), registers r8-r15 cannot be // used directly. Only very few operations work on them, such as mov. That's // why the higher register values are first stored in the temporary register // r3 when loading/storing them. // It is possible to reduce the swapTask by two instructions (~2 cycles) on // Cortex-M0 by reordering the layout of the pushed registers from {r4-r11, // lr} to {r8-r11, r4-r8, lr}. However, that also requires a change on the // Go side (depending on thumb1/thumb2!) and so is not really worth the // complexity. // Store state to old task. It saves the lr instead of the pc, because that // will be the pc after returning back to the old task (in a different // invocation of swapTask). #if defined(__thumb2__) push {r4-r11, lr} .cfi_def_cfa_offset 9*4 #else mov r0, r8 mov r1, r9 mov r2, r10 mov r3, r11 push {r0-r3, lr} .cfi_def_cfa_offset 5*4 push {r4-r7} .cfi_def_cfa_offset 9*4 #endif // Switch the stack. This could either switch from PSP to MSP, or from MSP // to PSP. By using an XOR (eor), it will just switch to the other stack. mrs r0, CONTROL // load CONTROL register movs r3, #2 eors r0, r0, r3 // flip the SPSEL (active stack pointer) bit msr CONTROL, r0 // store CONTROL register isb // required to flush the pipeline // Load state from new task and branch to the previous position in the // program. #if defined(__thumb2__) pop {r4-r11, pc} #else pop {r4-r7} .cfi_def_cfa_offset 5*4 pop {r0-r3} .cfi_def_cfa_offset 1*4 mov r8, r0 mov r9, r1 mov r10, r2 mov r11, r3 pop {pc} #endif .cfi_endproc .size tinygo_swapTask, .-tinygo_swapTask ================================================ FILE: src/internal/task/task_stack_cortexm.c ================================================ //go:build (scheduler.tasks || scheduler.cores) && cortexm #include uintptr_t SystemStack() { uintptr_t sp; asm volatile( "mrs %0, MSP" : "=r"(sp) : : "memory" ); return sp; } ================================================ FILE: src/internal/task/task_stack_cortexm.go ================================================ //go:build (scheduler.tasks || scheduler.cores) && cortexm package task // Note that this is almost the same as task_stack_arm.go, but it uses the MSP // register to store the system stack pointer instead of a global variable. The // big advantage of this is that interrupts always execute with MSP (and not // PSP, which is used for goroutines) so that goroutines do not need extra stack // space for interrupts. import "C" import ( "unsafe" ) // calleeSavedRegs is the list of registers that must be saved and restored when // switching between tasks. Also see task_stack_cortexm.S that relies on the // exact layout of this struct. type calleeSavedRegs struct { r4 uintptr r5 uintptr r6 uintptr r7 uintptr r8 uintptr r9 uintptr r10 uintptr r11 uintptr pc uintptr } // archInit runs architecture-specific setup for the goroutine startup. func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) { // Store the initial sp for the startTask function (implemented in assembly). s.sp = uintptr(unsafe.Pointer(r)) // Initialize the registers. // These will be popped off of the stack on the first resume of the goroutine. // Start the function at tinygo_startTask (defined in src/internal/task/task_stack_cortexm.S). // This assembly code calls a function (passed in r4) with a single argument (passed in r5). // After the function returns, it calls Pause(). r.pc = uintptr(unsafe.Pointer(&startTask)) // Pass the function to call in r4. // This function is a compiler-generated wrapper which loads arguments out of a struct pointer. // See createGoroutineStartWrapper (defined in compiler/goroutine.go) for more information. r.r4 = fn // Pass the pointer to the arguments struct in r5. r.r5 = uintptr(args) } func (s *state) resume() { tinygo_switchToTask(s.sp) } //export tinygo_switchToTask func tinygo_switchToTask(uintptr) //export tinygo_switchToScheduler func tinygo_switchToScheduler(*uintptr) func (s *state) pause() { tinygo_switchToScheduler(&s.sp) } // SystemStack returns the system stack pointer. On Cortex-M, it is always // available. // //export SystemStack func SystemStack() uintptr ================================================ FILE: src/internal/task/task_stack_esp32.S ================================================ //go:build tinygo .section .text.tinygo_startTask,"ax",@progbits .global tinygo_startTask .type tinygo_startTask, %function tinygo_startTask: // Small assembly stub for starting a goroutine. This already runs on the // new stack, control reaches this function after returning from the initial // tinygo_swapTask below (the retw.n instruction). // // The stack was set up in such a way that it looks as if this function was // paused using tinygo_swapTask by setting up the parent register window and // return pointer as a call4 instruction - except such a call never took // place. Instead, the stack pointer is switched to the new stack after all // live-but-invisible registers have been flushed to the stack. This means // that all registers as present in tinygo_swapTask are moved four up (a2 in // tinygo_swapTask is a6 in this function). We don't use any of those // registers however. Instead, the retw.n instruction will load them through // an underflow exception from the stack which means we get a0-a3 as defined // in task_stack_esp32.go. // Branch to the "goroutine start" function. The first (and only) parameter // is stored in a2, but has to be moved to a6 to make it appear as a2 in the // goroutine start function (due to changing the register window by four // with callx4). mov.n a6, a2 callx4 a3 // After return, exit this goroutine. This call never returns. call4 tinygo_task_exit .section .text.tinygo_swapTask,"ax",@progbits .global tinygo_swapTask .type tinygo_swapTask, %function tinygo_swapTask: // This function gets the following parameters: // a2 = newStack uintptr // a3 = oldStack *uintptr // Reserve 32 bytes on the stack. It really needs to be 32 bytes, with 16 // extra at the bottom to adhere to the ABI. entry sp, 32 // Disable interrupts while flushing registers. This is necessary because // interrupts might want to use the stack pointer (at a2) which will be some // arbitrary register while registers are flushed. rsil a4, 3 // XCHAL_EXCM_LEVEL // Flush all unsaved registers to the stack. // This trick has been borrowed from the Zephyr project: // https://github.com/zephyrproject-rtos/zephyr/blob/d79b003758/arch/xtensa/include/xtensa-asm2-s.h#L17 and a12, a12, a12 rotw 3 and a12, a12, a12 rotw 3 and a12, a12, a12 rotw 3 and a12, a12, a12 rotw 3 and a12, a12, a12 rotw 4 // Restore interrupts. wsr.ps a4 // At this point, the following is true: // WindowStart == 1 << WindowBase // Therefore, we don't need to do this manually. // It also means that the stack pointer can now be safely modified. // Save a0, which stores the return address and the parent register window // in the upper two bits. s32i.n a0, sp, 0 // Save the current stack pointer in oldStack. s32i.n sp, a3, 0 // Switch to the new stack pointer (newStack). mov.n sp, a2 // Load a0, which is the previous return address from before the previous // switch or the constructed return address to tinygo_startTask. This // register also stores the parent register window. l32i.n a0, sp, 0 // Return into the new stack. This instruction will trigger a window // underflow, reloading the saved registers from the stack. retw.n ================================================ FILE: src/internal/task/task_stack_esp32.go ================================================ //go:build scheduler.tasks && (esp32 || esp32s3) package task // The windowed ABI (used on the ESP32) is as follows: // a0: return address (link register) // a1: stack pointer (must be 16-byte aligned) // a2-a7: incoming arguments // a7: stack frame pointer (optional, normally unused in TinyGo) // Sources: // http://cholla.mmto.org/esp8266/xtensa.html // https://0x04.net/~mwk/doc/xtensa.pdf import ( "unsafe" ) var systemStack uintptr // calleeSavedRegs is the list of registers that must be saved and restored when // switching between tasks. Also see task_stack_esp8266.S that relies on the // exact layout of this struct. type calleeSavedRegs struct { // Registers in the register window of tinygo_startTask. a0 uintptr a1 uintptr a2 uintptr a3 uintptr // Locals that can be used by tinygo_swapTask. // The first field is the a0 loaded in tinygo_swapTask, the rest is unused. locals [4]uintptr } // archInit runs architecture-specific setup for the goroutine startup. func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) { // Store the stack pointer for the tinygo_swapTask function (implemented in // assembly). It needs to point to the locals field instead of a0 so that // the retw.n at the end of tinygo_swapTask will return into // tinygo_startTask with a0-a3 loaded (using the register window mechanism). s.sp = uintptr(unsafe.Pointer(&r.locals[0])) // Start the goroutine at tinygo_startTask (defined in // src/internal/task/task_stack_esp32.S). The topmost two bits are not part // of the address but instead store the register window of the caller. // In this case there is no caller, instead we set up the return address as // if tinygo_startTask called tinygo_swapTask with a call4 instruction. r.locals[0] = uintptr(unsafe.Pointer(&startTask))&^(3<<30) | (1 << 30) // Set up the stack pointer inside tinygo_startTask. // Unlike most calling conventions, the windowed ABI actually saves the // stack pointer on the stack to make register windowing work. r.a1 = uintptr(unsafe.Pointer(r)) + 32 // Store the function pointer and the (only) parameter on the stack in a // location that will be reloaded into registers when doing the // pseudo-return to tinygo_startTask using the register window mechanism. r.a3 = fn r.a2 = uintptr(args) } func (s *state) resume() { swapTask(s.sp, &systemStack) } func (s *state) pause() { newStack := systemStack systemStack = 0 swapTask(newStack, &s.sp) } // SystemStack returns the system stack pointer when called from a task stack. // When called from the system stack, it returns 0. func SystemStack() uintptr { return systemStack } ================================================ FILE: src/internal/task/task_stack_esp8266.S ================================================ //go:build tinygo .section .text.tinygo_startTask,"ax",@progbits .global tinygo_startTask .type tinygo_startTask, %function tinygo_startTask: // Small assembly stub for starting a goroutine. This is already run on the // new stack, with the callee-saved registers already loaded. // Most importantly, r4 contains the pc of the to-be-started function and r5 // contains the only argument it is given. Multiple arguments are packed // into one by storing them in a new allocation. // Set the first argument of the goroutine start wrapper, which contains all // the arguments. mov.n a2, a13 // Branch to the "goroutine start" function. callx0 a12 // After return, exit this goroutine. This is a tail call. call0 tinygo_task_exit .size tinygo_startTask, .-tinygo_startTask .global tinygo_swapTask .type tinygo_swapTask, %function tinygo_swapTask: // This function gets the following parameters: // a2 = newStack uintptr // a3 = oldStack *uintptr // Note: // a0 is the return address // a1 is the stack pointer (sp) // Save all callee-saved registers: addi sp, sp, -20 s32i.n a12, sp, 0 s32i.n a13, sp, 4 s32i.n a14, sp, 8 s32i.n a15, sp, 12 s32i.n a0, sp, 16 // Save the current stack pointer in oldStack. s32i.n sp, a3, 0 // Switch to the new stack pointer. mov.n sp, a2 // Load state from new task and branch to the previous position in the // program. l32i.n a12, sp, 0 l32i.n a13, sp, 4 l32i.n a14, sp, 8 l32i.n a15, sp, 12 l32i.n a0, sp, 16 addi sp, sp, 20 ret.n ================================================ FILE: src/internal/task/task_stack_esp8266.go ================================================ //go:build scheduler.tasks && esp8266 package task // Stack switch implementation for the ESP8266, which does not use the windowed // ABI of Xtensa. Registers are assigned as follows: // a0: return address (link register) // a1: stack pointer (must be 16-byte aligned) // a2-a7: incoming arguments // a8: static chain (unused) // a12-a15: callee-saved // a15: stack frame pointer (optional, unused) // Sources: // http://cholla.mmto.org/esp8266/xtensa.html // https://0x04.net/~mwk/doc/xtensa.pdf import "unsafe" var systemStack uintptr // calleeSavedRegs is the list of registers that must be saved and restored when // switching between tasks. Also see task_stack_esp8266.S that relies on the // exact layout of this struct. type calleeSavedRegs struct { a12 uintptr a13 uintptr a14 uintptr a15 uintptr pc uintptr // also link register or r0 } // archInit runs architecture-specific setup for the goroutine startup. func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) { // Store the initial sp for the startTask function (implemented in assembly). s.sp = uintptr(unsafe.Pointer(r)) // Initialize the registers. // These will be popped off of the stack on the first resume of the goroutine. // Start the function at tinygo_startTask (defined in // src/internal/task/task_stack_esp8266.S). // This assembly code calls a function (passed in a12) with a single argument // (passed in a13). After the function returns, it calls Pause(). r.pc = uintptr(unsafe.Pointer(&startTask)) // Pass the function to call in a12. // This function is a compiler-generated wrapper which loads arguments out of a struct pointer. // See createGoroutineStartWrapper (defined in compiler/goroutine.go) for more information. r.a12 = fn // Pass the pointer to the arguments struct in a13. r.a13 = uintptr(args) } func (s *state) resume() { swapTask(s.sp, &systemStack) } func (s *state) pause() { newStack := systemStack systemStack = 0 swapTask(newStack, &s.sp) } // SystemStack returns the system stack pointer when called from a task stack. // When called from the system stack, it returns 0. func SystemStack() uintptr { return systemStack } ================================================ FILE: src/internal/task/task_stack_mipsx.S ================================================ // Do not reorder instructions to insert a branch delay slot. // We know what we're doing, and will manually fill the branch delay slot. .set noreorder .section .text.tinygo_startTask .global tinygo_startTask .type tinygo_startTask, %function tinygo_startTask: // Small assembly stub for starting a goroutine. This is already run on the // new stack, with the callee-saved registers already loaded. // Most importantly, s0 contains the pc of the to-be-started function and s1 // contains the only argument it is given. Multiple arguments are packed // into one by storing them in a new allocation. // Set the first argument of the goroutine start wrapper, which contains all // the arguments. move $a0, $s1 // Branch to the "goroutine start" function. Use jalr to write the return // address to ra so we'll return here after the goroutine exits. jalr $s0 nop // After return, exit this goroutine. This is a tail call. j tinygo_task_exit nop .section .text.tinygo_swapTask .global tinygo_swapTask .type tinygo_swapTask, %function tinygo_swapTask: // This function gets the following parameters: // a0 = newStack uintptr // a1 = oldStack *uintptr // Push all callee-saved registers. addiu $sp, $sp, -40 sw $ra, 36($sp) sw $s8, 32($sp) sw $s7, 28($sp) sw $s6, 24($sp) sw $s5, 20($sp) sw $s4, 16($sp) sw $s3, 12($sp) sw $s2, 8($sp) sw $s1, 4($sp) sw $s0, ($sp) // Save the current stack pointer in oldStack. sw $sp, 0($a1) // Switch to the new stack pointer. move $sp, $a0 // Pop all saved registers from this new stack. lw $ra, 36($sp) lw $s8, 32($sp) lw $s7, 28($sp) lw $s6, 24($sp) lw $s5, 20($sp) lw $s4, 16($sp) lw $s3, 12($sp) lw $s2, 8($sp) lw $s1, 4($sp) lw $s0, ($sp) addiu $sp, $sp, 40 // Return into the task. jalr $ra nop ================================================ FILE: src/internal/task/task_stack_mipsx.go ================================================ //go:build scheduler.tasks && (mips || mipsle) package task import "unsafe" var systemStack uintptr // calleeSavedRegs is the list of registers that must be saved and restored when // switching between tasks. Also see task_stack_mips.S that relies on the exact // layout of this struct. type calleeSavedRegs struct { s0 uintptr s1 uintptr s2 uintptr s3 uintptr s4 uintptr s5 uintptr s6 uintptr s7 uintptr s8 uintptr ra uintptr } // archInit runs architecture-specific setup for the goroutine startup. func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) { // Store the initial sp for the startTask function (implemented in assembly). s.sp = uintptr(unsafe.Pointer(r)) // Initialize the registers. // These will be popped off of the stack on the first resume of the goroutine. // Start the function at tinygo_startTask (defined in src/internal/task/task_stack_mipsle.S). // This assembly code calls a function (passed in s0) with a single argument // (passed in s1). After the function returns, it calls Pause(). r.ra = uintptr(unsafe.Pointer(&startTask)) // Pass the function to call in s0. // This function is a compiler-generated wrapper which loads arguments out of a struct pointer. // See createGoroutineStartWrapper (defined in compiler/goroutine.go) for more information. r.s0 = fn // Pass the pointer to the arguments struct in s1. r.s1 = uintptr(args) } func (s *state) resume() { swapTask(s.sp, &systemStack) } func (s *state) pause() { newStack := systemStack systemStack = 0 swapTask(newStack, &s.sp) } // SystemStack returns the system stack pointer when called from a task stack. // When called from the system stack, it returns 0. func SystemStack() uintptr { return systemStack } ================================================ FILE: src/internal/task/task_stack_multicore.go ================================================ //go:build scheduler.cores package task import "runtime/interrupt" // Current returns the current active task. // //go:linkname Current runtime.currentTask func Current() *Task // Pause suspends the current task and returns to the scheduler. // This function may only be called when running on a goroutine stack, not when running on the system stack or in an interrupt. func Pause() { lockScheduler() PauseLocked() } // PauseLocked is the same as Pause, but must be called with the scheduler lock // already taken. func PauseLocked() { // Check whether the canary (the lowest address of the stack) is still // valid. If it is not, a stack overflow has occurred. current := Current() if *current.state.canaryPtr != stackCanary { runtimePanic("goroutine stack overflow") } if interrupt.In() { runtimePanic("blocked inside interrupt") } if current.RunState == RunStateResuming { // Another core already marked this goroutine as ready to resume. current.RunState = RunStateRunning unlockScheduler() return } current.RunState = RunStatePaused current.state.pause() } // Resume the task until it pauses or completes. // This may only be called from the scheduler. func (t *Task) Resume() { t.gcData.swap() t.state.resume() t.gcData.swap() } //go:linkname lockScheduler runtime.lockScheduler func lockScheduler() //go:linkname unlockScheduler runtime.unlockScheduler func unlockScheduler() ================================================ FILE: src/internal/task/task_stack_tinygoriscv.S ================================================ //go:build tinygo .section .text.tinygo_startTask .global tinygo_startTask .type tinygo_startTask, %function tinygo_startTask: // Small assembly stub for starting a goroutine. This is already run on the // new stack, with the callee-saved registers already loaded. // Most importantly, s0 contains the pc of the to-be-started function and s1 // contains the only argument it is given. Multiple arguments are packed // into one by storing them in a new allocation. // Set the first argument of the goroutine start wrapper, which contains all // the arguments. mv a0, s1 // Branch to the "goroutine start" function. Use jalr to write the return // address to ra so we'll return here after the goroutine exits. jalr s0 // After return, exit this goroutine. This is a tail call. tail tinygo_task_exit .section .text.tinygo_swapTask .global tinygo_swapTask .type tinygo_swapTask, %function tinygo_swapTask: // This function gets the following parameters: // a0 = newStack uintptr // a1 = oldStack *uintptr // Push all callee-saved registers. addi sp, sp, -52 sw ra, 48(sp) sw s11, 44(sp) sw s10, 40(sp) sw s9, 36(sp) sw s8, 32(sp) sw s7, 28(sp) sw s6, 24(sp) sw s5, 20(sp) sw s4, 16(sp) sw s3, 12(sp) sw s2, 8(sp) sw s1, 4(sp) sw s0, (sp) // Save the current stack pointer in oldStack. sw sp, 0(a1) // Switch to the new stack pointer. mv sp, a0 // Pop all saved registers from this new stack. lw ra, 48(sp) lw s11, 44(sp) lw s10, 40(sp) lw s9, 36(sp) lw s8, 32(sp) lw s7, 28(sp) lw s6, 24(sp) lw s5, 20(sp) lw s4, 16(sp) lw s3, 12(sp) lw s2, 8(sp) lw s1, 4(sp) lw s0, (sp) addi sp, sp, 52 // Return into the task. ret ================================================ FILE: src/internal/task/task_stack_tinygoriscv.go ================================================ //go:build (scheduler.tasks || scheduler.cores) && tinygo.riscv package task import "unsafe" // Returns a pointer where the system stack can be stored. // This is a layering violation! We should probably refactor this so that we // don't need such gymnastics to store the system stack pointer. (It should // probably be moved to the runtime). // //go:linkname runtime_systemStackPtr runtime.systemStackPtr func runtime_systemStackPtr() *uintptr // calleeSavedRegs is the list of registers that must be saved and restored when // switching between tasks. Also see scheduler_riscv.S that relies on the // exact layout of this struct. type calleeSavedRegs struct { s0 uintptr // x8 (fp) s1 uintptr // x9 s2 uintptr // x18 s3 uintptr // x19 s4 uintptr // x20 s5 uintptr // x21 s6 uintptr // x22 s7 uintptr // x23 s8 uintptr // x24 s9 uintptr // x25 s10 uintptr // x26 s11 uintptr // x27 pc uintptr } // archInit runs architecture-specific setup for the goroutine startup. func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) { // Store the initial sp for the startTask function (implemented in assembly). s.sp = uintptr(unsafe.Pointer(r)) // Initialize the registers. // These will be popped off of the stack on the first resume of the goroutine. // Start the function at tinygo_startTask (defined in src/internal/task/task_stack_riscv.S). // This assembly code calls a function (passed in s0) with a single argument // (passed in s1). After the function returns, it calls Pause(). r.pc = uintptr(unsafe.Pointer(&startTask)) // Pass the function to call in s0. // This function is a compiler-generated wrapper which loads arguments out // of a struct pointer. See createGoroutineStartWrapper (defined in // compiler/goroutine.go) for more information. r.s0 = fn // Pass the pointer to the arguments struct in s1. r.s1 = uintptr(args) } func (s *state) resume() { swapTask(s.sp, runtime_systemStackPtr()) } func (s *state) pause() { systemStackPtr := runtime_systemStackPtr() newStack := *systemStackPtr *systemStackPtr = 0 swapTask(newStack, &s.sp) } // SystemStack returns the system stack pointer when called from a task stack. // When called from the system stack, it returns 0. func SystemStack() uintptr { return *runtime_systemStackPtr() } ================================================ FILE: src/internal/task/task_stack_unicore.go ================================================ //go:build scheduler.tasks package task import "runtime/interrupt" // currentTask is the current running task, or nil if currently in the scheduler. var currentTask *Task // Current returns the current active task. func Current() *Task { return currentTask } // Pause suspends the current task and returns to the scheduler. // This function may only be called when running on a goroutine stack, not when running on the system stack or in an interrupt. func Pause() { // Check whether the canary (the lowest address of the stack) is still // valid. If it is not, a stack overflow has occurred. if *currentTask.state.canaryPtr != stackCanary { runtimePanic("goroutine stack overflow") } if interrupt.In() { runtimePanic("blocked inside interrupt") } currentTask.state.pause() } // Resume the task until it pauses or completes. // This may only be called from the scheduler. func (t *Task) Resume() { currentTask = t t.gcData.swap() t.state.resume() t.gcData.swap() currentTask = nil } ================================================ FILE: src/internal/task/task_threads.c ================================================ //go:build none #define _GNU_SOURCE #include #include #include #include #include #ifdef __linux__ #include // BDWGC also uses SIGRTMIN+6 on Linux, which seems like a reasonable choice. #define taskPauseSignal (SIGRTMIN + 6) #elif __APPLE__ #include // SIGIO is for interrupt-driven I/O. // I don't think anybody should be using this nowadays, so I think we can // repurpose it as a signal for GC. // BDWGC uses a special way to pause/resume other threads on MacOS, which may be // better but needs more work. Using signal keeps the code similar between Linux // and MacOS. #define taskPauseSignal SIGIO #endif // __linux__, __APPLE__ // Pointer to the current task.Task structure. // Ideally the entire task.Task structure would be a thread-local variable but // this also works. static __thread void *current_task; struct state_pass { void *(*start)(void*); void *args; void *task; uintptr_t *stackTop; #if __APPLE__ dispatch_semaphore_t startlock; #else sem_t startlock; #endif }; // Handle the GC pause in Go. void tinygo_task_gc_pause(int sig); // Initialize the main thread. void tinygo_task_init(void *mainTask, pthread_t *thread, int *numCPU, void *context) { // Make sure the current task pointer is set correctly for the main // goroutine as well. current_task = mainTask; // Store the thread ID of the main thread. *thread = pthread_self(); // Register the "GC pause" signal for the entire process. // Using pthread_kill, we can still send the signal to a specific thread. struct sigaction act = { 0 }; act.sa_handler = tinygo_task_gc_pause; act.sa_flags = SA_RESTART; sigaction(taskPauseSignal, &act, NULL); // Obtain the number of CPUs available on program start (for NumCPU). int num = sysconf(_SC_NPROCESSORS_ONLN); if (num <= 0) { // Fallback in case there is an error. num = 1; } *numCPU = num; } void tinygo_task_exited(void*); // Helper to start a goroutine while also storing the 'task' structure. static void* start_wrapper(void *arg) { struct state_pass *state = arg; void *(*start)(void*) = state->start; void *args = state->args; current_task = state->task; // Save the current stack pointer in the goroutine state, for the GC. int stackAddr; *(state->stackTop) = (uintptr_t)(&stackAddr); // Notify the caller that the thread has successfully started and // initialized. #if __APPLE__ dispatch_semaphore_signal(state->startlock); #else sem_post(&state->startlock); #endif // Run the goroutine function. start(args); // Notify the Go side this thread will exit. tinygo_task_exited(current_task); return NULL; }; // Start a new goroutine in an OS thread. int tinygo_task_start(uintptr_t fn, void *args, void *task, pthread_t *thread, uintptr_t *stackTop, uintptr_t stackSize, void *context) { // Sanity check. Should get optimized away. if (sizeof(pthread_t) != sizeof(void*)) { __builtin_trap(); } struct state_pass state = { .start = (void*)fn, .args = args, .task = task, .stackTop = stackTop, }; #if __APPLE__ state.startlock = dispatch_semaphore_create(0); #else sem_init(&state.startlock, 0, 0); #endif pthread_attr_t attrs; pthread_attr_init(&attrs); pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED); pthread_attr_setstacksize(&attrs, stackSize); int result = pthread_create(thread, &attrs, &start_wrapper, &state); pthread_attr_destroy(&attrs); if (result != 0) { return result; } // Wait until the thread has been created and read all state_pass variables. #if __APPLE__ dispatch_semaphore_wait(state.startlock, DISPATCH_TIME_FOREVER); dispatch_release(state.startlock); #else sem_wait(&state.startlock); sem_destroy(&state.startlock); #endif return result; } // Return the current task (for task.Current()). void* tinygo_task_current(void) { return current_task; } // Send a signal to cause the task to pause for the GC mark phase. void tinygo_task_send_gc_signal(pthread_t thread) { pthread_kill(thread, taskPauseSignal); } ================================================ FILE: src/internal/task/task_threads.go ================================================ //go:build scheduler.threads package task import ( "sync/atomic" "unsafe" ) // If true, print verbose debug logs. const verbose = false // Scheduler-specific state. type state struct { // Goroutine ID. The number here is not really significant and after a while // it could wrap around. But it is useful for debugging. id uintptr // Thread ID, pthread_t or similar (typically implemented as a pointer). thread threadID // Highest address of the stack. It is stored when the goroutine starts, and // is needed to be able to scan the stack. stackTop uintptr // Lowest address of the stack. // This is populated when the thread is stopped by the GC. stackBottom uintptr // Next task in the activeTasks queue. QueueNext *Task // Semaphore to pause/resume the thread atomically. pauseSem Semaphore } // Goroutine counter, starting at 0 for the main goroutine. var goroutineID uintptr var numCPU int32 var mainTask Task // Queue of tasks (see QueueNext) that currently exist in the program. var activeTasks = &mainTask var activeTaskLock PMutex func OnSystemStack() bool { runtimePanic("todo: task.OnSystemStack") return false } // Initialize the main goroutine state. Must be called by the runtime on // startup, before starting any other goroutines. func Init(sp uintptr) { mainTask.state.stackTop = sp tinygo_task_init(&mainTask, &mainTask.state.thread, &numCPU) } // Return the task struct for the current thread. func Current() *Task { t := (*Task)(tinygo_task_current()) if t == nil { runtimePanic("unknown current task") } return t } // Pause pauses the current task, until it is resumed by another task. // It is possible that another task has called Resume() on the task before it // hits Pause(), in which case the task won't be paused but continues // immediately. func Pause() { // Wait until resumed t := Current() if verbose { println("*** pause: ", t.state.id) } t.state.pauseSem.Wait() } // Resume the given task. // It is legal to resume a task before it gets paused, it means that the next // call to Pause() won't pause but will continue immediately. This happens in // practice sometimes in channel operations, where the Resume() might get called // between the channel unlock and the call to Pause(). func (t *Task) Resume() { if verbose { println("*** resume: ", t.state.id) } // Increment the semaphore counter. // If the task is currently paused in Wait(), it will resume. // If the task is not yet paused, the next call to Wait() will continue // immediately. t.state.pauseSem.Post() } // otherGoroutines is the total number of live goroutines minus one. var otherGoroutines uint32 // Start a new OS thread. func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) { t := &Task{} t.state.id = atomic.AddUintptr(&goroutineID, 1) if verbose { println("*** start: ", t.state.id, "from", Current().state.id) } // Start the new thread, and add it to the list of threads. // Do this with a lock so that only started threads are part of the queue // and the stop-the-world GC won't see threads that haven't started yet or // are not fully started yet. activeTaskLock.Lock() errCode := tinygo_task_start(fn, args, t, &t.state.thread, &t.state.stackTop, stackSize) if errCode != 0 { runtimePanic("could not start thread") } t.state.QueueNext = activeTasks activeTasks = t otherGoroutines++ activeTaskLock.Unlock() } //export tinygo_task_exited func taskExited(t *Task) { if verbose { println("*** exit:", t.state.id) } // Remove from the queue. // TODO: this can be made more efficient by using a doubly linked list. activeTaskLock.Lock() found := false for q := &activeTasks; *q != nil; q = &(*q).state.QueueNext { if *q == t { *q = t.state.QueueNext found = true break } } otherGoroutines-- activeTaskLock.Unlock() // Sanity check. if !found { runtimePanic("taskExited failed") } } // scanWaitGroup is used to wait on until all threads have finished the current state transition. var scanWaitGroup waitGroup type waitGroup struct { f Futex } func initWaitGroup(n uint32) waitGroup { var wg waitGroup wg.f.Store(n) return wg } func (wg *waitGroup) done() { if wg.f.Add(^uint32(0)) == 0 { wg.f.WakeAll() } } func (wg *waitGroup) wait() { for { val := wg.f.Load() if val == 0 { return } wg.f.Wait(val) } } // gcState is used to track and notify threads when the GC is stopping/resuming. var gcState Futex const ( gcStateResumed = iota gcStateStopped ) // GC scan phase. Because we need to stop the world while scanning, this kinda // needs to be done in the tasks package. // // After calling this function, GCResumeWorld needs to be called once to resume // all threads again. func GCStopWorldAndScan() { current := Current() // NOTE: This does not need to be atomic. if gcState.Load() == gcStateResumed { // Don't allow new goroutines to be started while pausing/resuming threads // in the stop-the-world phase. activeTaskLock.Lock() // Wait for threads to finish resuming. scanWaitGroup.wait() // Change the gc state to stopped. // NOTE: This does not need to be atomic. gcState.Store(gcStateStopped) // Set the number of threads to wait for. scanWaitGroup = initWaitGroup(otherGoroutines) // Pause all other threads. for t := activeTasks; t != nil; t = t.state.QueueNext { if t != current { tinygo_task_send_gc_signal(t.state.thread) } } // Wait for the threads to finish stopping. scanWaitGroup.wait() } // Scan other thread stacks. for t := activeTasks; t != nil; t = t.state.QueueNext { if t != current { markRoots(t.state.stackBottom, t.state.stackTop) } } // Scan the current stack, and all current registers. scanCurrentStack() // Scan all globals (implemented in the runtime). gcScanGlobals() } // After the GC is done scanning, resume all other threads. func GCResumeWorld() { // NOTE: This does not need to be atomic. if gcState.Load() == gcStateResumed { // This is already resumed. return } // Set the wait group to track resume progress. scanWaitGroup = initWaitGroup(otherGoroutines) // Set the state to resumed. gcState.Store(gcStateResumed) // Wake all of the stopped threads. gcState.WakeAll() // Allow goroutines to start and exit again. activeTaskLock.Unlock() } //go:linkname markRoots runtime.markRoots func markRoots(start, end uintptr) // Scan globals, implemented in the runtime package. func gcScanGlobals() var stackScanLock PMutex //export tinygo_task_gc_pause func tingyo_task_gc_pause(sig int32) { // Write the entrty stack pointer to the state. Current().state.stackBottom = uintptr(stacksave()) // Notify the GC that we are stopped. scanWaitGroup.done() // Wait for the GC to resume. for gcState.Load() == gcStateStopped { gcState.Wait(gcStateStopped) } // Notify the GC that we have resumed. scanWaitGroup.done() } //go:export tinygo_scanCurrentStack func scanCurrentStack() //go:linkname stacksave runtime.stacksave func stacksave() unsafe.Pointer // Return the highest address of the current stack. func StackTop() uintptr { return Current().state.stackTop } //go:linkname runtimePanic runtime.runtimePanic func runtimePanic(msg string) // Using //go:linkname instead of //export so that we don't tell the compiler // that the 't' parameter won't escape (because it will). // //go:linkname tinygo_task_init tinygo_task_init func tinygo_task_init(t *Task, thread *threadID, numCPU *int32) // Here same as for tinygo_task_init. // //go:linkname tinygo_task_start tinygo_task_start func tinygo_task_start(fn uintptr, args unsafe.Pointer, t *Task, thread *threadID, stackTop *uintptr, stackSize uintptr) int32 // Pause the thread by sending it a signal. // //export tinygo_task_send_gc_signal func tinygo_task_send_gc_signal(threadID) //export tinygo_task_current func tinygo_task_current() unsafe.Pointer func NumCPU() int { return int(numCPU) } ================================================ FILE: src/internal/wasi/cli/v0.2.0/command/command.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package command represents the world "wasi:cli/command@0.2.0". package command ================================================ FILE: src/internal/wasi/cli/v0.2.0/environment/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package environment import ( "internal/cm" ) // This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". //go:wasmimport wasi:cli/environment@0.2.0 get-environment //go:noescape func wasmimport_GetEnvironment(result *cm.List[[2]string]) //go:wasmimport wasi:cli/environment@0.2.0 get-arguments //go:noescape func wasmimport_GetArguments(result *cm.List[string]) //go:wasmimport wasi:cli/environment@0.2.0 initial-cwd //go:noescape func wasmimport_InitialCWD(result *cm.Option[string]) ================================================ FILE: src/internal/wasi/cli/v0.2.0/environment/environment.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package environment represents the imported interface "wasi:cli/environment@0.2.0". package environment import ( "internal/cm" ) // GetEnvironment represents the imported function "get-environment". // // Get the POSIX-style environment variables. // // Each environment variable is provided as a pair of string variable names // and string value. // // Morally, these are a value import, but until value imports are available // in the component model, this import function should return the same // values each time it is called. // // get-environment: func() -> list> // //go:nosplit func GetEnvironment() (result cm.List[[2]string]) { wasmimport_GetEnvironment(&result) return } // GetArguments represents the imported function "get-arguments". // // Get the POSIX-style arguments to the program. // // get-arguments: func() -> list // //go:nosplit func GetArguments() (result cm.List[string]) { wasmimport_GetArguments(&result) return } // InitialCWD represents the imported function "initial-cwd". // // Return a path that programs should use as their initial current working // directory, interpreting `.` as shorthand for this. // // initial-cwd: func() -> option // //go:nosplit func InitialCWD() (result cm.Option[string]) { wasmimport_InitialCWD(&result) return } ================================================ FILE: src/internal/wasi/cli/v0.2.0/exit/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package exit // This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". //go:wasmimport wasi:cli/exit@0.2.0 exit //go:noescape func wasmimport_Exit(status0 uint32) ================================================ FILE: src/internal/wasi/cli/v0.2.0/exit/exit.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package exit represents the imported interface "wasi:cli/exit@0.2.0". package exit import ( "internal/cm" ) // Exit represents the imported function "exit". // // Exit the current instance and any linked instances. // // exit: func(status: result) // //go:nosplit func Exit(status cm.BoolResult) { status0 := (uint32)(cm.BoolToU32(status)) wasmimport_Exit((uint32)(status0)) return } ================================================ FILE: src/internal/wasi/cli/v0.2.0/run/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/cli/v0.2.0/run/run.exports.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package run import ( "internal/cm" ) // Exports represents the caller-defined exports from "wasi:cli/run@0.2.0". var Exports struct { // Run represents the caller-defined, exported function "run". // // Run the program. // // run: func() -> result Run func() (result cm.BoolResult) } ================================================ FILE: src/internal/wasi/cli/v0.2.0/run/run.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package run import ( "internal/cm" ) // This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". //go:wasmexport wasi:cli/run@0.2.0#run //export wasi:cli/run@0.2.0#run func wasmexport_Run() (result0 uint32) { result := Exports.Run() result0 = (uint32)(cm.BoolToU32(result)) return } ================================================ FILE: src/internal/wasi/cli/v0.2.0/run/run.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package run represents the exported interface "wasi:cli/run@0.2.0". package run ================================================ FILE: src/internal/wasi/cli/v0.2.0/stderr/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package stderr // This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". //go:wasmimport wasi:cli/stderr@0.2.0 get-stderr //go:noescape func wasmimport_GetStderr() (result0 uint32) ================================================ FILE: src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package stderr represents the imported interface "wasi:cli/stderr@0.2.0". package stderr import ( "internal/cm" "internal/wasi/io/v0.2.0/streams" ) // OutputStream represents the imported type alias "wasi:cli/stderr@0.2.0#output-stream". // // See [streams.OutputStream] for more information. type OutputStream = streams.OutputStream // GetStderr represents the imported function "get-stderr". // // get-stderr: func() -> output-stream // //go:nosplit func GetStderr() (result OutputStream) { result0 := wasmimport_GetStderr() result = cm.Reinterpret[OutputStream]((uint32)(result0)) return } ================================================ FILE: src/internal/wasi/cli/v0.2.0/stdin/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package stdin // This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". //go:wasmimport wasi:cli/stdin@0.2.0 get-stdin //go:noescape func wasmimport_GetStdin() (result0 uint32) ================================================ FILE: src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package stdin represents the imported interface "wasi:cli/stdin@0.2.0". package stdin import ( "internal/cm" "internal/wasi/io/v0.2.0/streams" ) // InputStream represents the imported type alias "wasi:cli/stdin@0.2.0#input-stream". // // See [streams.InputStream] for more information. type InputStream = streams.InputStream // GetStdin represents the imported function "get-stdin". // // get-stdin: func() -> input-stream // //go:nosplit func GetStdin() (result InputStream) { result0 := wasmimport_GetStdin() result = cm.Reinterpret[InputStream]((uint32)(result0)) return } ================================================ FILE: src/internal/wasi/cli/v0.2.0/stdout/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package stdout // This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". //go:wasmimport wasi:cli/stdout@0.2.0 get-stdout //go:noescape func wasmimport_GetStdout() (result0 uint32) ================================================ FILE: src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package stdout represents the imported interface "wasi:cli/stdout@0.2.0". package stdout import ( "internal/cm" "internal/wasi/io/v0.2.0/streams" ) // OutputStream represents the imported type alias "wasi:cli/stdout@0.2.0#output-stream". // // See [streams.OutputStream] for more information. type OutputStream = streams.OutputStream // GetStdout represents the imported function "get-stdout". // // get-stdout: func() -> output-stream // //go:nosplit func GetStdout() (result OutputStream) { result0 := wasmimport_GetStdout() result = cm.Reinterpret[OutputStream]((uint32)(result0)) return } ================================================ FILE: src/internal/wasi/cli/v0.2.0/terminal-input/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package terminalinput // This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". //go:wasmimport wasi:cli/terminal-input@0.2.0 [resource-drop]terminal-input //go:noescape func wasmimport_TerminalInputResourceDrop(self0 uint32) ================================================ FILE: src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package terminalinput represents the imported interface "wasi:cli/terminal-input@0.2.0". // // Terminal input. // // In the future, this may include functions for disabling echoing, // disabling input buffering so that keyboard events are sent through // immediately, querying supported features, and so on. package terminalinput import ( "internal/cm" ) // TerminalInput represents the imported resource "wasi:cli/terminal-input@0.2.0#terminal-input". // // The input side of a terminal. // // resource terminal-input type TerminalInput cm.Resource // ResourceDrop represents the imported resource-drop for resource "terminal-input". // // Drops a resource handle. // //go:nosplit func (self TerminalInput) ResourceDrop() { self0 := cm.Reinterpret[uint32](self) wasmimport_TerminalInputResourceDrop((uint32)(self0)) return } ================================================ FILE: src/internal/wasi/cli/v0.2.0/terminal-output/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package terminaloutput // This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". //go:wasmimport wasi:cli/terminal-output@0.2.0 [resource-drop]terminal-output //go:noescape func wasmimport_TerminalOutputResourceDrop(self0 uint32) ================================================ FILE: src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package terminaloutput represents the imported interface "wasi:cli/terminal-output@0.2.0". // // Terminal output. // // In the future, this may include functions for querying the terminal // size, being notified of terminal size changes, querying supported // features, and so on. package terminaloutput import ( "internal/cm" ) // TerminalOutput represents the imported resource "wasi:cli/terminal-output@0.2.0#terminal-output". // // The output side of a terminal. // // resource terminal-output type TerminalOutput cm.Resource // ResourceDrop represents the imported resource-drop for resource "terminal-output". // // Drops a resource handle. // //go:nosplit func (self TerminalOutput) ResourceDrop() { self0 := cm.Reinterpret[uint32](self) wasmimport_TerminalOutputResourceDrop((uint32)(self0)) return } ================================================ FILE: src/internal/wasi/cli/v0.2.0/terminal-stderr/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package terminalstderr import ( "internal/cm" ) // This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". //go:wasmimport wasi:cli/terminal-stderr@0.2.0 get-terminal-stderr //go:noescape func wasmimport_GetTerminalStderr(result *cm.Option[TerminalOutput]) ================================================ FILE: src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package terminalstderr represents the imported interface "wasi:cli/terminal-stderr@0.2.0". // // An interface providing an optional `terminal-output` for stderr as a // link-time authority. package terminalstderr import ( "internal/cm" terminaloutput "internal/wasi/cli/v0.2.0/terminal-output" ) // TerminalOutput represents the imported type alias "wasi:cli/terminal-stderr@0.2.0#terminal-output". // // See [terminaloutput.TerminalOutput] for more information. type TerminalOutput = terminaloutput.TerminalOutput // GetTerminalStderr represents the imported function "get-terminal-stderr". // // If stderr is connected to a terminal, return a `terminal-output` handle // allowing further interaction with it. // // get-terminal-stderr: func() -> option // //go:nosplit func GetTerminalStderr() (result cm.Option[TerminalOutput]) { wasmimport_GetTerminalStderr(&result) return } ================================================ FILE: src/internal/wasi/cli/v0.2.0/terminal-stdin/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package terminalstdin import ( "internal/cm" ) // This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". //go:wasmimport wasi:cli/terminal-stdin@0.2.0 get-terminal-stdin //go:noescape func wasmimport_GetTerminalStdin(result *cm.Option[TerminalInput]) ================================================ FILE: src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package terminalstdin represents the imported interface "wasi:cli/terminal-stdin@0.2.0". // // An interface providing an optional `terminal-input` for stdin as a // link-time authority. package terminalstdin import ( "internal/cm" terminalinput "internal/wasi/cli/v0.2.0/terminal-input" ) // TerminalInput represents the imported type alias "wasi:cli/terminal-stdin@0.2.0#terminal-input". // // See [terminalinput.TerminalInput] for more information. type TerminalInput = terminalinput.TerminalInput // GetTerminalStdin represents the imported function "get-terminal-stdin". // // If stdin is connected to a terminal, return a `terminal-input` handle // allowing further interaction with it. // // get-terminal-stdin: func() -> option // //go:nosplit func GetTerminalStdin() (result cm.Option[TerminalInput]) { wasmimport_GetTerminalStdin(&result) return } ================================================ FILE: src/internal/wasi/cli/v0.2.0/terminal-stdout/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package terminalstdout import ( "internal/cm" ) // This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". //go:wasmimport wasi:cli/terminal-stdout@0.2.0 get-terminal-stdout //go:noescape func wasmimport_GetTerminalStdout(result *cm.Option[TerminalOutput]) ================================================ FILE: src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package terminalstdout represents the imported interface "wasi:cli/terminal-stdout@0.2.0". // // An interface providing an optional `terminal-output` for stdout as a // link-time authority. package terminalstdout import ( "internal/cm" terminaloutput "internal/wasi/cli/v0.2.0/terminal-output" ) // TerminalOutput represents the imported type alias "wasi:cli/terminal-stdout@0.2.0#terminal-output". // // See [terminaloutput.TerminalOutput] for more information. type TerminalOutput = terminaloutput.TerminalOutput // GetTerminalStdout represents the imported function "get-terminal-stdout". // // If stdout is connected to a terminal, return a `terminal-output` handle // allowing further interaction with it. // // get-terminal-stdout: func() -> option // //go:nosplit func GetTerminalStdout() (result cm.Option[TerminalOutput]) { wasmimport_GetTerminalStdout(&result) return } ================================================ FILE: src/internal/wasi/clocks/v0.2.0/monotonic-clock/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package monotonicclock // This file contains wasmimport and wasmexport declarations for "wasi:clocks@0.2.0". //go:wasmimport wasi:clocks/monotonic-clock@0.2.0 now //go:noescape func wasmimport_Now() (result0 uint64) //go:wasmimport wasi:clocks/monotonic-clock@0.2.0 resolution //go:noescape func wasmimport_Resolution() (result0 uint64) //go:wasmimport wasi:clocks/monotonic-clock@0.2.0 subscribe-instant //go:noescape func wasmimport_SubscribeInstant(when0 uint64) (result0 uint32) //go:wasmimport wasi:clocks/monotonic-clock@0.2.0 subscribe-duration //go:noescape func wasmimport_SubscribeDuration(when0 uint64) (result0 uint32) ================================================ FILE: src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package monotonicclock represents the imported interface "wasi:clocks/monotonic-clock@0.2.0". // // WASI Monotonic Clock is a clock API intended to let users measure elapsed // time. // // It is intended to be portable at least between Unix-family platforms and // Windows. // // A monotonic clock is a clock which has an unspecified initial value, and // successive reads of the clock will produce non-decreasing values. // // It is intended for measuring elapsed time. package monotonicclock import ( "internal/cm" "internal/wasi/io/v0.2.0/poll" ) // Pollable represents the imported type alias "wasi:clocks/monotonic-clock@0.2.0#pollable". // // See [poll.Pollable] for more information. type Pollable = poll.Pollable // Instant represents the u64 "wasi:clocks/monotonic-clock@0.2.0#instant". // // An instant in time, in nanoseconds. An instant is relative to an // unspecified initial value, and can only be compared to instances from // the same monotonic-clock. // // type instant = u64 type Instant uint64 // Duration represents the u64 "wasi:clocks/monotonic-clock@0.2.0#duration". // // A duration of time, in nanoseconds. // // type duration = u64 type Duration uint64 // Now represents the imported function "now". // // Read the current value of the clock. // // The clock is monotonic, therefore calling this function repeatedly will // produce a sequence of non-decreasing values. // // now: func() -> instant // //go:nosplit func Now() (result Instant) { result0 := wasmimport_Now() result = (Instant)((uint64)(result0)) return } // Resolution represents the imported function "resolution". // // Query the resolution of the clock. Returns the duration of time // corresponding to a clock tick. // // resolution: func() -> duration // //go:nosplit func Resolution() (result Duration) { result0 := wasmimport_Resolution() result = (Duration)((uint64)(result0)) return } // SubscribeInstant represents the imported function "subscribe-instant". // // Create a `pollable` which will resolve once the specified instant // occured. // // subscribe-instant: func(when: instant) -> pollable // //go:nosplit func SubscribeInstant(when Instant) (result Pollable) { when0 := (uint64)(when) result0 := wasmimport_SubscribeInstant((uint64)(when0)) result = cm.Reinterpret[Pollable]((uint32)(result0)) return } // SubscribeDuration represents the imported function "subscribe-duration". // // Create a `pollable` which will resolve once the given duration has // elapsed, starting at the time at which this function was called. // occured. // // subscribe-duration: func(when: duration) -> pollable // //go:nosplit func SubscribeDuration(when Duration) (result Pollable) { when0 := (uint64)(when) result0 := wasmimport_SubscribeDuration((uint64)(when0)) result = cm.Reinterpret[Pollable]((uint32)(result0)) return } ================================================ FILE: src/internal/wasi/clocks/v0.2.0/wall-clock/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package wallclock // This file contains wasmimport and wasmexport declarations for "wasi:clocks@0.2.0". //go:wasmimport wasi:clocks/wall-clock@0.2.0 now //go:noescape func wasmimport_Now(result *DateTime) //go:wasmimport wasi:clocks/wall-clock@0.2.0 resolution //go:noescape func wasmimport_Resolution(result *DateTime) ================================================ FILE: src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package wallclock represents the imported interface "wasi:clocks/wall-clock@0.2.0". // // WASI Wall Clock is a clock API intended to let users query the current // time. The name "wall" makes an analogy to a "clock on the wall", which // is not necessarily monotonic as it may be reset. // // It is intended to be portable at least between Unix-family platforms and // Windows. // // A wall clock is a clock which measures the date and time according to // some external reference. // // External references may be reset, so this clock is not necessarily // monotonic, making it unsuitable for measuring elapsed time. // // It is intended for reporting the current date and time for humans. package wallclock import ( "internal/cm" ) // DateTime represents the record "wasi:clocks/wall-clock@0.2.0#datetime". // // A time and date in seconds plus nanoseconds. // // record datetime { // seconds: u64, // nanoseconds: u32, // } type DateTime struct { _ cm.HostLayout `json:"-"` Seconds uint64 `json:"seconds"` Nanoseconds uint32 `json:"nanoseconds"` } // Now represents the imported function "now". // // Read the current value of the clock. // // This clock is not monotonic, therefore calling this function repeatedly // will not necessarily produce a sequence of non-decreasing values. // // The returned timestamps represent the number of seconds since // 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], // also known as [Unix Time]. // // The nanoseconds field of the output is always less than 1000000000. // // now: func() -> datetime // // [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 // [Unix Time]: https://en.wikipedia.org/wiki/Unix_time // //go:nosplit func Now() (result DateTime) { wasmimport_Now(&result) return } // Resolution represents the imported function "resolution". // // Query the resolution of the clock. // // The nanoseconds field of the output is always less than 1000000000. // // resolution: func() -> datetime // //go:nosplit func Resolution() (result DateTime) { wasmimport_Resolution(&result) return } ================================================ FILE: src/internal/wasi/filesystem/v0.2.0/preopens/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package preopens import ( "internal/cm" ) // This file contains wasmimport and wasmexport declarations for "wasi:filesystem@0.2.0". //go:wasmimport wasi:filesystem/preopens@0.2.0 get-directories //go:noescape func wasmimport_GetDirectories(result *cm.List[cm.Tuple[Descriptor, string]]) ================================================ FILE: src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package preopens represents the imported interface "wasi:filesystem/preopens@0.2.0". package preopens import ( "internal/cm" "internal/wasi/filesystem/v0.2.0/types" ) // Descriptor represents the imported type alias "wasi:filesystem/preopens@0.2.0#descriptor". // // See [types.Descriptor] for more information. type Descriptor = types.Descriptor // GetDirectories represents the imported function "get-directories". // // Return the set of preopened directories, and their path. // // get-directories: func() -> list> // //go:nosplit func GetDirectories() (result cm.List[cm.Tuple[Descriptor, string]]) { wasmimport_GetDirectories(&result) return } ================================================ FILE: src/internal/wasi/filesystem/v0.2.0/types/abi.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package types import ( "internal/cm" wallclock "internal/wasi/clocks/v0.2.0/wall-clock" "unsafe" ) // MetadataHashValueShape is used for storage in variant or result types. type MetadataHashValueShape struct { _ cm.HostLayout shape [unsafe.Sizeof(MetadataHashValue{})]byte } // TupleListU8BoolShape is used for storage in variant or result types. type TupleListU8BoolShape struct { _ cm.HostLayout shape [unsafe.Sizeof(cm.Tuple[cm.List[uint8], bool]{})]byte } func lower_DateTime(v wallclock.DateTime) (f0 uint64, f1 uint32) { f0 = (uint64)(v.Seconds) f1 = (uint32)(v.Nanoseconds) return } func lower_NewTimestamp(v NewTimestamp) (f0 uint32, f1 uint64, f2 uint32) { f0 = (uint32)(v.Tag()) switch f0 { case 2: // timestamp v1, v2 := lower_DateTime(*cm.Case[DateTime](&v, 2)) f1 = (uint64)(v1) f2 = (uint32)(v2) } return } // DescriptorStatShape is used for storage in variant or result types. type DescriptorStatShape struct { _ cm.HostLayout shape [unsafe.Sizeof(DescriptorStat{})]byte } // OptionDirectoryEntryShape is used for storage in variant or result types. type OptionDirectoryEntryShape struct { _ cm.HostLayout shape [unsafe.Sizeof(cm.Option[DirectoryEntry]{})]byte } ================================================ FILE: src/internal/wasi/filesystem/v0.2.0/types/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package types import ( "internal/cm" ) // This file contains wasmimport and wasmexport declarations for "wasi:filesystem@0.2.0". //go:wasmimport wasi:filesystem/types@0.2.0 [resource-drop]descriptor //go:noescape func wasmimport_DescriptorResourceDrop(self0 uint32) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.advise //go:noescape func wasmimport_DescriptorAdvise(self0 uint32, offset0 uint64, length0 uint64, advice0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.append-via-stream //go:noescape func wasmimport_DescriptorAppendViaStream(self0 uint32, result *cm.Result[OutputStream, OutputStream, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.create-directory-at //go:noescape func wasmimport_DescriptorCreateDirectoryAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.get-flags //go:noescape func wasmimport_DescriptorGetFlags(self0 uint32, result *cm.Result[DescriptorFlags, DescriptorFlags, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.get-type //go:noescape func wasmimport_DescriptorGetType(self0 uint32, result *cm.Result[DescriptorType, DescriptorType, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.is-same-object //go:noescape func wasmimport_DescriptorIsSameObject(self0 uint32, other0 uint32) (result0 uint32) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.link-at //go:noescape func wasmimport_DescriptorLinkAt(self0 uint32, oldPathFlags0 uint32, oldPath0 *uint8, oldPath1 uint32, newDescriptor0 uint32, newPath0 *uint8, newPath1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.metadata-hash //go:noescape func wasmimport_DescriptorMetadataHash(self0 uint32, result *cm.Result[MetadataHashValueShape, MetadataHashValue, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.metadata-hash-at //go:noescape func wasmimport_DescriptorMetadataHashAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, result *cm.Result[MetadataHashValueShape, MetadataHashValue, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.open-at //go:noescape func wasmimport_DescriptorOpenAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, openFlags0 uint32, flags0 uint32, result *cm.Result[Descriptor, Descriptor, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.read //go:noescape func wasmimport_DescriptorRead(self0 uint32, length0 uint64, offset0 uint64, result *cm.Result[TupleListU8BoolShape, cm.Tuple[cm.List[uint8], bool], ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.read-directory //go:noescape func wasmimport_DescriptorReadDirectory(self0 uint32, result *cm.Result[DirectoryEntryStream, DirectoryEntryStream, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.read-via-stream //go:noescape func wasmimport_DescriptorReadViaStream(self0 uint32, offset0 uint64, result *cm.Result[InputStream, InputStream, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.readlink-at //go:noescape func wasmimport_DescriptorReadLinkAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.Result[string, string, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.remove-directory-at //go:noescape func wasmimport_DescriptorRemoveDirectoryAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.rename-at //go:noescape func wasmimport_DescriptorRenameAt(self0 uint32, oldPath0 *uint8, oldPath1 uint32, newDescriptor0 uint32, newPath0 *uint8, newPath1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.set-size //go:noescape func wasmimport_DescriptorSetSize(self0 uint32, size0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.set-times //go:noescape func wasmimport_DescriptorSetTimes(self0 uint32, dataAccessTimestamp0 uint32, dataAccessTimestamp1 uint64, dataAccessTimestamp2 uint32, dataModificationTimestamp0 uint32, dataModificationTimestamp1 uint64, dataModificationTimestamp2 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.set-times-at //go:noescape func wasmimport_DescriptorSetTimesAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, dataAccessTimestamp0 uint32, dataAccessTimestamp1 uint64, dataAccessTimestamp2 uint32, dataModificationTimestamp0 uint32, dataModificationTimestamp1 uint64, dataModificationTimestamp2 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.stat //go:noescape func wasmimport_DescriptorStat(self0 uint32, result *cm.Result[DescriptorStatShape, DescriptorStat, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.stat-at //go:noescape func wasmimport_DescriptorStatAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, result *cm.Result[DescriptorStatShape, DescriptorStat, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.symlink-at //go:noescape func wasmimport_DescriptorSymlinkAt(self0 uint32, oldPath0 *uint8, oldPath1 uint32, newPath0 *uint8, newPath1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.sync //go:noescape func wasmimport_DescriptorSync(self0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.sync-data //go:noescape func wasmimport_DescriptorSyncData(self0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.unlink-file-at //go:noescape func wasmimport_DescriptorUnlinkFileAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.write //go:noescape func wasmimport_DescriptorWrite(self0 uint32, buffer0 *uint8, buffer1 uint32, offset0 uint64, result *cm.Result[uint64, FileSize, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.write-via-stream //go:noescape func wasmimport_DescriptorWriteViaStream(self0 uint32, offset0 uint64, result *cm.Result[OutputStream, OutputStream, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [resource-drop]directory-entry-stream //go:noescape func wasmimport_DirectoryEntryStreamResourceDrop(self0 uint32) //go:wasmimport wasi:filesystem/types@0.2.0 [method]directory-entry-stream.read-directory-entry //go:noescape func wasmimport_DirectoryEntryStreamReadDirectoryEntry(self0 uint32, result *cm.Result[OptionDirectoryEntryShape, cm.Option[DirectoryEntry], ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 filesystem-error-code //go:noescape func wasmimport_FilesystemErrorCode(err0 uint32, result *cm.Option[ErrorCode]) ================================================ FILE: src/internal/wasi/filesystem/v0.2.0/types/types.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package types represents the imported interface "wasi:filesystem/types@0.2.0". // // WASI filesystem is a filesystem API primarily intended to let users run WASI // programs that access their files on their existing filesystems, without // significant overhead. // // It is intended to be roughly portable between Unix-family platforms and // Windows, though it does not hide many of the major differences. // // Paths are passed as interface-type `string`s, meaning they must consist of // a sequence of Unicode Scalar Values (USVs). Some filesystems may contain // paths which are not accessible by this API. // // The directory separator in WASI is always the forward-slash (`/`). // // All paths in WASI are relative paths, and are interpreted relative to a // `descriptor` referring to a base directory. If a `path` argument to any WASI // function starts with `/`, or if any step of resolving a `path`, including // `..` and symbolic link steps, reaches a directory outside of the base // directory, or reaches a symlink to an absolute or rooted path in the // underlying filesystem, the function fails with `error-code::not-permitted`. // // For more information about WASI path resolution and sandboxing, see // [WASI filesystem path resolution]. // // [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md package types import ( "internal/cm" wallclock "internal/wasi/clocks/v0.2.0/wall-clock" "internal/wasi/io/v0.2.0/streams" ) // InputStream represents the imported type alias "wasi:filesystem/types@0.2.0#input-stream". // // See [streams.InputStream] for more information. type InputStream = streams.InputStream // OutputStream represents the imported type alias "wasi:filesystem/types@0.2.0#output-stream". // // See [streams.OutputStream] for more information. type OutputStream = streams.OutputStream // Error represents the imported type alias "wasi:filesystem/types@0.2.0#error". // // See [streams.Error] for more information. type Error = streams.Error // DateTime represents the type alias "wasi:filesystem/types@0.2.0#datetime". // // See [wallclock.DateTime] for more information. type DateTime = wallclock.DateTime // FileSize represents the u64 "wasi:filesystem/types@0.2.0#filesize". // // File size or length of a region within a file. // // type filesize = u64 type FileSize uint64 // DescriptorType represents the enum "wasi:filesystem/types@0.2.0#descriptor-type". // // The type of a filesystem object referenced by a descriptor. // // Note: This was called `filetype` in earlier versions of WASI. // // enum descriptor-type { // unknown, // block-device, // character-device, // directory, // fifo, // symbolic-link, // regular-file, // socket // } type DescriptorType uint8 const ( // The type of the descriptor or file is unknown or is different from // any of the other types specified. DescriptorTypeUnknown DescriptorType = iota // The descriptor refers to a block device inode. DescriptorTypeBlockDevice // The descriptor refers to a character device inode. DescriptorTypeCharacterDevice // The descriptor refers to a directory inode. DescriptorTypeDirectory // The descriptor refers to a named pipe. DescriptorTypeFIFO // The file refers to a symbolic link inode. DescriptorTypeSymbolicLink // The descriptor refers to a regular file inode. DescriptorTypeRegularFile // The descriptor refers to a socket. DescriptorTypeSocket ) var _DescriptorTypeStrings = [8]string{ "unknown", "block-device", "character-device", "directory", "fifo", "symbolic-link", "regular-file", "socket", } // String implements [fmt.Stringer], returning the enum case name of e. func (e DescriptorType) String() string { return _DescriptorTypeStrings[e] } // MarshalText implements [encoding.TextMarshaler]. func (e DescriptorType) MarshalText() ([]byte, error) { return []byte(e.String()), nil } // UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum // case. Returns an error if the supplied text is not one of the enum cases. func (e *DescriptorType) UnmarshalText(text []byte) error { return _DescriptorTypeUnmarshalCase(e, text) } var _DescriptorTypeUnmarshalCase = cm.CaseUnmarshaler[DescriptorType](_DescriptorTypeStrings[:]) // DescriptorFlags represents the flags "wasi:filesystem/types@0.2.0#descriptor-flags". // // Descriptor flags. // // Note: This was called `fdflags` in earlier versions of WASI. // // flags descriptor-flags { // read, // write, // file-integrity-sync, // data-integrity-sync, // requested-write-sync, // mutate-directory, // } type DescriptorFlags uint8 const ( // Read mode: Data can be read. DescriptorFlagsRead DescriptorFlags = 1 << iota // Write mode: Data can be written to. DescriptorFlagsWrite // Request that writes be performed according to synchronized I/O file // integrity completion. The data stored in the file and the file's // metadata are synchronized. This is similar to `O_SYNC` in POSIX. // // The precise semantics of this operation have not yet been defined for // WASI. At this time, it should be interpreted as a request, and not a // requirement. DescriptorFlagsFileIntegritySync // Request that writes be performed according to synchronized I/O data // integrity completion. Only the data stored in the file is // synchronized. This is similar to `O_DSYNC` in POSIX. // // The precise semantics of this operation have not yet been defined for // WASI. At this time, it should be interpreted as a request, and not a // requirement. DescriptorFlagsDataIntegritySync // Requests that reads be performed at the same level of integrety // requested for writes. This is similar to `O_RSYNC` in POSIX. // // The precise semantics of this operation have not yet been defined for // WASI. At this time, it should be interpreted as a request, and not a // requirement. DescriptorFlagsRequestedWriteSync // Mutating directories mode: Directory contents may be mutated. // // When this flag is unset on a descriptor, operations using the // descriptor which would create, rename, delete, modify the data or // metadata of filesystem objects, or obtain another handle which // would permit any of those, shall fail with `error-code::read-only` if // they would otherwise succeed. // // This may only be set on directories. DescriptorFlagsMutateDirectory ) // PathFlags represents the flags "wasi:filesystem/types@0.2.0#path-flags". // // Flags determining the method of how paths are resolved. // // flags path-flags { // symlink-follow, // } type PathFlags uint8 const ( // As long as the resolved path corresponds to a symbolic link, it is // expanded. PathFlagsSymlinkFollow PathFlags = 1 << iota ) // OpenFlags represents the flags "wasi:filesystem/types@0.2.0#open-flags". // // Open flags used by `open-at`. // // flags open-flags { // create, // directory, // exclusive, // truncate, // } type OpenFlags uint8 const ( // Create file if it does not exist, similar to `O_CREAT` in POSIX. OpenFlagsCreate OpenFlags = 1 << iota // Fail if not a directory, similar to `O_DIRECTORY` in POSIX. OpenFlagsDirectory // Fail if file already exists, similar to `O_EXCL` in POSIX. OpenFlagsExclusive // Truncate file to size 0, similar to `O_TRUNC` in POSIX. OpenFlagsTruncate ) // LinkCount represents the u64 "wasi:filesystem/types@0.2.0#link-count". // // Number of hard links to an inode. // // type link-count = u64 type LinkCount uint64 // DescriptorStat represents the record "wasi:filesystem/types@0.2.0#descriptor-stat". // // File attributes. // // Note: This was called `filestat` in earlier versions of WASI. // // record descriptor-stat { // %type: descriptor-type, // link-count: link-count, // size: filesize, // data-access-timestamp: option, // data-modification-timestamp: option, // status-change-timestamp: option, // } type DescriptorStat struct { _ cm.HostLayout `json:"-"` // File type. Type DescriptorType `json:"type"` // Number of hard links to the file. LinkCount LinkCount `json:"link-count"` // For regular files, the file size in bytes. For symbolic links, the // length in bytes of the pathname contained in the symbolic link. Size FileSize `json:"size"` // Last data access timestamp. // // If the `option` is none, the platform doesn't maintain an access // timestamp for this file. DataAccessTimestamp cm.Option[DateTime] `json:"data-access-timestamp"` // Last data modification timestamp. // // If the `option` is none, the platform doesn't maintain a // modification timestamp for this file. DataModificationTimestamp cm.Option[DateTime] `json:"data-modification-timestamp"` // Last file status-change timestamp. // // If the `option` is none, the platform doesn't maintain a // status-change timestamp for this file. StatusChangeTimestamp cm.Option[DateTime] `json:"status-change-timestamp"` } // NewTimestamp represents the variant "wasi:filesystem/types@0.2.0#new-timestamp". // // When setting a timestamp, this gives the value to set it to. // // variant new-timestamp { // no-change, // now, // timestamp(datetime), // } type NewTimestamp cm.Variant[uint8, DateTime, DateTime] // NewTimestampNoChange returns a [NewTimestamp] of case "no-change". // // Leave the timestamp set to its previous value. func NewTimestampNoChange() NewTimestamp { var data struct{} return cm.New[NewTimestamp](0, data) } // NoChange returns true if [NewTimestamp] represents the variant case "no-change". func (self *NewTimestamp) NoChange() bool { return self.Tag() == 0 } // NewTimestampNow returns a [NewTimestamp] of case "now". // // Set the timestamp to the current time of the system clock associated // with the filesystem. func NewTimestampNow() NewTimestamp { var data struct{} return cm.New[NewTimestamp](1, data) } // Now returns true if [NewTimestamp] represents the variant case "now". func (self *NewTimestamp) Now() bool { return self.Tag() == 1 } // NewTimestampTimestamp returns a [NewTimestamp] of case "timestamp". // // Set the timestamp to the given value. func NewTimestampTimestamp(data DateTime) NewTimestamp { return cm.New[NewTimestamp](2, data) } // Timestamp returns a non-nil *[DateTime] if [NewTimestamp] represents the variant case "timestamp". func (self *NewTimestamp) Timestamp() *DateTime { return cm.Case[DateTime](self, 2) } var _NewTimestampStrings = [3]string{ "no-change", "now", "timestamp", } // String implements [fmt.Stringer], returning the variant case name of v. func (v NewTimestamp) String() string { return _NewTimestampStrings[v.Tag()] } // DirectoryEntry represents the record "wasi:filesystem/types@0.2.0#directory-entry". // // A directory entry. // // record directory-entry { // %type: descriptor-type, // name: string, // } type DirectoryEntry struct { _ cm.HostLayout `json:"-"` // The type of the file referred to by this directory entry. Type DescriptorType `json:"type"` // The name of the object. Name string `json:"name"` } // ErrorCode represents the enum "wasi:filesystem/types@0.2.0#error-code". // // Error codes returned by functions, similar to `errno` in POSIX. // Not all of these error codes are returned by the functions provided by this // API; some are used in higher-level library layers, and others are provided // merely for alignment with POSIX. // // enum error-code { // access, // would-block, // already, // bad-descriptor, // busy, // deadlock, // quota, // exist, // file-too-large, // illegal-byte-sequence, // in-progress, // interrupted, // invalid, // io, // is-directory, // loop, // too-many-links, // message-size, // name-too-long, // no-device, // no-entry, // no-lock, // insufficient-memory, // insufficient-space, // not-directory, // not-empty, // not-recoverable, // unsupported, // no-tty, // no-such-device, // overflow, // not-permitted, // pipe, // read-only, // invalid-seek, // text-file-busy, // cross-device // } type ErrorCode uint8 const ( // Permission denied, similar to `EACCES` in POSIX. ErrorCodeAccess ErrorCode = iota // Resource unavailable, or operation would block, similar to `EAGAIN` and `EWOULDBLOCK` // in POSIX. ErrorCodeWouldBlock // Connection already in progress, similar to `EALREADY` in POSIX. ErrorCodeAlready // Bad descriptor, similar to `EBADF` in POSIX. ErrorCodeBadDescriptor // Device or resource busy, similar to `EBUSY` in POSIX. ErrorCodeBusy // Resource deadlock would occur, similar to `EDEADLK` in POSIX. ErrorCodeDeadlock // Storage quota exceeded, similar to `EDQUOT` in POSIX. ErrorCodeQuota // File exists, similar to `EEXIST` in POSIX. ErrorCodeExist // File too large, similar to `EFBIG` in POSIX. ErrorCodeFileTooLarge // Illegal byte sequence, similar to `EILSEQ` in POSIX. ErrorCodeIllegalByteSequence // Operation in progress, similar to `EINPROGRESS` in POSIX. ErrorCodeInProgress // Interrupted function, similar to `EINTR` in POSIX. ErrorCodeInterrupted // Invalid argument, similar to `EINVAL` in POSIX. ErrorCodeInvalid // I/O error, similar to `EIO` in POSIX. ErrorCodeIO // Is a directory, similar to `EISDIR` in POSIX. ErrorCodeIsDirectory // Too many levels of symbolic links, similar to `ELOOP` in POSIX. ErrorCodeLoop // Too many links, similar to `EMLINK` in POSIX. ErrorCodeTooManyLinks // Message too large, similar to `EMSGSIZE` in POSIX. ErrorCodeMessageSize // Filename too long, similar to `ENAMETOOLONG` in POSIX. ErrorCodeNameTooLong // No such device, similar to `ENODEV` in POSIX. ErrorCodeNoDevice // No such file or directory, similar to `ENOENT` in POSIX. ErrorCodeNoEntry // No locks available, similar to `ENOLCK` in POSIX. ErrorCodeNoLock // Not enough space, similar to `ENOMEM` in POSIX. ErrorCodeInsufficientMemory // No space left on device, similar to `ENOSPC` in POSIX. ErrorCodeInsufficientSpace // Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX. ErrorCodeNotDirectory // Directory not empty, similar to `ENOTEMPTY` in POSIX. ErrorCodeNotEmpty // State not recoverable, similar to `ENOTRECOVERABLE` in POSIX. ErrorCodeNotRecoverable // Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX. ErrorCodeUnsupported // Inappropriate I/O control operation, similar to `ENOTTY` in POSIX. ErrorCodeNoTTY // No such device or address, similar to `ENXIO` in POSIX. ErrorCodeNoSuchDevice // Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX. ErrorCodeOverflow // Operation not permitted, similar to `EPERM` in POSIX. ErrorCodeNotPermitted // Broken pipe, similar to `EPIPE` in POSIX. ErrorCodePipe // Read-only file system, similar to `EROFS` in POSIX. ErrorCodeReadOnly // Invalid seek, similar to `ESPIPE` in POSIX. ErrorCodeInvalidSeek // Text file busy, similar to `ETXTBSY` in POSIX. ErrorCodeTextFileBusy // Cross-device link, similar to `EXDEV` in POSIX. ErrorCodeCrossDevice ) var _ErrorCodeStrings = [37]string{ "access", "would-block", "already", "bad-descriptor", "busy", "deadlock", "quota", "exist", "file-too-large", "illegal-byte-sequence", "in-progress", "interrupted", "invalid", "io", "is-directory", "loop", "too-many-links", "message-size", "name-too-long", "no-device", "no-entry", "no-lock", "insufficient-memory", "insufficient-space", "not-directory", "not-empty", "not-recoverable", "unsupported", "no-tty", "no-such-device", "overflow", "not-permitted", "pipe", "read-only", "invalid-seek", "text-file-busy", "cross-device", } // String implements [fmt.Stringer], returning the enum case name of e. func (e ErrorCode) String() string { return _ErrorCodeStrings[e] } // MarshalText implements [encoding.TextMarshaler]. func (e ErrorCode) MarshalText() ([]byte, error) { return []byte(e.String()), nil } // UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum // case. Returns an error if the supplied text is not one of the enum cases. func (e *ErrorCode) UnmarshalText(text []byte) error { return _ErrorCodeUnmarshalCase(e, text) } var _ErrorCodeUnmarshalCase = cm.CaseUnmarshaler[ErrorCode](_ErrorCodeStrings[:]) // Advice represents the enum "wasi:filesystem/types@0.2.0#advice". // // File or memory access pattern advisory information. // // enum advice { // normal, // sequential, // random, // will-need, // dont-need, // no-reuse // } type Advice uint8 const ( // The application has no advice to give on its behavior with respect // to the specified data. AdviceNormal Advice = iota // The application expects to access the specified data sequentially // from lower offsets to higher offsets. AdviceSequential // The application expects to access the specified data in a random // order. AdviceRandom // The application expects to access the specified data in the near // future. AdviceWillNeed // The application expects that it will not access the specified data // in the near future. AdviceDontNeed // The application expects to access the specified data once and then // not reuse it thereafter. AdviceNoReuse ) var _AdviceStrings = [6]string{ "normal", "sequential", "random", "will-need", "dont-need", "no-reuse", } // String implements [fmt.Stringer], returning the enum case name of e. func (e Advice) String() string { return _AdviceStrings[e] } // MarshalText implements [encoding.TextMarshaler]. func (e Advice) MarshalText() ([]byte, error) { return []byte(e.String()), nil } // UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum // case. Returns an error if the supplied text is not one of the enum cases. func (e *Advice) UnmarshalText(text []byte) error { return _AdviceUnmarshalCase(e, text) } var _AdviceUnmarshalCase = cm.CaseUnmarshaler[Advice](_AdviceStrings[:]) // MetadataHashValue represents the record "wasi:filesystem/types@0.2.0#metadata-hash-value". // // A 128-bit hash value, split into parts because wasm doesn't have a // 128-bit integer type. // // record metadata-hash-value { // lower: u64, // upper: u64, // } type MetadataHashValue struct { _ cm.HostLayout `json:"-"` // 64 bits of a 128-bit hash value. Lower uint64 `json:"lower"` // Another 64 bits of a 128-bit hash value. Upper uint64 `json:"upper"` } // Descriptor represents the imported resource "wasi:filesystem/types@0.2.0#descriptor". // // A descriptor is a reference to a filesystem object, which may be a file, // directory, named pipe, special file, or other object on which filesystem // calls may be made. // // resource descriptor type Descriptor cm.Resource // ResourceDrop represents the imported resource-drop for resource "descriptor". // // Drops a resource handle. // //go:nosplit func (self Descriptor) ResourceDrop() { self0 := cm.Reinterpret[uint32](self) wasmimport_DescriptorResourceDrop((uint32)(self0)) return } // Advise represents the imported method "advise". // // Provide file advisory information on a descriptor. // // This is similar to `posix_fadvise` in POSIX. // // advise: func(offset: filesize, length: filesize, advice: advice) -> result<_, error-code> // //go:nosplit func (self Descriptor) Advise(offset FileSize, length FileSize, advice Advice) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) offset0 := (uint64)(offset) length0 := (uint64)(length) advice0 := (uint32)(advice) wasmimport_DescriptorAdvise((uint32)(self0), (uint64)(offset0), (uint64)(length0), (uint32)(advice0), &result) return } // AppendViaStream represents the imported method "append-via-stream". // // Return a stream for appending to a file, if available. // // May fail with an error-code describing why the file cannot be appended. // // Note: This allows using `write-stream`, which is similar to `write` with // `O_APPEND` in in POSIX. // // append-via-stream: func() -> result // //go:nosplit func (self Descriptor) AppendViaStream() (result cm.Result[OutputStream, OutputStream, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_DescriptorAppendViaStream((uint32)(self0), &result) return } // CreateDirectoryAt represents the imported method "create-directory-at". // // Create a directory. // // Note: This is similar to `mkdirat` in POSIX. // // create-directory-at: func(path: string) -> result<_, error-code> // //go:nosplit func (self Descriptor) CreateDirectoryAt(path string) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) path0, path1 := cm.LowerString(path) wasmimport_DescriptorCreateDirectoryAt((uint32)(self0), (*uint8)(path0), (uint32)(path1), &result) return } // GetFlags represents the imported method "get-flags". // // Get flags associated with a descriptor. // // Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. // // Note: This returns the value that was the `fs_flags` value returned // from `fdstat_get` in earlier versions of WASI. // // get-flags: func() -> result // //go:nosplit func (self Descriptor) GetFlags() (result cm.Result[DescriptorFlags, DescriptorFlags, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_DescriptorGetFlags((uint32)(self0), &result) return } // GetType represents the imported method "get-type". // // Get the dynamic type of a descriptor. // // Note: This returns the same value as the `type` field of the `fd-stat` // returned by `stat`, `stat-at` and similar. // // Note: This returns similar flags to the `st_mode & S_IFMT` value provided // by `fstat` in POSIX. // // Note: This returns the value that was the `fs_filetype` value returned // from `fdstat_get` in earlier versions of WASI. // // get-type: func() -> result // //go:nosplit func (self Descriptor) GetType() (result cm.Result[DescriptorType, DescriptorType, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_DescriptorGetType((uint32)(self0), &result) return } // IsSameObject represents the imported method "is-same-object". // // Test whether two descriptors refer to the same filesystem object. // // In POSIX, this corresponds to testing whether the two descriptors have the // same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers. // wasi-filesystem does not expose device and inode numbers, so this function // may be used instead. // // is-same-object: func(other: borrow) -> bool // //go:nosplit func (self Descriptor) IsSameObject(other Descriptor) (result bool) { self0 := cm.Reinterpret[uint32](self) other0 := cm.Reinterpret[uint32](other) result0 := wasmimport_DescriptorIsSameObject((uint32)(self0), (uint32)(other0)) result = (bool)(cm.U32ToBool((uint32)(result0))) return } // LinkAt represents the imported method "link-at". // // Create a hard link. // // Note: This is similar to `linkat` in POSIX. // // link-at: func(old-path-flags: path-flags, old-path: string, new-descriptor: borrow, // new-path: string) -> result<_, error-code> // //go:nosplit func (self Descriptor) LinkAt(oldPathFlags PathFlags, oldPath string, newDescriptor Descriptor, newPath string) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) oldPathFlags0 := (uint32)(oldPathFlags) oldPath0, oldPath1 := cm.LowerString(oldPath) newDescriptor0 := cm.Reinterpret[uint32](newDescriptor) newPath0, newPath1 := cm.LowerString(newPath) wasmimport_DescriptorLinkAt((uint32)(self0), (uint32)(oldPathFlags0), (*uint8)(oldPath0), (uint32)(oldPath1), (uint32)(newDescriptor0), (*uint8)(newPath0), (uint32)(newPath1), &result) return } // MetadataHash represents the imported method "metadata-hash". // // Return a hash of the metadata associated with a filesystem object referred // to by a descriptor. // // This returns a hash of the last-modification timestamp and file size, and // may also include the inode number, device number, birth timestamp, and // other metadata fields that may change when the file is modified or // replaced. It may also include a secret value chosen by the // implementation and not otherwise exposed. // // Implementations are encourated to provide the following properties: // // - If the file is not modified or replaced, the computed hash value should // usually not change. // - If the object is modified or replaced, the computed hash value should // usually change. // - The inputs to the hash should not be easily computable from the // computed hash. // // However, none of these is required. // // metadata-hash: func() -> result // //go:nosplit func (self Descriptor) MetadataHash() (result cm.Result[MetadataHashValueShape, MetadataHashValue, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_DescriptorMetadataHash((uint32)(self0), &result) return } // MetadataHashAt represents the imported method "metadata-hash-at". // // Return a hash of the metadata associated with a filesystem object referred // to by a directory descriptor and a relative path. // // This performs the same hash computation as `metadata-hash`. // // metadata-hash-at: func(path-flags: path-flags, path: string) -> result // //go:nosplit func (self Descriptor) MetadataHashAt(pathFlags PathFlags, path string) (result cm.Result[MetadataHashValueShape, MetadataHashValue, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) pathFlags0 := (uint32)(pathFlags) path0, path1 := cm.LowerString(path) wasmimport_DescriptorMetadataHashAt((uint32)(self0), (uint32)(pathFlags0), (*uint8)(path0), (uint32)(path1), &result) return } // OpenAt represents the imported method "open-at". // // Open a file or directory. // // The returned descriptor is not guaranteed to be the lowest-numbered // descriptor not currently open/ it is randomized to prevent applications // from depending on making assumptions about indexes, since this is // error-prone in multi-threaded contexts. The returned descriptor is // guaranteed to be less than 2**31. // // If `flags` contains `descriptor-flags::mutate-directory`, and the base // descriptor doesn't have `descriptor-flags::mutate-directory` set, // `open-at` fails with `error-code::read-only`. // // If `flags` contains `write` or `mutate-directory`, or `open-flags` // contains `truncate` or `create`, and the base descriptor doesn't have // `descriptor-flags::mutate-directory` set, `open-at` fails with // `error-code::read-only`. // // Note: This is similar to `openat` in POSIX. // // open-at: func(path-flags: path-flags, path: string, open-flags: open-flags, %flags: // descriptor-flags) -> result // //go:nosplit func (self Descriptor) OpenAt(pathFlags PathFlags, path string, openFlags OpenFlags, flags DescriptorFlags) (result cm.Result[Descriptor, Descriptor, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) pathFlags0 := (uint32)(pathFlags) path0, path1 := cm.LowerString(path) openFlags0 := (uint32)(openFlags) flags0 := (uint32)(flags) wasmimport_DescriptorOpenAt((uint32)(self0), (uint32)(pathFlags0), (*uint8)(path0), (uint32)(path1), (uint32)(openFlags0), (uint32)(flags0), &result) return } // Read represents the imported method "read". // // Read from a descriptor, without using and updating the descriptor's offset. // // This function returns a list of bytes containing the data that was // read, along with a bool which, when true, indicates that the end of the // file was reached. The returned list will contain up to `length` bytes; it // may return fewer than requested, if the end of the file is reached or // if the I/O operation is interrupted. // // In the future, this may change to return a `stream`. // // Note: This is similar to `pread` in POSIX. // // read: func(length: filesize, offset: filesize) -> result, bool>, // error-code> // //go:nosplit func (self Descriptor) Read(length FileSize, offset FileSize) (result cm.Result[TupleListU8BoolShape, cm.Tuple[cm.List[uint8], bool], ErrorCode]) { self0 := cm.Reinterpret[uint32](self) length0 := (uint64)(length) offset0 := (uint64)(offset) wasmimport_DescriptorRead((uint32)(self0), (uint64)(length0), (uint64)(offset0), &result) return } // ReadDirectory represents the imported method "read-directory". // // Read directory entries from a directory. // // On filesystems where directories contain entries referring to themselves // and their parents, often named `.` and `..` respectively, these entries // are omitted. // // This always returns a new stream which starts at the beginning of the // directory. Multiple streams may be active on the same directory, and they // do not interfere with each other. // // read-directory: func() -> result // //go:nosplit func (self Descriptor) ReadDirectory() (result cm.Result[DirectoryEntryStream, DirectoryEntryStream, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_DescriptorReadDirectory((uint32)(self0), &result) return } // ReadViaStream represents the imported method "read-via-stream". // // Return a stream for reading from a file, if available. // // May fail with an error-code describing why the file cannot be read. // // Multiple read, write, and append streams may be active on the same open // file and they do not interfere with each other. // // Note: This allows using `read-stream`, which is similar to `read` in POSIX. // // read-via-stream: func(offset: filesize) -> result // //go:nosplit func (self Descriptor) ReadViaStream(offset FileSize) (result cm.Result[InputStream, InputStream, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) offset0 := (uint64)(offset) wasmimport_DescriptorReadViaStream((uint32)(self0), (uint64)(offset0), &result) return } // ReadLinkAt represents the imported method "readlink-at". // // Read the contents of a symbolic link. // // If the contents contain an absolute or rooted path in the underlying // filesystem, this function fails with `error-code::not-permitted`. // // Note: This is similar to `readlinkat` in POSIX. // // readlink-at: func(path: string) -> result // //go:nosplit func (self Descriptor) ReadLinkAt(path string) (result cm.Result[string, string, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) path0, path1 := cm.LowerString(path) wasmimport_DescriptorReadLinkAt((uint32)(self0), (*uint8)(path0), (uint32)(path1), &result) return } // RemoveDirectoryAt represents the imported method "remove-directory-at". // // Remove a directory. // // Return `error-code::not-empty` if the directory is not empty. // // Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. // // remove-directory-at: func(path: string) -> result<_, error-code> // //go:nosplit func (self Descriptor) RemoveDirectoryAt(path string) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) path0, path1 := cm.LowerString(path) wasmimport_DescriptorRemoveDirectoryAt((uint32)(self0), (*uint8)(path0), (uint32)(path1), &result) return } // RenameAt represents the imported method "rename-at". // // Rename a filesystem object. // // Note: This is similar to `renameat` in POSIX. // // rename-at: func(old-path: string, new-descriptor: borrow, new-path: // string) -> result<_, error-code> // //go:nosplit func (self Descriptor) RenameAt(oldPath string, newDescriptor Descriptor, newPath string) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) oldPath0, oldPath1 := cm.LowerString(oldPath) newDescriptor0 := cm.Reinterpret[uint32](newDescriptor) newPath0, newPath1 := cm.LowerString(newPath) wasmimport_DescriptorRenameAt((uint32)(self0), (*uint8)(oldPath0), (uint32)(oldPath1), (uint32)(newDescriptor0), (*uint8)(newPath0), (uint32)(newPath1), &result) return } // SetSize represents the imported method "set-size". // // Adjust the size of an open file. If this increases the file's size, the // extra bytes are filled with zeros. // // Note: This was called `fd_filestat_set_size` in earlier versions of WASI. // // set-size: func(size: filesize) -> result<_, error-code> // //go:nosplit func (self Descriptor) SetSize(size FileSize) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) size0 := (uint64)(size) wasmimport_DescriptorSetSize((uint32)(self0), (uint64)(size0), &result) return } // SetTimes represents the imported method "set-times". // // Adjust the timestamps of an open file or directory. // // Note: This is similar to `futimens` in POSIX. // // Note: This was called `fd_filestat_set_times` in earlier versions of WASI. // // set-times: func(data-access-timestamp: new-timestamp, data-modification-timestamp: // new-timestamp) -> result<_, error-code> // //go:nosplit func (self Descriptor) SetTimes(dataAccessTimestamp NewTimestamp, dataModificationTimestamp NewTimestamp) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) dataAccessTimestamp0, dataAccessTimestamp1, dataAccessTimestamp2 := lower_NewTimestamp(dataAccessTimestamp) dataModificationTimestamp0, dataModificationTimestamp1, dataModificationTimestamp2 := lower_NewTimestamp(dataModificationTimestamp) wasmimport_DescriptorSetTimes((uint32)(self0), (uint32)(dataAccessTimestamp0), (uint64)(dataAccessTimestamp1), (uint32)(dataAccessTimestamp2), (uint32)(dataModificationTimestamp0), (uint64)(dataModificationTimestamp1), (uint32)(dataModificationTimestamp2), &result) return } // SetTimesAt represents the imported method "set-times-at". // // Adjust the timestamps of a file or directory. // // Note: This is similar to `utimensat` in POSIX. // // Note: This was called `path_filestat_set_times` in earlier versions of // WASI. // // set-times-at: func(path-flags: path-flags, path: string, data-access-timestamp: // new-timestamp, data-modification-timestamp: new-timestamp) -> result<_, error-code> // //go:nosplit func (self Descriptor) SetTimesAt(pathFlags PathFlags, path string, dataAccessTimestamp NewTimestamp, dataModificationTimestamp NewTimestamp) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) pathFlags0 := (uint32)(pathFlags) path0, path1 := cm.LowerString(path) dataAccessTimestamp0, dataAccessTimestamp1, dataAccessTimestamp2 := lower_NewTimestamp(dataAccessTimestamp) dataModificationTimestamp0, dataModificationTimestamp1, dataModificationTimestamp2 := lower_NewTimestamp(dataModificationTimestamp) wasmimport_DescriptorSetTimesAt((uint32)(self0), (uint32)(pathFlags0), (*uint8)(path0), (uint32)(path1), (uint32)(dataAccessTimestamp0), (uint64)(dataAccessTimestamp1), (uint32)(dataAccessTimestamp2), (uint32)(dataModificationTimestamp0), (uint64)(dataModificationTimestamp1), (uint32)(dataModificationTimestamp2), &result) return } // Stat represents the imported method "stat". // // Return the attributes of an open file or directory. // // Note: This is similar to `fstat` in POSIX, except that it does not return // device and inode information. For testing whether two descriptors refer to // the same underlying filesystem object, use `is-same-object`. To obtain // additional data that can be used do determine whether a file has been // modified, use `metadata-hash`. // // Note: This was called `fd_filestat_get` in earlier versions of WASI. // // stat: func() -> result // //go:nosplit func (self Descriptor) Stat() (result cm.Result[DescriptorStatShape, DescriptorStat, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_DescriptorStat((uint32)(self0), &result) return } // StatAt represents the imported method "stat-at". // // Return the attributes of a file or directory. // // Note: This is similar to `fstatat` in POSIX, except that it does not // return device and inode information. See the `stat` description for a // discussion of alternatives. // // Note: This was called `path_filestat_get` in earlier versions of WASI. // // stat-at: func(path-flags: path-flags, path: string) -> result // //go:nosplit func (self Descriptor) StatAt(pathFlags PathFlags, path string) (result cm.Result[DescriptorStatShape, DescriptorStat, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) pathFlags0 := (uint32)(pathFlags) path0, path1 := cm.LowerString(path) wasmimport_DescriptorStatAt((uint32)(self0), (uint32)(pathFlags0), (*uint8)(path0), (uint32)(path1), &result) return } // SymlinkAt represents the imported method "symlink-at". // // Create a symbolic link (also known as a "symlink"). // // If `old-path` starts with `/`, the function fails with // `error-code::not-permitted`. // // Note: This is similar to `symlinkat` in POSIX. // // symlink-at: func(old-path: string, new-path: string) -> result<_, error-code> // //go:nosplit func (self Descriptor) SymlinkAt(oldPath string, newPath string) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) oldPath0, oldPath1 := cm.LowerString(oldPath) newPath0, newPath1 := cm.LowerString(newPath) wasmimport_DescriptorSymlinkAt((uint32)(self0), (*uint8)(oldPath0), (uint32)(oldPath1), (*uint8)(newPath0), (uint32)(newPath1), &result) return } // Sync represents the imported method "sync". // // Synchronize the data and metadata of a file to disk. // // This function succeeds with no effect if the file descriptor is not // opened for writing. // // Note: This is similar to `fsync` in POSIX. // // sync: func() -> result<_, error-code> // //go:nosplit func (self Descriptor) Sync() (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_DescriptorSync((uint32)(self0), &result) return } // SyncData represents the imported method "sync-data". // // Synchronize the data of a file to disk. // // This function succeeds with no effect if the file descriptor is not // opened for writing. // // Note: This is similar to `fdatasync` in POSIX. // // sync-data: func() -> result<_, error-code> // //go:nosplit func (self Descriptor) SyncData() (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_DescriptorSyncData((uint32)(self0), &result) return } // UnlinkFileAt represents the imported method "unlink-file-at". // // Unlink a filesystem object that is not a directory. // // Return `error-code::is-directory` if the path refers to a directory. // Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. // // unlink-file-at: func(path: string) -> result<_, error-code> // //go:nosplit func (self Descriptor) UnlinkFileAt(path string) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) path0, path1 := cm.LowerString(path) wasmimport_DescriptorUnlinkFileAt((uint32)(self0), (*uint8)(path0), (uint32)(path1), &result) return } // Write represents the imported method "write". // // Write to a descriptor, without using and updating the descriptor's offset. // // It is valid to write past the end of a file; the file is extended to the // extent of the write, with bytes between the previous end and the start of // the write set to zero. // // In the future, this may change to take a `stream`. // // Note: This is similar to `pwrite` in POSIX. // // write: func(buffer: list, offset: filesize) -> result // //go:nosplit func (self Descriptor) Write(buffer cm.List[uint8], offset FileSize) (result cm.Result[uint64, FileSize, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) buffer0, buffer1 := cm.LowerList(buffer) offset0 := (uint64)(offset) wasmimport_DescriptorWrite((uint32)(self0), (*uint8)(buffer0), (uint32)(buffer1), (uint64)(offset0), &result) return } // WriteViaStream represents the imported method "write-via-stream". // // Return a stream for writing to a file, if available. // // May fail with an error-code describing why the file cannot be written. // // Note: This allows using `write-stream`, which is similar to `write` in // POSIX. // // write-via-stream: func(offset: filesize) -> result // //go:nosplit func (self Descriptor) WriteViaStream(offset FileSize) (result cm.Result[OutputStream, OutputStream, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) offset0 := (uint64)(offset) wasmimport_DescriptorWriteViaStream((uint32)(self0), (uint64)(offset0), &result) return } // DirectoryEntryStream represents the imported resource "wasi:filesystem/types@0.2.0#directory-entry-stream". // // A stream of directory entries. // // resource directory-entry-stream type DirectoryEntryStream cm.Resource // ResourceDrop represents the imported resource-drop for resource "directory-entry-stream". // // Drops a resource handle. // //go:nosplit func (self DirectoryEntryStream) ResourceDrop() { self0 := cm.Reinterpret[uint32](self) wasmimport_DirectoryEntryStreamResourceDrop((uint32)(self0)) return } // ReadDirectoryEntry represents the imported method "read-directory-entry". // // Read a single directory entry from a `directory-entry-stream`. // // read-directory-entry: func() -> result, error-code> // //go:nosplit func (self DirectoryEntryStream) ReadDirectoryEntry() (result cm.Result[OptionDirectoryEntryShape, cm.Option[DirectoryEntry], ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_DirectoryEntryStreamReadDirectoryEntry((uint32)(self0), &result) return } // FilesystemErrorCode represents the imported function "filesystem-error-code". // // Attempts to extract a filesystem-related `error-code` from the stream // `error` provided. // // Stream operations which return `stream-error::last-operation-failed` // have a payload with more information about the operation that failed. // This payload can be passed through to this function to see if there's // filesystem-related information about the error to return. // // Note that this function is fallible because not all stream-related // errors are filesystem-related errors. // // filesystem-error-code: func(err: borrow) -> option // //go:nosplit func FilesystemErrorCode(err Error) (result cm.Option[ErrorCode]) { err0 := cm.Reinterpret[uint32](err) wasmimport_FilesystemErrorCode((uint32)(err0), &result) return } ================================================ FILE: src/internal/wasi/io/v0.2.0/error/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/io/v0.2.0/error/error.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package ioerror // This file contains wasmimport and wasmexport declarations for "wasi:io@0.2.0". //go:wasmimport wasi:io/error@0.2.0 [resource-drop]error //go:noescape func wasmimport_ErrorResourceDrop(self0 uint32) //go:wasmimport wasi:io/error@0.2.0 [method]error.to-debug-string //go:noescape func wasmimport_ErrorToDebugString(self0 uint32, result *string) ================================================ FILE: src/internal/wasi/io/v0.2.0/error/error.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package ioerror represents the imported interface "wasi:io/error@0.2.0". package ioerror import ( "internal/cm" ) // Error represents the imported resource "wasi:io/error@0.2.0#error". // // A resource which represents some error information. // // The only method provided by this resource is `to-debug-string`, // which provides some human-readable information about the error. // // In the `wasi:io` package, this resource is returned through the // `wasi:io/streams/stream-error` type. // // To provide more specific error information, other interfaces may // provide functions to further "downcast" this error into more specific // error information. For example, `error`s returned in streams derived // from filesystem types to be described using the filesystem's own // error-code type, using the function // `wasi:filesystem/types/filesystem-error-code`, which takes a parameter // `borrow` and returns // `option`. // // The set of functions which can "downcast" an `error` into a more // concrete type is open. // // resource error type Error cm.Resource // ResourceDrop represents the imported resource-drop for resource "error". // // Drops a resource handle. // //go:nosplit func (self Error) ResourceDrop() { self0 := cm.Reinterpret[uint32](self) wasmimport_ErrorResourceDrop((uint32)(self0)) return } // ToDebugString represents the imported method "to-debug-string". // // Returns a string that is suitable to assist humans in debugging // this error. // // WARNING: The returned string should not be consumed mechanically! // It may change across platforms, hosts, or other implementation // details. Parsing this string is a major platform-compatibility // hazard. // // to-debug-string: func() -> string // //go:nosplit func (self Error) ToDebugString() (result string) { self0 := cm.Reinterpret[uint32](self) wasmimport_ErrorToDebugString((uint32)(self0), &result) return } ================================================ FILE: src/internal/wasi/io/v0.2.0/poll/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/io/v0.2.0/poll/poll.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package poll import ( "internal/cm" ) // This file contains wasmimport and wasmexport declarations for "wasi:io@0.2.0". //go:wasmimport wasi:io/poll@0.2.0 [resource-drop]pollable //go:noescape func wasmimport_PollableResourceDrop(self0 uint32) //go:wasmimport wasi:io/poll@0.2.0 [method]pollable.block //go:noescape func wasmimport_PollableBlock(self0 uint32) //go:wasmimport wasi:io/poll@0.2.0 [method]pollable.ready //go:noescape func wasmimport_PollableReady(self0 uint32) (result0 uint32) //go:wasmimport wasi:io/poll@0.2.0 poll //go:noescape func wasmimport_Poll(in0 *Pollable, in1 uint32, result *cm.List[uint32]) ================================================ FILE: src/internal/wasi/io/v0.2.0/poll/poll.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package poll represents the imported interface "wasi:io/poll@0.2.0". // // A poll API intended to let users wait for I/O events on multiple handles // at once. package poll import ( "internal/cm" ) // Pollable represents the imported resource "wasi:io/poll@0.2.0#pollable". // // `pollable` represents a single I/O event which may be ready, or not. // // resource pollable type Pollable cm.Resource // ResourceDrop represents the imported resource-drop for resource "pollable". // // Drops a resource handle. // //go:nosplit func (self Pollable) ResourceDrop() { self0 := cm.Reinterpret[uint32](self) wasmimport_PollableResourceDrop((uint32)(self0)) return } // Block represents the imported method "block". // // `block` returns immediately if the pollable is ready, and otherwise // blocks until ready. // // This function is equivalent to calling `poll.poll` on a list // containing only this pollable. // // block: func() // //go:nosplit func (self Pollable) Block() { self0 := cm.Reinterpret[uint32](self) wasmimport_PollableBlock((uint32)(self0)) return } // Ready represents the imported method "ready". // // Return the readiness of a pollable. This function never blocks. // // Returns `true` when the pollable is ready, and `false` otherwise. // // ready: func() -> bool // //go:nosplit func (self Pollable) Ready() (result bool) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_PollableReady((uint32)(self0)) result = (bool)(cm.U32ToBool((uint32)(result0))) return } // Poll represents the imported function "poll". // // Poll for completion on a set of pollables. // // This function takes a list of pollables, which identify I/O sources of // interest, and waits until one or more of the events is ready for I/O. // // The result `list` contains one or more indices of handles in the // argument list that is ready for I/O. // // If the list contains more elements than can be indexed with a `u32` // value, this function traps. // // A timeout can be implemented by adding a pollable from the // wasi-clocks API to the list. // // This function does not return a `result`; polling in itself does not // do any I/O so it doesn't fail. If any of the I/O sources identified by // the pollables has an error, it is indicated by marking the source as // being reaedy for I/O. // // poll: func(in: list>) -> list // //go:nosplit func Poll(in cm.List[Pollable]) (result cm.List[uint32]) { in0, in1 := cm.LowerList(in) wasmimport_Poll((*Pollable)(in0), (uint32)(in1), &result) return } ================================================ FILE: src/internal/wasi/io/v0.2.0/streams/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/io/v0.2.0/streams/streams.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package streams import ( "internal/cm" ) // This file contains wasmimport and wasmexport declarations for "wasi:io@0.2.0". //go:wasmimport wasi:io/streams@0.2.0 [resource-drop]input-stream //go:noescape func wasmimport_InputStreamResourceDrop(self0 uint32) //go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.blocking-read //go:noescape func wasmimport_InputStreamBlockingRead(self0 uint32, len0 uint64, result *cm.Result[cm.List[uint8], cm.List[uint8], StreamError]) //go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.blocking-skip //go:noescape func wasmimport_InputStreamBlockingSkip(self0 uint32, len0 uint64, result *cm.Result[uint64, uint64, StreamError]) //go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.read //go:noescape func wasmimport_InputStreamRead(self0 uint32, len0 uint64, result *cm.Result[cm.List[uint8], cm.List[uint8], StreamError]) //go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.skip //go:noescape func wasmimport_InputStreamSkip(self0 uint32, len0 uint64, result *cm.Result[uint64, uint64, StreamError]) //go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.subscribe //go:noescape func wasmimport_InputStreamSubscribe(self0 uint32) (result0 uint32) //go:wasmimport wasi:io/streams@0.2.0 [resource-drop]output-stream //go:noescape func wasmimport_OutputStreamResourceDrop(self0 uint32) //go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-flush //go:noescape func wasmimport_OutputStreamBlockingFlush(self0 uint32, result *cm.Result[StreamError, struct{}, StreamError]) //go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-splice //go:noescape func wasmimport_OutputStreamBlockingSplice(self0 uint32, src0 uint32, len0 uint64, result *cm.Result[uint64, uint64, StreamError]) //go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-write-and-flush //go:noescape func wasmimport_OutputStreamBlockingWriteAndFlush(self0 uint32, contents0 *uint8, contents1 uint32, result *cm.Result[StreamError, struct{}, StreamError]) //go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-write-zeroes-and-flush //go:noescape func wasmimport_OutputStreamBlockingWriteZeroesAndFlush(self0 uint32, len0 uint64, result *cm.Result[StreamError, struct{}, StreamError]) //go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.check-write //go:noescape func wasmimport_OutputStreamCheckWrite(self0 uint32, result *cm.Result[uint64, uint64, StreamError]) //go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.flush //go:noescape func wasmimport_OutputStreamFlush(self0 uint32, result *cm.Result[StreamError, struct{}, StreamError]) //go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.splice //go:noescape func wasmimport_OutputStreamSplice(self0 uint32, src0 uint32, len0 uint64, result *cm.Result[uint64, uint64, StreamError]) //go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.subscribe //go:noescape func wasmimport_OutputStreamSubscribe(self0 uint32) (result0 uint32) //go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.write //go:noescape func wasmimport_OutputStreamWrite(self0 uint32, contents0 *uint8, contents1 uint32, result *cm.Result[StreamError, struct{}, StreamError]) //go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.write-zeroes //go:noescape func wasmimport_OutputStreamWriteZeroes(self0 uint32, len0 uint64, result *cm.Result[StreamError, struct{}, StreamError]) ================================================ FILE: src/internal/wasi/io/v0.2.0/streams/streams.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package streams represents the imported interface "wasi:io/streams@0.2.0". // // WASI I/O is an I/O abstraction API which is currently focused on providing // stream types. // // In the future, the component model is expected to add built-in stream types; // when it does, they are expected to subsume this API. package streams import ( "internal/cm" ioerror "internal/wasi/io/v0.2.0/error" "internal/wasi/io/v0.2.0/poll" ) // Error represents the imported type alias "wasi:io/streams@0.2.0#error". // // See [ioerror.Error] for more information. type Error = ioerror.Error // Pollable represents the imported type alias "wasi:io/streams@0.2.0#pollable". // // See [poll.Pollable] for more information. type Pollable = poll.Pollable // StreamError represents the imported variant "wasi:io/streams@0.2.0#stream-error". // // An error for input-stream and output-stream operations. // // variant stream-error { // last-operation-failed(error), // closed, // } type StreamError cm.Variant[uint8, Error, Error] // StreamErrorLastOperationFailed returns a [StreamError] of case "last-operation-failed". // // The last operation (a write or flush) failed before completion. // // More information is available in the `error` payload. func StreamErrorLastOperationFailed(data Error) StreamError { return cm.New[StreamError](0, data) } // LastOperationFailed returns a non-nil *[Error] if [StreamError] represents the variant case "last-operation-failed". func (self *StreamError) LastOperationFailed() *Error { return cm.Case[Error](self, 0) } // StreamErrorClosed returns a [StreamError] of case "closed". // // The stream is closed: no more input will be accepted by the // stream. A closed output-stream will return this error on all // future operations. func StreamErrorClosed() StreamError { var data struct{} return cm.New[StreamError](1, data) } // Closed returns true if [StreamError] represents the variant case "closed". func (self *StreamError) Closed() bool { return self.Tag() == 1 } var _StreamErrorStrings = [2]string{ "last-operation-failed", "closed", } // String implements [fmt.Stringer], returning the variant case name of v. func (v StreamError) String() string { return _StreamErrorStrings[v.Tag()] } // InputStream represents the imported resource "wasi:io/streams@0.2.0#input-stream". // // An input bytestream. // // `input-stream`s are *non-blocking* to the extent practical on underlying // platforms. I/O operations always return promptly; if fewer bytes are // promptly available than requested, they return the number of bytes promptly // available, which could even be zero. To wait for data to be available, // use the `subscribe` function to obtain a `pollable` which can be polled // for using `wasi:io/poll`. // // resource input-stream type InputStream cm.Resource // ResourceDrop represents the imported resource-drop for resource "input-stream". // // Drops a resource handle. // //go:nosplit func (self InputStream) ResourceDrop() { self0 := cm.Reinterpret[uint32](self) wasmimport_InputStreamResourceDrop((uint32)(self0)) return } // BlockingRead represents the imported method "blocking-read". // // Read bytes from a stream, after blocking until at least one byte can // be read. Except for blocking, behavior is identical to `read`. // // blocking-read: func(len: u64) -> result, stream-error> // //go:nosplit func (self InputStream) BlockingRead(len_ uint64) (result cm.Result[cm.List[uint8], cm.List[uint8], StreamError]) { self0 := cm.Reinterpret[uint32](self) len0 := (uint64)(len_) wasmimport_InputStreamBlockingRead((uint32)(self0), (uint64)(len0), &result) return } // BlockingSkip represents the imported method "blocking-skip". // // Skip bytes from a stream, after blocking until at least one byte // can be skipped. Except for blocking behavior, identical to `skip`. // // blocking-skip: func(len: u64) -> result // //go:nosplit func (self InputStream) BlockingSkip(len_ uint64) (result cm.Result[uint64, uint64, StreamError]) { self0 := cm.Reinterpret[uint32](self) len0 := (uint64)(len_) wasmimport_InputStreamBlockingSkip((uint32)(self0), (uint64)(len0), &result) return } // Read represents the imported method "read". // // Perform a non-blocking read from the stream. // // When the source of a `read` is binary data, the bytes from the source // are returned verbatim. When the source of a `read` is known to the // implementation to be text, bytes containing the UTF-8 encoding of the // text are returned. // // This function returns a list of bytes containing the read data, // when successful. The returned list will contain up to `len` bytes; // it may return fewer than requested, but not more. The list is // empty when no bytes are available for reading at this time. The // pollable given by `subscribe` will be ready when more bytes are // available. // // This function fails with a `stream-error` when the operation // encounters an error, giving `last-operation-failed`, or when the // stream is closed, giving `closed`. // // When the caller gives a `len` of 0, it represents a request to // read 0 bytes. If the stream is still open, this call should // succeed and return an empty list, or otherwise fail with `closed`. // // The `len` parameter is a `u64`, which could represent a list of u8 which // is not possible to allocate in wasm32, or not desirable to allocate as // as a return value by the callee. The callee may return a list of bytes // less than `len` in size while more bytes are available for reading. // // read: func(len: u64) -> result, stream-error> // //go:nosplit func (self InputStream) Read(len_ uint64) (result cm.Result[cm.List[uint8], cm.List[uint8], StreamError]) { self0 := cm.Reinterpret[uint32](self) len0 := (uint64)(len_) wasmimport_InputStreamRead((uint32)(self0), (uint64)(len0), &result) return } // Skip represents the imported method "skip". // // Skip bytes from a stream. Returns number of bytes skipped. // // Behaves identical to `read`, except instead of returning a list // of bytes, returns the number of bytes consumed from the stream. // // skip: func(len: u64) -> result // //go:nosplit func (self InputStream) Skip(len_ uint64) (result cm.Result[uint64, uint64, StreamError]) { self0 := cm.Reinterpret[uint32](self) len0 := (uint64)(len_) wasmimport_InputStreamSkip((uint32)(self0), (uint64)(len0), &result) return } // Subscribe represents the imported method "subscribe". // // Create a `pollable` which will resolve once either the specified stream // has bytes available to read or the other end of the stream has been // closed. // The created `pollable` is a child resource of the `input-stream`. // Implementations may trap if the `input-stream` is dropped before // all derived `pollable`s created with this function are dropped. // // subscribe: func() -> pollable // //go:nosplit func (self InputStream) Subscribe() (result Pollable) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_InputStreamSubscribe((uint32)(self0)) result = cm.Reinterpret[Pollable]((uint32)(result0)) return } // OutputStream represents the imported resource "wasi:io/streams@0.2.0#output-stream". // // An output bytestream. // // `output-stream`s are *non-blocking* to the extent practical on // underlying platforms. Except where specified otherwise, I/O operations also // always return promptly, after the number of bytes that can be written // promptly, which could even be zero. To wait for the stream to be ready to // accept data, the `subscribe` function to obtain a `pollable` which can be // polled for using `wasi:io/poll`. // // resource output-stream type OutputStream cm.Resource // ResourceDrop represents the imported resource-drop for resource "output-stream". // // Drops a resource handle. // //go:nosplit func (self OutputStream) ResourceDrop() { self0 := cm.Reinterpret[uint32](self) wasmimport_OutputStreamResourceDrop((uint32)(self0)) return } // BlockingFlush represents the imported method "blocking-flush". // // Request to flush buffered output, and block until flush completes // and stream is ready for writing again. // // blocking-flush: func() -> result<_, stream-error> // //go:nosplit func (self OutputStream) BlockingFlush() (result cm.Result[StreamError, struct{}, StreamError]) { self0 := cm.Reinterpret[uint32](self) wasmimport_OutputStreamBlockingFlush((uint32)(self0), &result) return } // BlockingSplice represents the imported method "blocking-splice". // // Read from one stream and write to another, with blocking. // // This is similar to `splice`, except that it blocks until the // `output-stream` is ready for writing, and the `input-stream` // is ready for reading, before performing the `splice`. // // blocking-splice: func(src: borrow, len: u64) -> result // //go:nosplit func (self OutputStream) BlockingSplice(src InputStream, len_ uint64) (result cm.Result[uint64, uint64, StreamError]) { self0 := cm.Reinterpret[uint32](self) src0 := cm.Reinterpret[uint32](src) len0 := (uint64)(len_) wasmimport_OutputStreamBlockingSplice((uint32)(self0), (uint32)(src0), (uint64)(len0), &result) return } // BlockingWriteAndFlush represents the imported method "blocking-write-and-flush". // // Perform a write of up to 4096 bytes, and then flush the stream. Block // until all of these operations are complete, or an error occurs. // // This is a convenience wrapper around the use of `check-write`, // `subscribe`, `write`, and `flush`, and is implemented with the // following pseudo-code: // // let pollable = this.subscribe(); // while !contents.is_empty() { // // Wait for the stream to become writable // pollable.block(); // let Ok(n) = this.check-write(); // eliding error handling // let len = min(n, contents.len()); // let (chunk, rest) = contents.split_at(len); // this.write(chunk ); // eliding error handling // contents = rest; // } // this.flush(); // // Wait for completion of `flush` // pollable.block(); // // Check for any errors that arose during `flush` // let _ = this.check-write(); // eliding error handling // // blocking-write-and-flush: func(contents: list) -> result<_, stream-error> // //go:nosplit func (self OutputStream) BlockingWriteAndFlush(contents cm.List[uint8]) (result cm.Result[StreamError, struct{}, StreamError]) { self0 := cm.Reinterpret[uint32](self) contents0, contents1 := cm.LowerList(contents) wasmimport_OutputStreamBlockingWriteAndFlush((uint32)(self0), (*uint8)(contents0), (uint32)(contents1), &result) return } // BlockingWriteZeroesAndFlush represents the imported method "blocking-write-zeroes-and-flush". // // Perform a write of up to 4096 zeroes, and then flush the stream. // Block until all of these operations are complete, or an error // occurs. // // This is a convenience wrapper around the use of `check-write`, // `subscribe`, `write-zeroes`, and `flush`, and is implemented with // the following pseudo-code: // // let pollable = this.subscribe(); // while num_zeroes != 0 { // // Wait for the stream to become writable // pollable.block(); // let Ok(n) = this.check-write(); // eliding error handling // let len = min(n, num_zeroes); // this.write-zeroes(len); // eliding error handling // num_zeroes -= len; // } // this.flush(); // // Wait for completion of `flush` // pollable.block(); // // Check for any errors that arose during `flush` // let _ = this.check-write(); // eliding error handling // // blocking-write-zeroes-and-flush: func(len: u64) -> result<_, stream-error> // //go:nosplit func (self OutputStream) BlockingWriteZeroesAndFlush(len_ uint64) (result cm.Result[StreamError, struct{}, StreamError]) { self0 := cm.Reinterpret[uint32](self) len0 := (uint64)(len_) wasmimport_OutputStreamBlockingWriteZeroesAndFlush((uint32)(self0), (uint64)(len0), &result) return } // CheckWrite represents the imported method "check-write". // // Check readiness for writing. This function never blocks. // // Returns the number of bytes permitted for the next call to `write`, // or an error. Calling `write` with more bytes than this function has // permitted will trap. // // When this function returns 0 bytes, the `subscribe` pollable will // become ready when this function will report at least 1 byte, or an // error. // // check-write: func() -> result // //go:nosplit func (self OutputStream) CheckWrite() (result cm.Result[uint64, uint64, StreamError]) { self0 := cm.Reinterpret[uint32](self) wasmimport_OutputStreamCheckWrite((uint32)(self0), &result) return } // Flush represents the imported method "flush". // // Request to flush buffered output. This function never blocks. // // This tells the output-stream that the caller intends any buffered // output to be flushed. the output which is expected to be flushed // is all that has been passed to `write` prior to this call. // // Upon calling this function, the `output-stream` will not accept any // writes (`check-write` will return `ok(0)`) until the flush has // completed. The `subscribe` pollable will become ready when the // flush has completed and the stream can accept more writes. // // flush: func() -> result<_, stream-error> // //go:nosplit func (self OutputStream) Flush() (result cm.Result[StreamError, struct{}, StreamError]) { self0 := cm.Reinterpret[uint32](self) wasmimport_OutputStreamFlush((uint32)(self0), &result) return } // Splice represents the imported method "splice". // // Read from one stream and write to another. // // The behavior of splice is equivelant to: // 1. calling `check-write` on the `output-stream` // 2. calling `read` on the `input-stream` with the smaller of the // `check-write` permitted length and the `len` provided to `splice` // 3. calling `write` on the `output-stream` with that read data. // // Any error reported by the call to `check-write`, `read`, or // `write` ends the splice and reports that error. // // This function returns the number of bytes transferred; it may be less // than `len`. // // splice: func(src: borrow, len: u64) -> result // //go:nosplit func (self OutputStream) Splice(src InputStream, len_ uint64) (result cm.Result[uint64, uint64, StreamError]) { self0 := cm.Reinterpret[uint32](self) src0 := cm.Reinterpret[uint32](src) len0 := (uint64)(len_) wasmimport_OutputStreamSplice((uint32)(self0), (uint32)(src0), (uint64)(len0), &result) return } // Subscribe represents the imported method "subscribe". // // Create a `pollable` which will resolve once the output-stream // is ready for more writing, or an error has occured. When this // pollable is ready, `check-write` will return `ok(n)` with n>0, or an // error. // // If the stream is closed, this pollable is always ready immediately. // // The created `pollable` is a child resource of the `output-stream`. // Implementations may trap if the `output-stream` is dropped before // all derived `pollable`s created with this function are dropped. // // subscribe: func() -> pollable // //go:nosplit func (self OutputStream) Subscribe() (result Pollable) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_OutputStreamSubscribe((uint32)(self0)) result = cm.Reinterpret[Pollable]((uint32)(result0)) return } // Write represents the imported method "write". // // Perform a write. This function never blocks. // // When the destination of a `write` is binary data, the bytes from // `contents` are written verbatim. When the destination of a `write` is // known to the implementation to be text, the bytes of `contents` are // transcoded from UTF-8 into the encoding of the destination and then // written. // // Precondition: check-write gave permit of Ok(n) and contents has a // length of less than or equal to n. Otherwise, this function will trap. // // returns Err(closed) without writing if the stream has closed since // the last call to check-write provided a permit. // // write: func(contents: list) -> result<_, stream-error> // //go:nosplit func (self OutputStream) Write(contents cm.List[uint8]) (result cm.Result[StreamError, struct{}, StreamError]) { self0 := cm.Reinterpret[uint32](self) contents0, contents1 := cm.LowerList(contents) wasmimport_OutputStreamWrite((uint32)(self0), (*uint8)(contents0), (uint32)(contents1), &result) return } // WriteZeroes represents the imported method "write-zeroes". // // Write zeroes to a stream. // // This should be used precisely like `write` with the exact same // preconditions (must use check-write first), but instead of // passing a list of bytes, you simply pass the number of zero-bytes // that should be written. // // write-zeroes: func(len: u64) -> result<_, stream-error> // //go:nosplit func (self OutputStream) WriteZeroes(len_ uint64) (result cm.Result[StreamError, struct{}, StreamError]) { self0 := cm.Reinterpret[uint32](self) len0 := (uint64)(len_) wasmimport_OutputStreamWriteZeroes((uint32)(self0), (uint64)(len0), &result) return } ================================================ FILE: src/internal/wasi/random/v0.2.0/insecure/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package insecure import ( "internal/cm" ) // This file contains wasmimport and wasmexport declarations for "wasi:random@0.2.0". //go:wasmimport wasi:random/insecure@0.2.0 get-insecure-random-bytes //go:noescape func wasmimport_GetInsecureRandomBytes(len0 uint64, result *cm.List[uint8]) //go:wasmimport wasi:random/insecure@0.2.0 get-insecure-random-u64 //go:noescape func wasmimport_GetInsecureRandomU64() (result0 uint64) ================================================ FILE: src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package insecure represents the imported interface "wasi:random/insecure@0.2.0". // // The insecure interface for insecure pseudo-random numbers. // // It is intended to be portable at least between Unix-family platforms and // Windows. package insecure import ( "internal/cm" ) // GetInsecureRandomBytes represents the imported function "get-insecure-random-bytes". // // Return `len` insecure pseudo-random bytes. // // This function is not cryptographically secure. Do not use it for // anything related to security. // // There are no requirements on the values of the returned bytes, however // implementations are encouraged to return evenly distributed values with // a long period. // // get-insecure-random-bytes: func(len: u64) -> list // //go:nosplit func GetInsecureRandomBytes(len_ uint64) (result cm.List[uint8]) { len0 := (uint64)(len_) wasmimport_GetInsecureRandomBytes((uint64)(len0), &result) return } // GetInsecureRandomU64 represents the imported function "get-insecure-random-u64". // // Return an insecure pseudo-random `u64` value. // // This function returns the same type of pseudo-random data as // `get-insecure-random-bytes`, represented as a `u64`. // // get-insecure-random-u64: func() -> u64 // //go:nosplit func GetInsecureRandomU64() (result uint64) { result0 := wasmimport_GetInsecureRandomU64() result = (uint64)((uint64)(result0)) return } ================================================ FILE: src/internal/wasi/random/v0.2.0/insecure-seed/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package insecureseed // This file contains wasmimport and wasmexport declarations for "wasi:random@0.2.0". //go:wasmimport wasi:random/insecure-seed@0.2.0 insecure-seed //go:noescape func wasmimport_InsecureSeed(result *[2]uint64) ================================================ FILE: src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package insecureseed represents the imported interface "wasi:random/insecure-seed@0.2.0". // // The insecure-seed interface for seeding hash-map DoS resistance. // // It is intended to be portable at least between Unix-family platforms and // Windows. package insecureseed // InsecureSeed represents the imported function "insecure-seed". // // Return a 128-bit value that may contain a pseudo-random value. // // The returned value is not required to be computed from a CSPRNG, and may // even be entirely deterministic. Host implementations are encouraged to // provide pseudo-random values to any program exposed to // attacker-controlled content, to enable DoS protection built into many // languages' hash-map implementations. // // This function is intended to only be called once, by a source language // to initialize Denial Of Service (DoS) protection in its hash-map // implementation. // // # Expected future evolution // // This will likely be changed to a value import, to prevent it from being // called multiple times and potentially used for purposes other than DoS // protection. // // insecure-seed: func() -> tuple // //go:nosplit func InsecureSeed() (result [2]uint64) { wasmimport_InsecureSeed(&result) return } ================================================ FILE: src/internal/wasi/random/v0.2.0/random/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/random/v0.2.0/random/random.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package random import ( "internal/cm" ) // This file contains wasmimport and wasmexport declarations for "wasi:random@0.2.0". //go:wasmimport wasi:random/random@0.2.0 get-random-bytes //go:noescape func wasmimport_GetRandomBytes(len0 uint64, result *cm.List[uint8]) //go:wasmimport wasi:random/random@0.2.0 get-random-u64 //go:noescape func wasmimport_GetRandomU64() (result0 uint64) ================================================ FILE: src/internal/wasi/random/v0.2.0/random/random.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package random represents the imported interface "wasi:random/random@0.2.0". // // WASI Random is a random data API. // // It is intended to be portable at least between Unix-family platforms and // Windows. package random import ( "internal/cm" ) // GetRandomBytes represents the imported function "get-random-bytes". // // Return `len` cryptographically-secure random or pseudo-random bytes. // // This function must produce data at least as cryptographically secure and // fast as an adequately seeded cryptographically-secure pseudo-random // number generator (CSPRNG). It must not block, from the perspective of // the calling program, under any circumstances, including on the first // request and on requests for numbers of bytes. The returned data must // always be unpredictable. // // This function must always return fresh data. Deterministic environments // must omit this function, rather than implementing it with deterministic // data. // // get-random-bytes: func(len: u64) -> list // //go:nosplit func GetRandomBytes(len_ uint64) (result cm.List[uint8]) { len0 := (uint64)(len_) wasmimport_GetRandomBytes((uint64)(len0), &result) return } // GetRandomU64 represents the imported function "get-random-u64". // // Return a cryptographically-secure random or pseudo-random `u64` value. // // This function returns the same type of data as `get-random-bytes`, // represented as a `u64`. // // get-random-u64: func() -> u64 // //go:nosplit func GetRandomU64() (result uint64) { result0 := wasmimport_GetRandomU64() result = (uint64)((uint64)(result0)) return } ================================================ FILE: src/internal/wasi/sockets/v0.2.0/instance-network/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package instancenetwork // This file contains wasmimport and wasmexport declarations for "wasi:sockets@0.2.0". //go:wasmimport wasi:sockets/instance-network@0.2.0 instance-network //go:noescape func wasmimport_InstanceNetwork() (result0 uint32) ================================================ FILE: src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package instancenetwork represents the imported interface "wasi:sockets/instance-network@0.2.0". // // This interface provides a value-export of the default network handle.. package instancenetwork import ( "internal/cm" "internal/wasi/sockets/v0.2.0/network" ) // Network represents the imported type alias "wasi:sockets/instance-network@0.2.0#network". // // See [network.Network] for more information. type Network = network.Network // InstanceNetwork represents the imported function "instance-network". // // Get a handle to the default network. // // instance-network: func() -> network // //go:nosplit func InstanceNetwork() (result Network) { result0 := wasmimport_InstanceNetwork() result = cm.Reinterpret[Network]((uint32)(result0)) return } ================================================ FILE: src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package ipnamelookup import ( "internal/cm" "unsafe" ) // OptionIPAddressShape is used for storage in variant or result types. type OptionIPAddressShape struct { _ cm.HostLayout shape [unsafe.Sizeof(cm.Option[IPAddress]{})]byte } ================================================ FILE: src/internal/wasi/sockets/v0.2.0/ip-name-lookup/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package ipnamelookup import ( "internal/cm" ) // This file contains wasmimport and wasmexport declarations for "wasi:sockets@0.2.0". //go:wasmimport wasi:sockets/ip-name-lookup@0.2.0 [resource-drop]resolve-address-stream //go:noescape func wasmimport_ResolveAddressStreamResourceDrop(self0 uint32) //go:wasmimport wasi:sockets/ip-name-lookup@0.2.0 [method]resolve-address-stream.resolve-next-address //go:noescape func wasmimport_ResolveAddressStreamResolveNextAddress(self0 uint32, result *cm.Result[OptionIPAddressShape, cm.Option[IPAddress], ErrorCode]) //go:wasmimport wasi:sockets/ip-name-lookup@0.2.0 [method]resolve-address-stream.subscribe //go:noescape func wasmimport_ResolveAddressStreamSubscribe(self0 uint32) (result0 uint32) //go:wasmimport wasi:sockets/ip-name-lookup@0.2.0 resolve-addresses //go:noescape func wasmimport_ResolveAddresses(network0 uint32, name0 *uint8, name1 uint32, result *cm.Result[ResolveAddressStream, ResolveAddressStream, ErrorCode]) ================================================ FILE: src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package ipnamelookup represents the imported interface "wasi:sockets/ip-name-lookup@0.2.0". package ipnamelookup import ( "internal/cm" "internal/wasi/io/v0.2.0/poll" "internal/wasi/sockets/v0.2.0/network" ) // Pollable represents the imported type alias "wasi:sockets/ip-name-lookup@0.2.0#pollable". // // See [poll.Pollable] for more information. type Pollable = poll.Pollable // Network represents the imported type alias "wasi:sockets/ip-name-lookup@0.2.0#network". // // See [network.Network] for more information. type Network = network.Network // ErrorCode represents the type alias "wasi:sockets/ip-name-lookup@0.2.0#error-code". // // See [network.ErrorCode] for more information. type ErrorCode = network.ErrorCode // IPAddress represents the type alias "wasi:sockets/ip-name-lookup@0.2.0#ip-address". // // See [network.IPAddress] for more information. type IPAddress = network.IPAddress // ResolveAddressStream represents the imported resource "wasi:sockets/ip-name-lookup@0.2.0#resolve-address-stream". // // resource resolve-address-stream type ResolveAddressStream cm.Resource // ResourceDrop represents the imported resource-drop for resource "resolve-address-stream". // // Drops a resource handle. // //go:nosplit func (self ResolveAddressStream) ResourceDrop() { self0 := cm.Reinterpret[uint32](self) wasmimport_ResolveAddressStreamResourceDrop((uint32)(self0)) return } // ResolveNextAddress represents the imported method "resolve-next-address". // // Returns the next address from the resolver. // // This function should be called multiple times. On each call, it will // return the next address in connection order preference. If all // addresses have been exhausted, this function returns `none`. // // This function never returns IPv4-mapped IPv6 addresses. // // # Typical errors // - `name-unresolvable`: Name does not exist or has no suitable associated // IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) // - `temporary-resolver-failure`: A temporary failure in name resolution occurred. // (EAI_AGAIN) // - `permanent-resolver-failure`: A permanent failure in name resolution occurred. // (EAI_FAIL) // - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) // // resolve-next-address: func() -> result, error-code> // //go:nosplit func (self ResolveAddressStream) ResolveNextAddress() (result cm.Result[OptionIPAddressShape, cm.Option[IPAddress], ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_ResolveAddressStreamResolveNextAddress((uint32)(self0), &result) return } // Subscribe represents the imported method "subscribe". // // Create a `pollable` which will resolve once the stream is ready for I/O. // // Note: this function is here for WASI Preview2 only. // It's planned to be removed when `future` is natively supported in Preview3. // // subscribe: func() -> pollable // //go:nosplit func (self ResolveAddressStream) Subscribe() (result Pollable) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_ResolveAddressStreamSubscribe((uint32)(self0)) result = cm.Reinterpret[Pollable]((uint32)(result0)) return } // ResolveAddresses represents the imported function "resolve-addresses". // // Resolve an internet host name to a list of IP addresses. // // Unicode domain names are automatically converted to ASCII using IDNA encoding. // If the input is an IP address string, the address is parsed and returned // as-is without making any external requests. // // See the wasi-socket proposal README.md for a comparison with getaddrinfo. // // This function never blocks. It either immediately fails or immediately // returns successfully with a `resolve-address-stream` that can be used // to (asynchronously) fetch the results. // // # Typical errors // - `invalid-argument`: `name` is a syntactically invalid domain name or IP address. // // # References: // - // - // - // - // // resolve-addresses: func(network: borrow, name: string) -> result // //go:nosplit func ResolveAddresses(network_ Network, name string) (result cm.Result[ResolveAddressStream, ResolveAddressStream, ErrorCode]) { network0 := cm.Reinterpret[uint32](network_) name0, name1 := cm.LowerString(name) wasmimport_ResolveAddresses((uint32)(network0), (*uint8)(name0), (uint32)(name1), &result) return } ================================================ FILE: src/internal/wasi/sockets/v0.2.0/network/abi.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package network import ( "internal/cm" "unsafe" ) // IPv6SocketAddressShape is used for storage in variant or result types. type IPv6SocketAddressShape struct { _ cm.HostLayout shape [unsafe.Sizeof(IPv6SocketAddress{})]byte } ================================================ FILE: src/internal/wasi/sockets/v0.2.0/network/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/sockets/v0.2.0/network/network.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package network // This file contains wasmimport and wasmexport declarations for "wasi:sockets@0.2.0". //go:wasmimport wasi:sockets/network@0.2.0 [resource-drop]network //go:noescape func wasmimport_NetworkResourceDrop(self0 uint32) ================================================ FILE: src/internal/wasi/sockets/v0.2.0/network/network.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package network represents the imported interface "wasi:sockets/network@0.2.0". package network import ( "internal/cm" ) // Network represents the imported resource "wasi:sockets/network@0.2.0#network". // // An opaque resource that represents access to (a subset of) the network. // This enables context-based security for networking. // There is no need for this to map 1:1 to a physical network interface. // // resource network type Network cm.Resource // ResourceDrop represents the imported resource-drop for resource "network". // // Drops a resource handle. // //go:nosplit func (self Network) ResourceDrop() { self0 := cm.Reinterpret[uint32](self) wasmimport_NetworkResourceDrop((uint32)(self0)) return } // ErrorCode represents the enum "wasi:sockets/network@0.2.0#error-code". // // Error codes. // // In theory, every API can return any error code. // In practice, API's typically only return the errors documented per API // combined with a couple of errors that are always possible: // - `unknown` // - `access-denied` // - `not-supported` // - `out-of-memory` // - `concurrency-conflict` // // See each individual API for what the POSIX equivalents are. They sometimes differ // per API. // // enum error-code { // unknown, // access-denied, // not-supported, // invalid-argument, // out-of-memory, // timeout, // concurrency-conflict, // not-in-progress, // would-block, // invalid-state, // new-socket-limit, // address-not-bindable, // address-in-use, // remote-unreachable, // connection-refused, // connection-reset, // connection-aborted, // datagram-too-large, // name-unresolvable, // temporary-resolver-failure, // permanent-resolver-failure // } type ErrorCode uint8 const ( // Unknown error ErrorCodeUnknown ErrorCode = iota // Access denied. // // POSIX equivalent: EACCES, EPERM ErrorCodeAccessDenied // The operation is not supported. // // POSIX equivalent: EOPNOTSUPP ErrorCodeNotSupported // One of the arguments is invalid. // // POSIX equivalent: EINVAL ErrorCodeInvalidArgument // Not enough memory to complete the operation. // // POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY ErrorCodeOutOfMemory // The operation timed out before it could finish completely. ErrorCodeTimeout // This operation is incompatible with another asynchronous operation that is already // in progress. // // POSIX equivalent: EALREADY ErrorCodeConcurrencyConflict // Trying to finish an asynchronous operation that: // - has not been started yet, or: // - was already finished by a previous `finish-*` call. // // Note: this is scheduled to be removed when `future`s are natively supported. ErrorCodeNotInProgress // The operation has been aborted because it could not be completed immediately. // // Note: this is scheduled to be removed when `future`s are natively supported. ErrorCodeWouldBlock // The operation is not valid in the socket's current state. ErrorCodeInvalidState // A new socket resource could not be created because of a system limit. ErrorCodeNewSocketLimit // A bind operation failed because the provided address is not an address that the // `network` can bind to. ErrorCodeAddressNotBindable // A bind operation failed because the provided address is already in use or because // there are no ephemeral ports available. ErrorCodeAddressInUse // The remote address is not reachable ErrorCodeRemoteUnreachable // The TCP connection was forcefully rejected ErrorCodeConnectionRefused // The TCP connection was reset. ErrorCodeConnectionReset // A TCP connection was aborted. ErrorCodeConnectionAborted // The size of a datagram sent to a UDP socket exceeded the maximum // supported size. ErrorCodeDatagramTooLarge // Name does not exist or has no suitable associated IP addresses. ErrorCodeNameUnresolvable // A temporary failure in name resolution occurred. ErrorCodeTemporaryResolverFailure // A permanent failure in name resolution occurred. ErrorCodePermanentResolverFailure ) var _ErrorCodeStrings = [21]string{ "unknown", "access-denied", "not-supported", "invalid-argument", "out-of-memory", "timeout", "concurrency-conflict", "not-in-progress", "would-block", "invalid-state", "new-socket-limit", "address-not-bindable", "address-in-use", "remote-unreachable", "connection-refused", "connection-reset", "connection-aborted", "datagram-too-large", "name-unresolvable", "temporary-resolver-failure", "permanent-resolver-failure", } // String implements [fmt.Stringer], returning the enum case name of e. func (e ErrorCode) String() string { return _ErrorCodeStrings[e] } // MarshalText implements [encoding.TextMarshaler]. func (e ErrorCode) MarshalText() ([]byte, error) { return []byte(e.String()), nil } // UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum // case. Returns an error if the supplied text is not one of the enum cases. func (e *ErrorCode) UnmarshalText(text []byte) error { return _ErrorCodeUnmarshalCase(e, text) } var _ErrorCodeUnmarshalCase = cm.CaseUnmarshaler[ErrorCode](_ErrorCodeStrings[:]) // IPAddressFamily represents the enum "wasi:sockets/network@0.2.0#ip-address-family". // // enum ip-address-family { // ipv4, // ipv6 // } type IPAddressFamily uint8 const ( // Similar to `AF_INET` in POSIX. IPAddressFamilyIPv4 IPAddressFamily = iota // Similar to `AF_INET6` in POSIX. IPAddressFamilyIPv6 ) var _IPAddressFamilyStrings = [2]string{ "ipv4", "ipv6", } // String implements [fmt.Stringer], returning the enum case name of e. func (e IPAddressFamily) String() string { return _IPAddressFamilyStrings[e] } // MarshalText implements [encoding.TextMarshaler]. func (e IPAddressFamily) MarshalText() ([]byte, error) { return []byte(e.String()), nil } // UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum // case. Returns an error if the supplied text is not one of the enum cases. func (e *IPAddressFamily) UnmarshalText(text []byte) error { return _IPAddressFamilyUnmarshalCase(e, text) } var _IPAddressFamilyUnmarshalCase = cm.CaseUnmarshaler[IPAddressFamily](_IPAddressFamilyStrings[:]) // IPv4Address represents the tuple "wasi:sockets/network@0.2.0#ipv4-address". // // type ipv4-address = tuple type IPv4Address [4]uint8 // IPv6Address represents the tuple "wasi:sockets/network@0.2.0#ipv6-address". // // type ipv6-address = tuple type IPv6Address [8]uint16 // IPAddress represents the variant "wasi:sockets/network@0.2.0#ip-address". // // variant ip-address { // ipv4(ipv4-address), // ipv6(ipv6-address), // } type IPAddress cm.Variant[uint8, IPv6Address, IPv6Address] // IPAddressIPv4 returns a [IPAddress] of case "ipv4". func IPAddressIPv4(data IPv4Address) IPAddress { return cm.New[IPAddress](0, data) } // IPv4 returns a non-nil *[IPv4Address] if [IPAddress] represents the variant case "ipv4". func (self *IPAddress) IPv4() *IPv4Address { return cm.Case[IPv4Address](self, 0) } // IPAddressIPv6 returns a [IPAddress] of case "ipv6". func IPAddressIPv6(data IPv6Address) IPAddress { return cm.New[IPAddress](1, data) } // IPv6 returns a non-nil *[IPv6Address] if [IPAddress] represents the variant case "ipv6". func (self *IPAddress) IPv6() *IPv6Address { return cm.Case[IPv6Address](self, 1) } var _IPAddressStrings = [2]string{ "ipv4", "ipv6", } // String implements [fmt.Stringer], returning the variant case name of v. func (v IPAddress) String() string { return _IPAddressStrings[v.Tag()] } // IPv4SocketAddress represents the record "wasi:sockets/network@0.2.0#ipv4-socket-address". // // record ipv4-socket-address { // port: u16, // address: ipv4-address, // } type IPv4SocketAddress struct { _ cm.HostLayout `json:"-"` // sin_port Port uint16 `json:"port"` // sin_addr Address IPv4Address `json:"address"` } // IPv6SocketAddress represents the record "wasi:sockets/network@0.2.0#ipv6-socket-address". // // record ipv6-socket-address { // port: u16, // flow-info: u32, // address: ipv6-address, // scope-id: u32, // } type IPv6SocketAddress struct { _ cm.HostLayout `json:"-"` // sin6_port Port uint16 `json:"port"` // sin6_flowinfo FlowInfo uint32 `json:"flow-info"` // sin6_addr Address IPv6Address `json:"address"` // sin6_scope_id ScopeID uint32 `json:"scope-id"` } // IPSocketAddress represents the variant "wasi:sockets/network@0.2.0#ip-socket-address". // // variant ip-socket-address { // ipv4(ipv4-socket-address), // ipv6(ipv6-socket-address), // } type IPSocketAddress cm.Variant[uint8, IPv6SocketAddressShape, IPv6SocketAddress] // IPSocketAddressIPv4 returns a [IPSocketAddress] of case "ipv4". func IPSocketAddressIPv4(data IPv4SocketAddress) IPSocketAddress { return cm.New[IPSocketAddress](0, data) } // IPv4 returns a non-nil *[IPv4SocketAddress] if [IPSocketAddress] represents the variant case "ipv4". func (self *IPSocketAddress) IPv4() *IPv4SocketAddress { return cm.Case[IPv4SocketAddress](self, 0) } // IPSocketAddressIPv6 returns a [IPSocketAddress] of case "ipv6". func IPSocketAddressIPv6(data IPv6SocketAddress) IPSocketAddress { return cm.New[IPSocketAddress](1, data) } // IPv6 returns a non-nil *[IPv6SocketAddress] if [IPSocketAddress] represents the variant case "ipv6". func (self *IPSocketAddress) IPv6() *IPv6SocketAddress { return cm.Case[IPv6SocketAddress](self, 1) } var _IPSocketAddressStrings = [2]string{ "ipv4", "ipv6", } // String implements [fmt.Stringer], returning the variant case name of v. func (v IPSocketAddress) String() string { return _IPSocketAddressStrings[v.Tag()] } ================================================ FILE: src/internal/wasi/sockets/v0.2.0/tcp/abi.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package tcp import ( "internal/cm" "internal/wasi/sockets/v0.2.0/network" "unsafe" ) // TupleTCPSocketInputStreamOutputStreamShape is used for storage in variant or result types. type TupleTCPSocketInputStreamOutputStreamShape struct { _ cm.HostLayout shape [unsafe.Sizeof(cm.Tuple3[TCPSocket, InputStream, OutputStream]{})]byte } // TupleInputStreamOutputStreamShape is used for storage in variant or result types. type TupleInputStreamOutputStreamShape struct { _ cm.HostLayout shape [unsafe.Sizeof(cm.Tuple[InputStream, OutputStream]{})]byte } // IPSocketAddressShape is used for storage in variant or result types. type IPSocketAddressShape struct { _ cm.HostLayout shape [unsafe.Sizeof(IPSocketAddress{})]byte } func lower_IPv4Address(v network.IPv4Address) (f0 uint32, f1 uint32, f2 uint32, f3 uint32) { f0 = (uint32)(v[0]) f1 = (uint32)(v[1]) f2 = (uint32)(v[2]) f3 = (uint32)(v[3]) return } func lower_IPv4SocketAddress(v network.IPv4SocketAddress) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32) { f0 = (uint32)(v.Port) f1, f2, f3, f4 = lower_IPv4Address(v.Address) return } func lower_IPv6Address(v network.IPv6Address) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32) { f0 = (uint32)(v[0]) f1 = (uint32)(v[1]) f2 = (uint32)(v[2]) f3 = (uint32)(v[3]) f4 = (uint32)(v[4]) f5 = (uint32)(v[5]) f6 = (uint32)(v[6]) f7 = (uint32)(v[7]) return } func lower_IPv6SocketAddress(v network.IPv6SocketAddress) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32, f8 uint32, f9 uint32, f10 uint32) { f0 = (uint32)(v.Port) f1 = (uint32)(v.FlowInfo) f2, f3, f4, f5, f6, f7, f8, f9 = lower_IPv6Address(v.Address) f10 = (uint32)(v.ScopeID) return } func lower_IPSocketAddress(v network.IPSocketAddress) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32, f8 uint32, f9 uint32, f10 uint32, f11 uint32) { f0 = (uint32)(v.Tag()) switch f0 { case 0: // ipv4 v1, v2, v3, v4, v5 := lower_IPv4SocketAddress(*cm.Case[network.IPv4SocketAddress](&v, 0)) f1 = (uint32)(v1) f2 = (uint32)(v2) f3 = (uint32)(v3) f4 = (uint32)(v4) f5 = (uint32)(v5) case 1: // ipv6 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 := lower_IPv6SocketAddress(*cm.Case[network.IPv6SocketAddress](&v, 1)) f1 = (uint32)(v1) f2 = (uint32)(v2) f3 = (uint32)(v3) f4 = (uint32)(v4) f5 = (uint32)(v5) f6 = (uint32)(v6) f7 = (uint32)(v7) f8 = (uint32)(v8) f9 = (uint32)(v9) f10 = (uint32)(v10) f11 = (uint32)(v11) } return } ================================================ FILE: src/internal/wasi/sockets/v0.2.0/tcp/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package tcp import ( "internal/cm" ) // This file contains wasmimport and wasmexport declarations for "wasi:sockets@0.2.0". //go:wasmimport wasi:sockets/tcp@0.2.0 [resource-drop]tcp-socket //go:noescape func wasmimport_TCPSocketResourceDrop(self0 uint32) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.accept //go:noescape func wasmimport_TCPSocketAccept(self0 uint32, result *cm.Result[TupleTCPSocketInputStreamOutputStreamShape, cm.Tuple3[TCPSocket, InputStream, OutputStream], ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.address-family //go:noescape func wasmimport_TCPSocketAddressFamily(self0 uint32) (result0 uint32) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.finish-bind //go:noescape func wasmimport_TCPSocketFinishBind(self0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.finish-connect //go:noescape func wasmimport_TCPSocketFinishConnect(self0 uint32, result *cm.Result[TupleInputStreamOutputStreamShape, cm.Tuple[InputStream, OutputStream], ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.finish-listen //go:noescape func wasmimport_TCPSocketFinishListen(self0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.hop-limit //go:noescape func wasmimport_TCPSocketHopLimit(self0 uint32, result *cm.Result[uint8, uint8, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.is-listening //go:noescape func wasmimport_TCPSocketIsListening(self0 uint32) (result0 uint32) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-count //go:noescape func wasmimport_TCPSocketKeepAliveCount(self0 uint32, result *cm.Result[uint32, uint32, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-enabled //go:noescape func wasmimport_TCPSocketKeepAliveEnabled(self0 uint32, result *cm.Result[ErrorCode, bool, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-idle-time //go:noescape func wasmimport_TCPSocketKeepAliveIdleTime(self0 uint32, result *cm.Result[uint64, Duration, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-interval //go:noescape func wasmimport_TCPSocketKeepAliveInterval(self0 uint32, result *cm.Result[uint64, Duration, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.local-address //go:noescape func wasmimport_TCPSocketLocalAddress(self0 uint32, result *cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.receive-buffer-size //go:noescape func wasmimport_TCPSocketReceiveBufferSize(self0 uint32, result *cm.Result[uint64, uint64, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.remote-address //go:noescape func wasmimport_TCPSocketRemoteAddress(self0 uint32, result *cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.send-buffer-size //go:noescape func wasmimport_TCPSocketSendBufferSize(self0 uint32, result *cm.Result[uint64, uint64, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-hop-limit //go:noescape func wasmimport_TCPSocketSetHopLimit(self0 uint32, value0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-count //go:noescape func wasmimport_TCPSocketSetKeepAliveCount(self0 uint32, value0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-enabled //go:noescape func wasmimport_TCPSocketSetKeepAliveEnabled(self0 uint32, value0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-idle-time //go:noescape func wasmimport_TCPSocketSetKeepAliveIdleTime(self0 uint32, value0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-interval //go:noescape func wasmimport_TCPSocketSetKeepAliveInterval(self0 uint32, value0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-listen-backlog-size //go:noescape func wasmimport_TCPSocketSetListenBacklogSize(self0 uint32, value0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-receive-buffer-size //go:noescape func wasmimport_TCPSocketSetReceiveBufferSize(self0 uint32, value0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-send-buffer-size //go:noescape func wasmimport_TCPSocketSetSendBufferSize(self0 uint32, value0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.shutdown //go:noescape func wasmimport_TCPSocketShutdown(self0 uint32, shutdownType0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.start-bind //go:noescape func wasmimport_TCPSocketStartBind(self0 uint32, network0 uint32, localAddress0 uint32, localAddress1 uint32, localAddress2 uint32, localAddress3 uint32, localAddress4 uint32, localAddress5 uint32, localAddress6 uint32, localAddress7 uint32, localAddress8 uint32, localAddress9 uint32, localAddress10 uint32, localAddress11 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.start-connect //go:noescape func wasmimport_TCPSocketStartConnect(self0 uint32, network0 uint32, remoteAddress0 uint32, remoteAddress1 uint32, remoteAddress2 uint32, remoteAddress3 uint32, remoteAddress4 uint32, remoteAddress5 uint32, remoteAddress6 uint32, remoteAddress7 uint32, remoteAddress8 uint32, remoteAddress9 uint32, remoteAddress10 uint32, remoteAddress11 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.start-listen //go:noescape func wasmimport_TCPSocketStartListen(self0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.subscribe //go:noescape func wasmimport_TCPSocketSubscribe(self0 uint32) (result0 uint32) ================================================ FILE: src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package tcp represents the imported interface "wasi:sockets/tcp@0.2.0". package tcp import ( "internal/cm" monotonicclock "internal/wasi/clocks/v0.2.0/monotonic-clock" "internal/wasi/io/v0.2.0/poll" "internal/wasi/io/v0.2.0/streams" "internal/wasi/sockets/v0.2.0/network" ) // InputStream represents the imported type alias "wasi:sockets/tcp@0.2.0#input-stream". // // See [streams.InputStream] for more information. type InputStream = streams.InputStream // OutputStream represents the imported type alias "wasi:sockets/tcp@0.2.0#output-stream". // // See [streams.OutputStream] for more information. type OutputStream = streams.OutputStream // Pollable represents the imported type alias "wasi:sockets/tcp@0.2.0#pollable". // // See [poll.Pollable] for more information. type Pollable = poll.Pollable // Duration represents the type alias "wasi:sockets/tcp@0.2.0#duration". // // See [monotonicclock.Duration] for more information. type Duration = monotonicclock.Duration // Network represents the imported type alias "wasi:sockets/tcp@0.2.0#network". // // See [network.Network] for more information. type Network = network.Network // ErrorCode represents the type alias "wasi:sockets/tcp@0.2.0#error-code". // // See [network.ErrorCode] for more information. type ErrorCode = network.ErrorCode // IPSocketAddress represents the type alias "wasi:sockets/tcp@0.2.0#ip-socket-address". // // See [network.IPSocketAddress] for more information. type IPSocketAddress = network.IPSocketAddress // IPAddressFamily represents the type alias "wasi:sockets/tcp@0.2.0#ip-address-family". // // See [network.IPAddressFamily] for more information. type IPAddressFamily = network.IPAddressFamily // ShutdownType represents the enum "wasi:sockets/tcp@0.2.0#shutdown-type". // // enum shutdown-type { // receive, // send, // both // } type ShutdownType uint8 const ( // Similar to `SHUT_RD` in POSIX. ShutdownTypeReceive ShutdownType = iota // Similar to `SHUT_WR` in POSIX. ShutdownTypeSend // Similar to `SHUT_RDWR` in POSIX. ShutdownTypeBoth ) var _ShutdownTypeStrings = [3]string{ "receive", "send", "both", } // String implements [fmt.Stringer], returning the enum case name of e. func (e ShutdownType) String() string { return _ShutdownTypeStrings[e] } // MarshalText implements [encoding.TextMarshaler]. func (e ShutdownType) MarshalText() ([]byte, error) { return []byte(e.String()), nil } // UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum // case. Returns an error if the supplied text is not one of the enum cases. func (e *ShutdownType) UnmarshalText(text []byte) error { return _ShutdownTypeUnmarshalCase(e, text) } var _ShutdownTypeUnmarshalCase = cm.CaseUnmarshaler[ShutdownType](_ShutdownTypeStrings[:]) // TCPSocket represents the imported resource "wasi:sockets/tcp@0.2.0#tcp-socket". // // A TCP socket resource. // // The socket can be in one of the following states: // - `unbound` // - `bind-in-progress` // - `bound` (See note below) // - `listen-in-progress` // - `listening` // - `connect-in-progress` // - `connected` // - `closed` // See // for a more information. // // Note: Except where explicitly mentioned, whenever this documentation uses // the term "bound" without backticks it actually means: in the `bound` state *or // higher*. // (i.e. `bound`, `listen-in-progress`, `listening`, `connect-in-progress` or `connected`) // // In addition to the general error codes documented on the // `network::error-code` type, TCP socket methods may always return // `error(invalid-state)` when in the `closed` state. // // resource tcp-socket type TCPSocket cm.Resource // ResourceDrop represents the imported resource-drop for resource "tcp-socket". // // Drops a resource handle. // //go:nosplit func (self TCPSocket) ResourceDrop() { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketResourceDrop((uint32)(self0)) return } // Accept represents the imported method "accept". // // Accept a new client socket. // // The returned socket is bound and in the `connected` state. The following properties // are inherited from the listener socket: // - `address-family` // - `keep-alive-enabled` // - `keep-alive-idle-time` // - `keep-alive-interval` // - `keep-alive-count` // - `hop-limit` // - `receive-buffer-size` // - `send-buffer-size` // // On success, this function returns the newly accepted client socket along with // a pair of streams that can be used to read & write to the connection. // // # Typical errors // - `invalid-state`: Socket is not in the `listening` state. (EINVAL) // - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) // - `connection-aborted`: An incoming connection was pending, but was terminated // by the client before this listener could accept it. (ECONNABORTED) // - `new-socket-limit`: The new socket resource could not be created because of // a system limit. (EMFILE, ENFILE) // // # References // - // - // - // - // // accept: func() -> result, error-code> // //go:nosplit func (self TCPSocket) Accept() (result cm.Result[TupleTCPSocketInputStreamOutputStreamShape, cm.Tuple3[TCPSocket, InputStream, OutputStream], ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketAccept((uint32)(self0), &result) return } // AddressFamily represents the imported method "address-family". // // Whether this is a IPv4 or IPv6 socket. // // Equivalent to the SO_DOMAIN socket option. // // address-family: func() -> ip-address-family // //go:nosplit func (self TCPSocket) AddressFamily() (result IPAddressFamily) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_TCPSocketAddressFamily((uint32)(self0)) result = (network.IPAddressFamily)((uint32)(result0)) return } // FinishBind represents the imported method "finish-bind". // // finish-bind: func() -> result<_, error-code> // //go:nosplit func (self TCPSocket) FinishBind() (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketFinishBind((uint32)(self0), &result) return } // FinishConnect represents the imported method "finish-connect". // // finish-connect: func() -> result, error-code> // //go:nosplit func (self TCPSocket) FinishConnect() (result cm.Result[TupleInputStreamOutputStreamShape, cm.Tuple[InputStream, OutputStream], ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketFinishConnect((uint32)(self0), &result) return } // FinishListen represents the imported method "finish-listen". // // finish-listen: func() -> result<_, error-code> // //go:nosplit func (self TCPSocket) FinishListen() (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketFinishListen((uint32)(self0), &result) return } // HopLimit represents the imported method "hop-limit". // // Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. // // If the provided value is 0, an `invalid-argument` error is returned. // // # Typical errors // - `invalid-argument`: (set) The TTL value must be 1 or higher. // // hop-limit: func() -> result // //go:nosplit func (self TCPSocket) HopLimit() (result cm.Result[uint8, uint8, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketHopLimit((uint32)(self0), &result) return } // IsListening represents the imported method "is-listening". // // Whether the socket is in the `listening` state. // // Equivalent to the SO_ACCEPTCONN socket option. // // is-listening: func() -> bool // //go:nosplit func (self TCPSocket) IsListening() (result bool) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_TCPSocketIsListening((uint32)(self0)) result = (bool)(cm.U32ToBool((uint32)(result0))) return } // KeepAliveCount represents the imported method "keep-alive-count". // // The maximum amount of keepalive packets TCP should send before aborting the connection. // // If the provided value is 0, an `invalid-argument` error is returned. // Any other value will never cause an error, but it might be silently clamped and/or // rounded. // I.e. after setting a value, reading the same setting back may return a different // value. // // Equivalent to the TCP_KEEPCNT socket option. // // # Typical errors // - `invalid-argument`: (set) The provided value was 0. // // keep-alive-count: func() -> result // //go:nosplit func (self TCPSocket) KeepAliveCount() (result cm.Result[uint32, uint32, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketKeepAliveCount((uint32)(self0), &result) return } // KeepAliveEnabled represents the imported method "keep-alive-enabled". // // Enables or disables keepalive. // // The keepalive behavior can be adjusted using: // - `keep-alive-idle-time` // - `keep-alive-interval` // - `keep-alive-count` // These properties can be configured while `keep-alive-enabled` is false, but only // come into effect when `keep-alive-enabled` is true. // // Equivalent to the SO_KEEPALIVE socket option. // // keep-alive-enabled: func() -> result // //go:nosplit func (self TCPSocket) KeepAliveEnabled() (result cm.Result[ErrorCode, bool, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketKeepAliveEnabled((uint32)(self0), &result) return } // KeepAliveIdleTime represents the imported method "keep-alive-idle-time". // // Amount of time the connection has to be idle before TCP starts sending keepalive // packets. // // If the provided value is 0, an `invalid-argument` error is returned. // Any other value will never cause an error, but it might be silently clamped and/or // rounded. // I.e. after setting a value, reading the same setting back may return a different // value. // // Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS) // // # Typical errors // - `invalid-argument`: (set) The provided value was 0. // // keep-alive-idle-time: func() -> result // //go:nosplit func (self TCPSocket) KeepAliveIdleTime() (result cm.Result[uint64, Duration, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketKeepAliveIdleTime((uint32)(self0), &result) return } // KeepAliveInterval represents the imported method "keep-alive-interval". // // The time between keepalive packets. // // If the provided value is 0, an `invalid-argument` error is returned. // Any other value will never cause an error, but it might be silently clamped and/or // rounded. // I.e. after setting a value, reading the same setting back may return a different // value. // // Equivalent to the TCP_KEEPINTVL socket option. // // # Typical errors // - `invalid-argument`: (set) The provided value was 0. // // keep-alive-interval: func() -> result // //go:nosplit func (self TCPSocket) KeepAliveInterval() (result cm.Result[uint64, Duration, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketKeepAliveInterval((uint32)(self0), &result) return } // LocalAddress represents the imported method "local-address". // // Get the bound local address. // // POSIX mentions: // > If the socket has not been bound to a local name, the value // > stored in the object pointed to by `address` is unspecified. // // WASI is stricter and requires `local-address` to return `invalid-state` when the // socket hasn't been bound yet. // // # Typical errors // - `invalid-state`: The socket is not bound to any local address. // // # References // - // - // - // - // // local-address: func() -> result // //go:nosplit func (self TCPSocket) LocalAddress() (result cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketLocalAddress((uint32)(self0), &result) return } // ReceiveBufferSize represents the imported method "receive-buffer-size". // // The kernel buffer space reserved for sends/receives on this socket. // // If the provided value is 0, an `invalid-argument` error is returned. // Any other value will never cause an error, but it might be silently clamped and/or // rounded. // I.e. after setting a value, reading the same setting back may return a different // value. // // Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. // // # Typical errors // - `invalid-argument`: (set) The provided value was 0. // // receive-buffer-size: func() -> result // //go:nosplit func (self TCPSocket) ReceiveBufferSize() (result cm.Result[uint64, uint64, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketReceiveBufferSize((uint32)(self0), &result) return } // RemoteAddress represents the imported method "remote-address". // // Get the remote address. // // # Typical errors // - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) // // # References // - // - // - // - // // remote-address: func() -> result // //go:nosplit func (self TCPSocket) RemoteAddress() (result cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketRemoteAddress((uint32)(self0), &result) return } // SendBufferSize represents the imported method "send-buffer-size". // // send-buffer-size: func() -> result // //go:nosplit func (self TCPSocket) SendBufferSize() (result cm.Result[uint64, uint64, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketSendBufferSize((uint32)(self0), &result) return } // SetHopLimit represents the imported method "set-hop-limit". // // set-hop-limit: func(value: u8) -> result<_, error-code> // //go:nosplit func (self TCPSocket) SetHopLimit(value uint8) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint32)(value) wasmimport_TCPSocketSetHopLimit((uint32)(self0), (uint32)(value0), &result) return } // SetKeepAliveCount represents the imported method "set-keep-alive-count". // // set-keep-alive-count: func(value: u32) -> result<_, error-code> // //go:nosplit func (self TCPSocket) SetKeepAliveCount(value uint32) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint32)(value) wasmimport_TCPSocketSetKeepAliveCount((uint32)(self0), (uint32)(value0), &result) return } // SetKeepAliveEnabled represents the imported method "set-keep-alive-enabled". // // set-keep-alive-enabled: func(value: bool) -> result<_, error-code> // //go:nosplit func (self TCPSocket) SetKeepAliveEnabled(value bool) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint32)(cm.BoolToU32(value)) wasmimport_TCPSocketSetKeepAliveEnabled((uint32)(self0), (uint32)(value0), &result) return } // SetKeepAliveIdleTime represents the imported method "set-keep-alive-idle-time". // // set-keep-alive-idle-time: func(value: duration) -> result<_, error-code> // //go:nosplit func (self TCPSocket) SetKeepAliveIdleTime(value Duration) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint64)(value) wasmimport_TCPSocketSetKeepAliveIdleTime((uint32)(self0), (uint64)(value0), &result) return } // SetKeepAliveInterval represents the imported method "set-keep-alive-interval". // // set-keep-alive-interval: func(value: duration) -> result<_, error-code> // //go:nosplit func (self TCPSocket) SetKeepAliveInterval(value Duration) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint64)(value) wasmimport_TCPSocketSetKeepAliveInterval((uint32)(self0), (uint64)(value0), &result) return } // SetListenBacklogSize represents the imported method "set-listen-backlog-size". // // Hints the desired listen queue size. Implementations are free to ignore this. // // If the provided value is 0, an `invalid-argument` error is returned. // Any other value will never cause an error, but it might be silently clamped and/or // rounded. // // # Typical errors // - `not-supported`: (set) The platform does not support changing the backlog // size after the initial listen. // - `invalid-argument`: (set) The provided value was 0. // - `invalid-state`: (set) The socket is in the `connect-in-progress` or `connected` // state. // // set-listen-backlog-size: func(value: u64) -> result<_, error-code> // //go:nosplit func (self TCPSocket) SetListenBacklogSize(value uint64) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint64)(value) wasmimport_TCPSocketSetListenBacklogSize((uint32)(self0), (uint64)(value0), &result) return } // SetReceiveBufferSize represents the imported method "set-receive-buffer-size". // // set-receive-buffer-size: func(value: u64) -> result<_, error-code> // //go:nosplit func (self TCPSocket) SetReceiveBufferSize(value uint64) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint64)(value) wasmimport_TCPSocketSetReceiveBufferSize((uint32)(self0), (uint64)(value0), &result) return } // SetSendBufferSize represents the imported method "set-send-buffer-size". // // set-send-buffer-size: func(value: u64) -> result<_, error-code> // //go:nosplit func (self TCPSocket) SetSendBufferSize(value uint64) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint64)(value) wasmimport_TCPSocketSetSendBufferSize((uint32)(self0), (uint64)(value0), &result) return } // Shutdown represents the imported method "shutdown". // // Initiate a graceful shutdown. // // - `receive`: The socket is not expecting to receive any data from // the peer. The `input-stream` associated with this socket will be // closed. Any data still in the receive queue at time of calling // this method will be discarded. // - `send`: The socket has no more data to send to the peer. The `output-stream` // associated with this socket will be closed and a FIN packet will be sent. // - `both`: Same effect as `receive` & `send` combined. // // This function is idempotent. Shutting a down a direction more than once // has no effect and returns `ok`. // // The shutdown function does not close (drop) the socket. // // # Typical errors // - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) // // # References // - // - // - // - // // shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code> // //go:nosplit func (self TCPSocket) Shutdown(shutdownType ShutdownType) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) shutdownType0 := (uint32)(shutdownType) wasmimport_TCPSocketShutdown((uint32)(self0), (uint32)(shutdownType0), &result) return } // StartBind represents the imported method "start-bind". // // Bind the socket to a specific network on the provided IP address and port. // // If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the // implementation to decide which // network interface(s) to bind to. // If the TCP/UDP port is zero, the socket will be bound to a random free port. // // Bind can be attempted multiple times on the same socket, even with // different arguments on each iteration. But never concurrently and // only as long as the previous bind failed. Once a bind succeeds, the // binding can't be changed anymore. // // # Typical errors // - `invalid-argument`: The `local-address` has the wrong address family. // (EAFNOSUPPORT, EFAULT on Windows) // - `invalid-argument`: `local-address` is not a unicast address. (EINVAL) // - `invalid-argument`: `local-address` is an IPv4-mapped IPv6 address. // (EINVAL) // - `invalid-state`: The socket is already bound. (EINVAL) // - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS // on Windows) // - `address-in-use`: Address is already in use. (EADDRINUSE) // - `address-not-bindable`: `local-address` is not an address that the `network` // can bind to. (EADDRNOTAVAIL) // - `not-in-progress`: A `bind` operation is not in progress. // - `would-block`: Can't finish the operation, it is still in progress. // (EWOULDBLOCK, EAGAIN) // // # Implementors note // When binding to a non-zero port, this bind operation shouldn't be affected by the // TIME_WAIT // state of a recently closed socket on the same local address. In practice this means // that the SO_REUSEADDR // socket option should be set implicitly on all platforms, except on Windows where // this is the default behavior // and SO_REUSEADDR performs something different entirely. // // Unlike in POSIX, in WASI the bind operation is async. This enables // interactive WASI hosts to inject permission prompts. Runtimes that // don't want to make use of this ability can simply call the native // `bind` as part of either `start-bind` or `finish-bind`. // // # References // - // - // - // - // // start-bind: func(network: borrow, local-address: ip-socket-address) -> // result<_, error-code> // //go:nosplit func (self TCPSocket) StartBind(network_ Network, localAddress IPSocketAddress) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) network0 := cm.Reinterpret[uint32](network_) localAddress0, localAddress1, localAddress2, localAddress3, localAddress4, localAddress5, localAddress6, localAddress7, localAddress8, localAddress9, localAddress10, localAddress11 := lower_IPSocketAddress(localAddress) wasmimport_TCPSocketStartBind((uint32)(self0), (uint32)(network0), (uint32)(localAddress0), (uint32)(localAddress1), (uint32)(localAddress2), (uint32)(localAddress3), (uint32)(localAddress4), (uint32)(localAddress5), (uint32)(localAddress6), (uint32)(localAddress7), (uint32)(localAddress8), (uint32)(localAddress9), (uint32)(localAddress10), (uint32)(localAddress11), &result) return } // StartConnect represents the imported method "start-connect". // // Connect to a remote endpoint. // // On success: // - the socket is transitioned into the `connection` state. // - a pair of streams is returned that can be used to read & write to the connection // // After a failed connection attempt, the socket will be in the `closed` // state and the only valid action left is to `drop` the socket. A single // socket can not be used to connect more than once. // // # Typical errors // - `invalid-argument`: The `remote-address` has the wrong address family. // (EAFNOSUPPORT) // - `invalid-argument`: `remote-address` is not a unicast address. (EINVAL, // ENETUNREACH on Linux, EAFNOSUPPORT on MacOS) // - `invalid-argument`: `remote-address` is an IPv4-mapped IPv6 address. // (EINVAL, EADDRNOTAVAIL on Illumos) // - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY // (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) // - `invalid-argument`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL // on Windows) // - `invalid-argument`: The socket is already attached to a different network. // The `network` passed to `connect` must be identical to the one passed to `bind`. // - `invalid-state`: The socket is already in the `connected` state. // (EISCONN) // - `invalid-state`: The socket is already in the `listening` state. // (EOPNOTSUPP, EINVAL on Windows) // - `timeout`: Connection timed out. (ETIMEDOUT) // - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) // - `connection-reset`: The connection was reset. (ECONNRESET) // - `connection-aborted`: The connection was aborted. (ECONNABORTED) // - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, // EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) // - `address-in-use`: Tried to perform an implicit bind, but there were // no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) // - `not-in-progress`: A connect operation is not in progress. // - `would-block`: Can't finish the operation, it is still in progress. // (EWOULDBLOCK, EAGAIN) // // # Implementors note // The POSIX equivalent of `start-connect` is the regular `connect` syscall. // Because all WASI sockets are non-blocking this is expected to return // EINPROGRESS, which should be translated to `ok()` in WASI. // // The POSIX equivalent of `finish-connect` is a `poll` for event `POLLOUT` // with a timeout of 0 on the socket descriptor. Followed by a check for // the `SO_ERROR` socket option, in case the poll signaled readiness. // // # References // - // - // - // - // // start-connect: func(network: borrow, remote-address: ip-socket-address) // -> result<_, error-code> // //go:nosplit func (self TCPSocket) StartConnect(network_ Network, remoteAddress IPSocketAddress) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) network0 := cm.Reinterpret[uint32](network_) remoteAddress0, remoteAddress1, remoteAddress2, remoteAddress3, remoteAddress4, remoteAddress5, remoteAddress6, remoteAddress7, remoteAddress8, remoteAddress9, remoteAddress10, remoteAddress11 := lower_IPSocketAddress(remoteAddress) wasmimport_TCPSocketStartConnect((uint32)(self0), (uint32)(network0), (uint32)(remoteAddress0), (uint32)(remoteAddress1), (uint32)(remoteAddress2), (uint32)(remoteAddress3), (uint32)(remoteAddress4), (uint32)(remoteAddress5), (uint32)(remoteAddress6), (uint32)(remoteAddress7), (uint32)(remoteAddress8), (uint32)(remoteAddress9), (uint32)(remoteAddress10), (uint32)(remoteAddress11), &result) return } // StartListen represents the imported method "start-listen". // // Start listening for new connections. // // Transitions the socket into the `listening` state. // // Unlike POSIX, the socket must already be explicitly bound. // // # Typical errors // - `invalid-state`: The socket is not bound to any local address. (EDESTADDRREQ) // - `invalid-state`: The socket is already in the `connected` state. // (EISCONN, EINVAL on BSD) // - `invalid-state`: The socket is already in the `listening` state. // - `address-in-use`: Tried to perform an implicit bind, but there were // no ephemeral ports available. (EADDRINUSE) // - `not-in-progress`: A listen operation is not in progress. // - `would-block`: Can't finish the operation, it is still in progress. // (EWOULDBLOCK, EAGAIN) // // # Implementors note // Unlike in POSIX, in WASI the listen operation is async. This enables // interactive WASI hosts to inject permission prompts. Runtimes that // don't want to make use of this ability can simply call the native // `listen` as part of either `start-listen` or `finish-listen`. // // # References // - // - // - // - // // start-listen: func() -> result<_, error-code> // //go:nosplit func (self TCPSocket) StartListen() (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketStartListen((uint32)(self0), &result) return } // Subscribe represents the imported method "subscribe". // // Create a `pollable` which can be used to poll for, or block on, // completion of any of the asynchronous operations of this socket. // // When `finish-bind`, `finish-listen`, `finish-connect` or `accept` // return `error(would-block)`, this pollable can be used to wait for // their success or failure, after which the method can be retried. // // The pollable is not limited to the async operation that happens to be // in progress at the time of calling `subscribe` (if any). Theoretically, // `subscribe` only has to be called once per socket and can then be // (re)used for the remainder of the socket's lifetime. // // See // for a more information. // // Note: this function is here for WASI Preview2 only. // It's planned to be removed when `future` is natively supported in Preview3. // // subscribe: func() -> pollable // //go:nosplit func (self TCPSocket) Subscribe() (result Pollable) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_TCPSocketSubscribe((uint32)(self0)) result = cm.Reinterpret[Pollable]((uint32)(result0)) return } ================================================ FILE: src/internal/wasi/sockets/v0.2.0/tcp-create-socket/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package tcpcreatesocket import ( "internal/cm" ) // This file contains wasmimport and wasmexport declarations for "wasi:sockets@0.2.0". //go:wasmimport wasi:sockets/tcp-create-socket@0.2.0 create-tcp-socket //go:noescape func wasmimport_CreateTCPSocket(addressFamily0 uint32, result *cm.Result[TCPSocket, TCPSocket, ErrorCode]) ================================================ FILE: src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package tcpcreatesocket represents the imported interface "wasi:sockets/tcp-create-socket@0.2.0". package tcpcreatesocket import ( "internal/cm" "internal/wasi/sockets/v0.2.0/network" "internal/wasi/sockets/v0.2.0/tcp" ) // Network represents the imported type alias "wasi:sockets/tcp-create-socket@0.2.0#network". // // See [network.Network] for more information. type Network = network.Network // ErrorCode represents the type alias "wasi:sockets/tcp-create-socket@0.2.0#error-code". // // See [network.ErrorCode] for more information. type ErrorCode = network.ErrorCode // IPAddressFamily represents the type alias "wasi:sockets/tcp-create-socket@0.2.0#ip-address-family". // // See [network.IPAddressFamily] for more information. type IPAddressFamily = network.IPAddressFamily // TCPSocket represents the imported type alias "wasi:sockets/tcp-create-socket@0.2.0#tcp-socket". // // See [tcp.TCPSocket] for more information. type TCPSocket = tcp.TCPSocket // CreateTCPSocket represents the imported function "create-tcp-socket". // // Create a new TCP socket. // // Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. // On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. // // This function does not require a network capability handle. This is considered // to be safe because // at time of creation, the socket is not bound to any `network` yet. Up to the moment // `bind`/`connect` // is called, the socket is effectively an in-memory configuration object, unable // to communicate with the outside world. // // All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous // operations. // // # Typical errors // - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) // - `new-socket-limit`: The new socket resource could not be created because of // a system limit. (EMFILE, ENFILE) // // # References // - // - // - // - // // create-tcp-socket: func(address-family: ip-address-family) -> result // //go:nosplit func CreateTCPSocket(addressFamily IPAddressFamily) (result cm.Result[TCPSocket, TCPSocket, ErrorCode]) { addressFamily0 := (uint32)(addressFamily) wasmimport_CreateTCPSocket((uint32)(addressFamily0), &result) return } ================================================ FILE: src/internal/wasi/sockets/v0.2.0/udp/abi.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package udp import ( "internal/cm" "internal/wasi/sockets/v0.2.0/network" "unsafe" ) // IPSocketAddressShape is used for storage in variant or result types. type IPSocketAddressShape struct { _ cm.HostLayout shape [unsafe.Sizeof(IPSocketAddress{})]byte } func lower_IPv4Address(v network.IPv4Address) (f0 uint32, f1 uint32, f2 uint32, f3 uint32) { f0 = (uint32)(v[0]) f1 = (uint32)(v[1]) f2 = (uint32)(v[2]) f3 = (uint32)(v[3]) return } func lower_IPv4SocketAddress(v network.IPv4SocketAddress) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32) { f0 = (uint32)(v.Port) f1, f2, f3, f4 = lower_IPv4Address(v.Address) return } func lower_IPv6Address(v network.IPv6Address) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32) { f0 = (uint32)(v[0]) f1 = (uint32)(v[1]) f2 = (uint32)(v[2]) f3 = (uint32)(v[3]) f4 = (uint32)(v[4]) f5 = (uint32)(v[5]) f6 = (uint32)(v[6]) f7 = (uint32)(v[7]) return } func lower_IPv6SocketAddress(v network.IPv6SocketAddress) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32, f8 uint32, f9 uint32, f10 uint32) { f0 = (uint32)(v.Port) f1 = (uint32)(v.FlowInfo) f2, f3, f4, f5, f6, f7, f8, f9 = lower_IPv6Address(v.Address) f10 = (uint32)(v.ScopeID) return } func lower_IPSocketAddress(v network.IPSocketAddress) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32, f8 uint32, f9 uint32, f10 uint32, f11 uint32) { f0 = (uint32)(v.Tag()) switch f0 { case 0: // ipv4 v1, v2, v3, v4, v5 := lower_IPv4SocketAddress(*cm.Case[network.IPv4SocketAddress](&v, 0)) f1 = (uint32)(v1) f2 = (uint32)(v2) f3 = (uint32)(v3) f4 = (uint32)(v4) f5 = (uint32)(v5) case 1: // ipv6 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 := lower_IPv6SocketAddress(*cm.Case[network.IPv6SocketAddress](&v, 1)) f1 = (uint32)(v1) f2 = (uint32)(v2) f3 = (uint32)(v3) f4 = (uint32)(v4) f5 = (uint32)(v5) f6 = (uint32)(v6) f7 = (uint32)(v7) f8 = (uint32)(v8) f9 = (uint32)(v9) f10 = (uint32)(v10) f11 = (uint32)(v11) } return } // TupleIncomingDatagramStreamOutgoingDatagramStreamShape is used for storage in variant or result types. type TupleIncomingDatagramStreamOutgoingDatagramStreamShape struct { _ cm.HostLayout shape [unsafe.Sizeof(cm.Tuple[IncomingDatagramStream, OutgoingDatagramStream]{})]byte } func lower_OptionIPSocketAddress(v cm.Option[IPSocketAddress]) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32, f8 uint32, f9 uint32, f10 uint32, f11 uint32, f12 uint32) { some := v.Some() if some != nil { f0 = 1 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12 := lower_IPSocketAddress(*some) f1 = (uint32)(v1) f2 = (uint32)(v2) f3 = (uint32)(v3) f4 = (uint32)(v4) f5 = (uint32)(v5) f6 = (uint32)(v6) f7 = (uint32)(v7) f8 = (uint32)(v8) f9 = (uint32)(v9) f10 = (uint32)(v10) f11 = (uint32)(v11) f12 = (uint32)(v12) } return } ================================================ FILE: src/internal/wasi/sockets/v0.2.0/udp/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package udp import ( "internal/cm" ) // This file contains wasmimport and wasmexport declarations for "wasi:sockets@0.2.0". //go:wasmimport wasi:sockets/udp@0.2.0 [resource-drop]udp-socket //go:noescape func wasmimport_UDPSocketResourceDrop(self0 uint32) //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.address-family //go:noescape func wasmimport_UDPSocketAddressFamily(self0 uint32) (result0 uint32) //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.finish-bind //go:noescape func wasmimport_UDPSocketFinishBind(self0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.local-address //go:noescape func wasmimport_UDPSocketLocalAddress(self0 uint32, result *cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.receive-buffer-size //go:noescape func wasmimport_UDPSocketReceiveBufferSize(self0 uint32, result *cm.Result[uint64, uint64, ErrorCode]) //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.remote-address //go:noescape func wasmimport_UDPSocketRemoteAddress(self0 uint32, result *cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.send-buffer-size //go:noescape func wasmimport_UDPSocketSendBufferSize(self0 uint32, result *cm.Result[uint64, uint64, ErrorCode]) //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.set-receive-buffer-size //go:noescape func wasmimport_UDPSocketSetReceiveBufferSize(self0 uint32, value0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.set-send-buffer-size //go:noescape func wasmimport_UDPSocketSetSendBufferSize(self0 uint32, value0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.set-unicast-hop-limit //go:noescape func wasmimport_UDPSocketSetUnicastHopLimit(self0 uint32, value0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.start-bind //go:noescape func wasmimport_UDPSocketStartBind(self0 uint32, network0 uint32, localAddress0 uint32, localAddress1 uint32, localAddress2 uint32, localAddress3 uint32, localAddress4 uint32, localAddress5 uint32, localAddress6 uint32, localAddress7 uint32, localAddress8 uint32, localAddress9 uint32, localAddress10 uint32, localAddress11 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.stream //go:noescape func wasmimport_UDPSocketStream(self0 uint32, remoteAddress0 uint32, remoteAddress1 uint32, remoteAddress2 uint32, remoteAddress3 uint32, remoteAddress4 uint32, remoteAddress5 uint32, remoteAddress6 uint32, remoteAddress7 uint32, remoteAddress8 uint32, remoteAddress9 uint32, remoteAddress10 uint32, remoteAddress11 uint32, remoteAddress12 uint32, result *cm.Result[TupleIncomingDatagramStreamOutgoingDatagramStreamShape, cm.Tuple[IncomingDatagramStream, OutgoingDatagramStream], ErrorCode]) //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.subscribe //go:noescape func wasmimport_UDPSocketSubscribe(self0 uint32) (result0 uint32) //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.unicast-hop-limit //go:noescape func wasmimport_UDPSocketUnicastHopLimit(self0 uint32, result *cm.Result[uint8, uint8, ErrorCode]) //go:wasmimport wasi:sockets/udp@0.2.0 [resource-drop]incoming-datagram-stream //go:noescape func wasmimport_IncomingDatagramStreamResourceDrop(self0 uint32) //go:wasmimport wasi:sockets/udp@0.2.0 [method]incoming-datagram-stream.receive //go:noescape func wasmimport_IncomingDatagramStreamReceive(self0 uint32, maxResults0 uint64, result *cm.Result[cm.List[IncomingDatagram], cm.List[IncomingDatagram], ErrorCode]) //go:wasmimport wasi:sockets/udp@0.2.0 [method]incoming-datagram-stream.subscribe //go:noescape func wasmimport_IncomingDatagramStreamSubscribe(self0 uint32) (result0 uint32) //go:wasmimport wasi:sockets/udp@0.2.0 [resource-drop]outgoing-datagram-stream //go:noescape func wasmimport_OutgoingDatagramStreamResourceDrop(self0 uint32) //go:wasmimport wasi:sockets/udp@0.2.0 [method]outgoing-datagram-stream.check-send //go:noescape func wasmimport_OutgoingDatagramStreamCheckSend(self0 uint32, result *cm.Result[uint64, uint64, ErrorCode]) //go:wasmimport wasi:sockets/udp@0.2.0 [method]outgoing-datagram-stream.send //go:noescape func wasmimport_OutgoingDatagramStreamSend(self0 uint32, datagrams0 *OutgoingDatagram, datagrams1 uint32, result *cm.Result[uint64, uint64, ErrorCode]) //go:wasmimport wasi:sockets/udp@0.2.0 [method]outgoing-datagram-stream.subscribe //go:noescape func wasmimport_OutgoingDatagramStreamSubscribe(self0 uint32) (result0 uint32) ================================================ FILE: src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package udp represents the imported interface "wasi:sockets/udp@0.2.0". package udp import ( "internal/cm" "internal/wasi/io/v0.2.0/poll" "internal/wasi/sockets/v0.2.0/network" ) // Pollable represents the imported type alias "wasi:sockets/udp@0.2.0#pollable". // // See [poll.Pollable] for more information. type Pollable = poll.Pollable // Network represents the imported type alias "wasi:sockets/udp@0.2.0#network". // // See [network.Network] for more information. type Network = network.Network // ErrorCode represents the type alias "wasi:sockets/udp@0.2.0#error-code". // // See [network.ErrorCode] for more information. type ErrorCode = network.ErrorCode // IPSocketAddress represents the type alias "wasi:sockets/udp@0.2.0#ip-socket-address". // // See [network.IPSocketAddress] for more information. type IPSocketAddress = network.IPSocketAddress // IPAddressFamily represents the type alias "wasi:sockets/udp@0.2.0#ip-address-family". // // See [network.IPAddressFamily] for more information. type IPAddressFamily = network.IPAddressFamily // IncomingDatagram represents the record "wasi:sockets/udp@0.2.0#incoming-datagram". // // A received datagram. // // record incoming-datagram { // data: list, // remote-address: ip-socket-address, // } type IncomingDatagram struct { _ cm.HostLayout `json:"-"` // The payload. // // Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. Data cm.List[uint8] `json:"data"` // The source address. // // This field is guaranteed to match the remote address the stream was initialized // with, if any. // // Equivalent to the `src_addr` out parameter of `recvfrom`. RemoteAddress IPSocketAddress `json:"remote-address"` } // OutgoingDatagram represents the record "wasi:sockets/udp@0.2.0#outgoing-datagram". // // A datagram to be sent out. // // record outgoing-datagram { // data: list, // remote-address: option, // } type OutgoingDatagram struct { _ cm.HostLayout `json:"-"` // The payload. Data cm.List[uint8] `json:"data"` // The destination address. // // The requirements on this field depend on how the stream was initialized: // - with a remote address: this field must be None or match the stream's remote address // exactly. // - without a remote address: this field is required. // // If this value is None, the send operation is equivalent to `send` in POSIX. Otherwise // it is equivalent to `sendto`. RemoteAddress cm.Option[IPSocketAddress] `json:"remote-address"` } // UDPSocket represents the imported resource "wasi:sockets/udp@0.2.0#udp-socket". // // A UDP socket handle. // // resource udp-socket type UDPSocket cm.Resource // ResourceDrop represents the imported resource-drop for resource "udp-socket". // // Drops a resource handle. // //go:nosplit func (self UDPSocket) ResourceDrop() { self0 := cm.Reinterpret[uint32](self) wasmimport_UDPSocketResourceDrop((uint32)(self0)) return } // AddressFamily represents the imported method "address-family". // // Whether this is a IPv4 or IPv6 socket. // // Equivalent to the SO_DOMAIN socket option. // // address-family: func() -> ip-address-family // //go:nosplit func (self UDPSocket) AddressFamily() (result IPAddressFamily) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_UDPSocketAddressFamily((uint32)(self0)) result = (network.IPAddressFamily)((uint32)(result0)) return } // FinishBind represents the imported method "finish-bind". // // finish-bind: func() -> result<_, error-code> // //go:nosplit func (self UDPSocket) FinishBind() (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_UDPSocketFinishBind((uint32)(self0), &result) return } // LocalAddress represents the imported method "local-address". // // Get the current bound address. // // POSIX mentions: // > If the socket has not been bound to a local name, the value // > stored in the object pointed to by `address` is unspecified. // // WASI is stricter and requires `local-address` to return `invalid-state` when the // socket hasn't been bound yet. // // # Typical errors // - `invalid-state`: The socket is not bound to any local address. // // # References // - // - // - // - // // local-address: func() -> result // //go:nosplit func (self UDPSocket) LocalAddress() (result cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_UDPSocketLocalAddress((uint32)(self0), &result) return } // ReceiveBufferSize represents the imported method "receive-buffer-size". // // The kernel buffer space reserved for sends/receives on this socket. // // If the provided value is 0, an `invalid-argument` error is returned. // Any other value will never cause an error, but it might be silently clamped and/or // rounded. // I.e. after setting a value, reading the same setting back may return a different // value. // // Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. // // # Typical errors // - `invalid-argument`: (set) The provided value was 0. // // receive-buffer-size: func() -> result // //go:nosplit func (self UDPSocket) ReceiveBufferSize() (result cm.Result[uint64, uint64, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_UDPSocketReceiveBufferSize((uint32)(self0), &result) return } // RemoteAddress represents the imported method "remote-address". // // Get the address the socket is currently streaming to. // // # Typical errors // - `invalid-state`: The socket is not streaming to a specific remote address. (ENOTCONN) // // # References // - // - // - // - // // remote-address: func() -> result // //go:nosplit func (self UDPSocket) RemoteAddress() (result cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_UDPSocketRemoteAddress((uint32)(self0), &result) return } // SendBufferSize represents the imported method "send-buffer-size". // // send-buffer-size: func() -> result // //go:nosplit func (self UDPSocket) SendBufferSize() (result cm.Result[uint64, uint64, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_UDPSocketSendBufferSize((uint32)(self0), &result) return } // SetReceiveBufferSize represents the imported method "set-receive-buffer-size". // // set-receive-buffer-size: func(value: u64) -> result<_, error-code> // //go:nosplit func (self UDPSocket) SetReceiveBufferSize(value uint64) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint64)(value) wasmimport_UDPSocketSetReceiveBufferSize((uint32)(self0), (uint64)(value0), &result) return } // SetSendBufferSize represents the imported method "set-send-buffer-size". // // set-send-buffer-size: func(value: u64) -> result<_, error-code> // //go:nosplit func (self UDPSocket) SetSendBufferSize(value uint64) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint64)(value) wasmimport_UDPSocketSetSendBufferSize((uint32)(self0), (uint64)(value0), &result) return } // SetUnicastHopLimit represents the imported method "set-unicast-hop-limit". // // set-unicast-hop-limit: func(value: u8) -> result<_, error-code> // //go:nosplit func (self UDPSocket) SetUnicastHopLimit(value uint8) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint32)(value) wasmimport_UDPSocketSetUnicastHopLimit((uint32)(self0), (uint32)(value0), &result) return } // StartBind represents the imported method "start-bind". // // Bind the socket to a specific network on the provided IP address and port. // // If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the // implementation to decide which // network interface(s) to bind to. // If the port is zero, the socket will be bound to a random free port. // // # Typical errors // - `invalid-argument`: The `local-address` has the wrong address family. // (EAFNOSUPPORT, EFAULT on Windows) // - `invalid-state`: The socket is already bound. (EINVAL) // - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS // on Windows) // - `address-in-use`: Address is already in use. (EADDRINUSE) // - `address-not-bindable`: `local-address` is not an address that the `network` // can bind to. (EADDRNOTAVAIL) // - `not-in-progress`: A `bind` operation is not in progress. // - `would-block`: Can't finish the operation, it is still in progress. // (EWOULDBLOCK, EAGAIN) // // # Implementors note // Unlike in POSIX, in WASI the bind operation is async. This enables // interactive WASI hosts to inject permission prompts. Runtimes that // don't want to make use of this ability can simply call the native // `bind` as part of either `start-bind` or `finish-bind`. // // # References // - // - // - // - // // start-bind: func(network: borrow, local-address: ip-socket-address) -> // result<_, error-code> // //go:nosplit func (self UDPSocket) StartBind(network_ Network, localAddress IPSocketAddress) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) network0 := cm.Reinterpret[uint32](network_) localAddress0, localAddress1, localAddress2, localAddress3, localAddress4, localAddress5, localAddress6, localAddress7, localAddress8, localAddress9, localAddress10, localAddress11 := lower_IPSocketAddress(localAddress) wasmimport_UDPSocketStartBind((uint32)(self0), (uint32)(network0), (uint32)(localAddress0), (uint32)(localAddress1), (uint32)(localAddress2), (uint32)(localAddress3), (uint32)(localAddress4), (uint32)(localAddress5), (uint32)(localAddress6), (uint32)(localAddress7), (uint32)(localAddress8), (uint32)(localAddress9), (uint32)(localAddress10), (uint32)(localAddress11), &result) return } // Stream represents the imported method "stream". // // Set up inbound & outbound communication channels, optionally to a specific peer. // // This function only changes the local socket configuration and does not generate // any network traffic. // On success, the `remote-address` of the socket is updated. The `local-address` // may be updated as well, // based on the best network path to `remote-address`. // // When a `remote-address` is provided, the returned streams are limited to communicating // with that specific peer: // - `send` can only be used to send to this destination. // - `receive` will only return datagrams sent from the provided `remote-address`. // // This method may be called multiple times on the same socket to change its association, // but // only the most recently returned pair of streams will be operational. Implementations // may trap if // the streams returned by a previous invocation haven't been dropped yet before calling // `stream` again. // // The POSIX equivalent in pseudo-code is: // // if (was previously connected) { // connect(s, AF_UNSPEC) // } // if (remote_address is Some) { // connect(s, remote_address) // } // // Unlike in POSIX, the socket must already be explicitly bound. // // # Typical errors // - `invalid-argument`: The `remote-address` has the wrong address family. // (EAFNOSUPPORT) // - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY // (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) // - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, // EADDRNOTAVAIL) // - `invalid-state`: The socket is not bound. // - `address-in-use`: Tried to perform an implicit bind, but there were // no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) // - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, // ENETRESET, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) // - `connection-refused`: The connection was refused. (ECONNREFUSED) // // # References // - // - // - // - // // %stream: func(remote-address: option) -> result, error-code> // //go:nosplit func (self UDPSocket) Stream(remoteAddress cm.Option[IPSocketAddress]) (result cm.Result[TupleIncomingDatagramStreamOutgoingDatagramStreamShape, cm.Tuple[IncomingDatagramStream, OutgoingDatagramStream], ErrorCode]) { self0 := cm.Reinterpret[uint32](self) remoteAddress0, remoteAddress1, remoteAddress2, remoteAddress3, remoteAddress4, remoteAddress5, remoteAddress6, remoteAddress7, remoteAddress8, remoteAddress9, remoteAddress10, remoteAddress11, remoteAddress12 := lower_OptionIPSocketAddress(remoteAddress) wasmimport_UDPSocketStream((uint32)(self0), (uint32)(remoteAddress0), (uint32)(remoteAddress1), (uint32)(remoteAddress2), (uint32)(remoteAddress3), (uint32)(remoteAddress4), (uint32)(remoteAddress5), (uint32)(remoteAddress6), (uint32)(remoteAddress7), (uint32)(remoteAddress8), (uint32)(remoteAddress9), (uint32)(remoteAddress10), (uint32)(remoteAddress11), (uint32)(remoteAddress12), &result) return } // Subscribe represents the imported method "subscribe". // // Create a `pollable` which will resolve once the socket is ready for I/O. // // Note: this function is here for WASI Preview2 only. // It's planned to be removed when `future` is natively supported in Preview3. // // subscribe: func() -> pollable // //go:nosplit func (self UDPSocket) Subscribe() (result Pollable) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_UDPSocketSubscribe((uint32)(self0)) result = cm.Reinterpret[Pollable]((uint32)(result0)) return } // UnicastHopLimit represents the imported method "unicast-hop-limit". // // Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. // // If the provided value is 0, an `invalid-argument` error is returned. // // # Typical errors // - `invalid-argument`: (set) The TTL value must be 1 or higher. // // unicast-hop-limit: func() -> result // //go:nosplit func (self UDPSocket) UnicastHopLimit() (result cm.Result[uint8, uint8, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_UDPSocketUnicastHopLimit((uint32)(self0), &result) return } // IncomingDatagramStream represents the imported resource "wasi:sockets/udp@0.2.0#incoming-datagram-stream". // // resource incoming-datagram-stream type IncomingDatagramStream cm.Resource // ResourceDrop represents the imported resource-drop for resource "incoming-datagram-stream". // // Drops a resource handle. // //go:nosplit func (self IncomingDatagramStream) ResourceDrop() { self0 := cm.Reinterpret[uint32](self) wasmimport_IncomingDatagramStreamResourceDrop((uint32)(self0)) return } // Receive represents the imported method "receive". // // Receive messages on the socket. // // This function attempts to receive up to `max-results` datagrams on the socket without // blocking. // The returned list may contain fewer elements than requested, but never more. // // This function returns successfully with an empty list when either: // - `max-results` is 0, or: // - `max-results` is greater than 0, but no results are immediately available. // This function never returns `error(would-block)`. // // # Typical errors // - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET // on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) // - `connection-refused`: The connection was refused. (ECONNREFUSED) // // # References // - // - // - // - // - // - // - // - // // receive: func(max-results: u64) -> result, error-code> // //go:nosplit func (self IncomingDatagramStream) Receive(maxResults uint64) (result cm.Result[cm.List[IncomingDatagram], cm.List[IncomingDatagram], ErrorCode]) { self0 := cm.Reinterpret[uint32](self) maxResults0 := (uint64)(maxResults) wasmimport_IncomingDatagramStreamReceive((uint32)(self0), (uint64)(maxResults0), &result) return } // Subscribe represents the imported method "subscribe". // // Create a `pollable` which will resolve once the stream is ready to receive again. // // Note: this function is here for WASI Preview2 only. // It's planned to be removed when `future` is natively supported in Preview3. // // subscribe: func() -> pollable // //go:nosplit func (self IncomingDatagramStream) Subscribe() (result Pollable) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_IncomingDatagramStreamSubscribe((uint32)(self0)) result = cm.Reinterpret[Pollable]((uint32)(result0)) return } // OutgoingDatagramStream represents the imported resource "wasi:sockets/udp@0.2.0#outgoing-datagram-stream". // // resource outgoing-datagram-stream type OutgoingDatagramStream cm.Resource // ResourceDrop represents the imported resource-drop for resource "outgoing-datagram-stream". // // Drops a resource handle. // //go:nosplit func (self OutgoingDatagramStream) ResourceDrop() { self0 := cm.Reinterpret[uint32](self) wasmimport_OutgoingDatagramStreamResourceDrop((uint32)(self0)) return } // CheckSend represents the imported method "check-send". // // Check readiness for sending. This function never blocks. // // Returns the number of datagrams permitted for the next call to `send`, // or an error. Calling `send` with more datagrams than this function has // permitted will trap. // // When this function returns ok(0), the `subscribe` pollable will // become ready when this function will report at least ok(1), or an // error. // // Never returns `would-block`. // // check-send: func() -> result // //go:nosplit func (self OutgoingDatagramStream) CheckSend() (result cm.Result[uint64, uint64, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_OutgoingDatagramStreamCheckSend((uint32)(self0), &result) return } // Send represents the imported method "send". // // Send messages on the socket. // // This function attempts to send all provided `datagrams` on the socket without blocking // and // returns how many messages were actually sent (or queued for sending). This function // never // returns `error(would-block)`. If none of the datagrams were able to be sent, `ok(0)` // is returned. // // This function semantically behaves the same as iterating the `datagrams` list and // sequentially // sending each individual datagram until either the end of the list has been reached // or the first error occurred. // If at least one datagram has been sent successfully, this function never returns // an error. // // If the input list is empty, the function returns `ok(0)`. // // Each call to `send` must be permitted by a preceding `check-send`. Implementations // must trap if // either `check-send` was not called or `datagrams` contains more items than `check-send` // permitted. // // # Typical errors // - `invalid-argument`: The `remote-address` has the wrong address family. // (EAFNOSUPPORT) // - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY // (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) // - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, // EADDRNOTAVAIL) // - `invalid-argument`: The socket is in "connected" mode and `remote-address` // is `some` value that does not match the address passed to `stream`. (EISCONN) // - `invalid-argument`: The socket is not "connected" and no value for `remote-address` // was provided. (EDESTADDRREQ) // - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, // ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) // - `connection-refused`: The connection was refused. (ECONNREFUSED) // - `datagram-too-large`: The datagram is too large. (EMSGSIZE) // // # References // - // - // - // - // - // - // - // - // // send: func(datagrams: list) -> result // //go:nosplit func (self OutgoingDatagramStream) Send(datagrams cm.List[OutgoingDatagram]) (result cm.Result[uint64, uint64, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) datagrams0, datagrams1 := cm.LowerList(datagrams) wasmimport_OutgoingDatagramStreamSend((uint32)(self0), (*OutgoingDatagram)(datagrams0), (uint32)(datagrams1), &result) return } // Subscribe represents the imported method "subscribe". // // Create a `pollable` which will resolve once the stream is ready to send again. // // Note: this function is here for WASI Preview2 only. // It's planned to be removed when `future` is natively supported in Preview3. // // subscribe: func() -> pollable // //go:nosplit func (self OutgoingDatagramStream) Subscribe() (result Pollable) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_OutgoingDatagramStreamSubscribe((uint32)(self0)) result = cm.Reinterpret[Pollable]((uint32)(result0)) return } ================================================ FILE: src/internal/wasi/sockets/v0.2.0/udp-create-socket/empty.s ================================================ // This file exists for testing this package without WebAssembly, // allowing empty function bodies with a //go:wasmimport directive. // See https://pkg.go.dev/cmd/compile for more information. ================================================ FILE: src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wasm.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. package udpcreatesocket import ( "internal/cm" ) // This file contains wasmimport and wasmexport declarations for "wasi:sockets@0.2.0". //go:wasmimport wasi:sockets/udp-create-socket@0.2.0 create-udp-socket //go:noescape func wasmimport_CreateUDPSocket(addressFamily0 uint32, result *cm.Result[UDPSocket, UDPSocket, ErrorCode]) ================================================ FILE: src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go ================================================ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package udpcreatesocket represents the imported interface "wasi:sockets/udp-create-socket@0.2.0". package udpcreatesocket import ( "internal/cm" "internal/wasi/sockets/v0.2.0/network" "internal/wasi/sockets/v0.2.0/udp" ) // Network represents the imported type alias "wasi:sockets/udp-create-socket@0.2.0#network". // // See [network.Network] for more information. type Network = network.Network // ErrorCode represents the type alias "wasi:sockets/udp-create-socket@0.2.0#error-code". // // See [network.ErrorCode] for more information. type ErrorCode = network.ErrorCode // IPAddressFamily represents the type alias "wasi:sockets/udp-create-socket@0.2.0#ip-address-family". // // See [network.IPAddressFamily] for more information. type IPAddressFamily = network.IPAddressFamily // UDPSocket represents the imported type alias "wasi:sockets/udp-create-socket@0.2.0#udp-socket". // // See [udp.UDPSocket] for more information. type UDPSocket = udp.UDPSocket // CreateUDPSocket represents the imported function "create-udp-socket". // // Create a new UDP socket. // // Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. // On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. // // This function does not require a network capability handle. This is considered // to be safe because // at time of creation, the socket is not bound to any `network` yet. Up to the moment // `bind` is called, // the socket is effectively an in-memory configuration object, unable to communicate // with the outside world. // // All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous // operations. // // # Typical errors // - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) // - `new-socket-limit`: The new socket resource could not be created because of // a system limit. (EMFILE, ENFILE) // // # References: // - // - // - // - // // create-udp-socket: func(address-family: ip-address-family) -> result // //go:nosplit func CreateUDPSocket(addressFamily IPAddressFamily) (result cm.Result[UDPSocket, UDPSocket, ErrorCode]) { addressFamily0 := (uint32)(addressFamily) wasmimport_CreateUDPSocket((uint32)(addressFamily0), &result) return } ================================================ FILE: src/machine/adc.go ================================================ package machine // Hardware abstraction layer for the analog-to-digital conversion (ADC) // peripheral. // ADCConfig holds ADC configuration parameters. If left unspecified, the zero // value of each parameter will use the peripheral's default settings. type ADCConfig struct { Reference uint32 // analog reference voltage (AREF) in millivolts Resolution uint32 // number of bits for a single conversion (e.g., 8, 10, 12) Samples uint32 // number of samples for a single conversion (e.g., 4, 8, 16, 32) SampleTime uint32 // sample time, in microseconds (µs) } ================================================ FILE: src/machine/board_adafruit-esp32-feather-v2.go ================================================ //go:build adafruit_esp32_feather_v2 package machine const GPIO20 Pin = 20 const ( IO0 = GPIO0 IO2 = GPIO2 IO4 = GPIO4 IO5 = GPIO5 IO7 = GPIO7 IO8 = GPIO8 IO12 = GPIO12 IO13 = GPIO13 IO14 = GPIO14 IO15 = GPIO15 IO19 = GPIO19 IO20 = GPIO20 IO21 = GPIO21 IO22 = GPIO22 IO25 = GPIO25 IO26 = GPIO26 IO27 = GPIO27 IO32 = GPIO32 IO33 = GPIO33 IO34 = GPIO34 IO35 = GPIO35 IO36 = GPIO36 IO37 = GPIO37 IO38 = GPIO38 IO39 = GPIO39 ) // Digital pins const ( D12 = IO12 D13 = IO13 D14 = IO14 D15 = IO15 D27 = IO27 D32 = IO32 D33 = IO33 D37 = IO37 ) // Analog pins const ( A0 = IO26 A1 = IO25 A2 = IO34 A3 = IO39 A4 = IO36 A5 = IO4 ) // Built-in LEDs and Button const ( WS2812 = IO0 NEOPIXEL = WS2812 NEOPIXEL_I2C_POWER = IO2 LED = IO13 BUTTON = IO38 ) // SPI pins const ( SPI_SCK_PIN = IO5 SPI_MOSI_PIN = IO19 SPI_MISO_PIN = IO21 SPI_SDO_PIN = SPI_MOSI_PIN SPI_SDI_PIN = SPI_MISO_PIN // Silk labels SCK = SPI_SCK_PIN MO = SPI_MOSI_PIN MI = SPI_MISO_PIN ) // I2C pins const ( I2C_SCL_PIN = IO20 I2C_SDA_PIN = IO22 // Silk labels SCL = I2C_SCL_PIN SDA = I2C_SDA_PIN ) // ADC pins const ( ADC1_0 = IO36 ADC1_1 = IO37 ADC1_2 = IO38 ADC1_3 = IO39 ADC1_4 = IO32 ADC1_5 = IO33 ADC1_6 = IO34 ADC1_7 = IO35 ADC2_0 = IO4 ADC2_1 = IO0 ADC2_2 = IO2 ADC2_3 = IO15 ADC2_4 = IO13 ADC2_5 = IO12 ADC2_6 = IO14 ADC2_7 = IO27 ADC2_8 = IO25 ADC2_9 = IO26 ) // UART pins const ( UART_TX_PIN = IO19 UART_RX_PIN = IO22 UART2_TX_PIN = IO8 UART2_RX_PIN = IO7 // Silk labels RX = UART2_RX_PIN TX = UART2_TX_PIN ) ================================================ FILE: src/machine/board_ae_rp2040.go ================================================ //go:build ae_rp2040 package machine // GPIO pins const ( GP0 Pin = GPIO0 GP1 Pin = GPIO1 GP2 Pin = GPIO2 GP3 Pin = GPIO3 GP4 Pin = GPIO4 GP5 Pin = GPIO5 GP6 Pin = GPIO6 GP7 Pin = GPIO7 GP8 Pin = GPIO8 GP9 Pin = GPIO9 GP10 Pin = GPIO10 GP11 Pin = GPIO11 GP12 Pin = GPIO12 GP13 Pin = GPIO13 GP14 Pin = GPIO14 GP15 Pin = GPIO15 GP16 Pin = GPIO16 GP17 Pin = GPIO17 GP18 Pin = GPIO18 GP19 Pin = GPIO19 GP20 Pin = GPIO20 GP21 Pin = GPIO21 GP22 Pin = GPIO22 GP26 Pin = GPIO26 GP27 Pin = GPIO27 GP28 Pin = GPIO28 GP29 Pin = GPIO29 // Onboard crystal oscillator frequency, in MHz. xoscFreq = 12 // MHz ) // I2C Default pins on Raspberry Pico. const ( I2C0_SDA_PIN = GP4 I2C0_SCL_PIN = GP5 I2C1_SDA_PIN = GP2 I2C1_SCL_PIN = GP3 ) // SPI default pins const ( // Default Serial Clock Bus 0 for SPI communications SPI0_SCK_PIN = GPIO18 // Default Serial Out Bus 0 for SPI communications SPI0_SDO_PIN = GPIO19 // Tx // Default Serial In Bus 0 for SPI communications SPI0_SDI_PIN = GPIO16 // Rx // Default Serial Clock Bus 1 for SPI communications SPI1_SCK_PIN = GPIO10 // Default Serial Out Bus 1 for SPI communications SPI1_SDO_PIN = GPIO11 // Tx // Default Serial In Bus 1 for SPI communications SPI1_SDI_PIN = GPIO12 // Rx ) // UART pins const ( UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART1_TX_PIN = GPIO8 UART1_RX_PIN = GPIO9 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) var DefaultUART = UART0 // USB identifiers const ( usb_STRING_PRODUCT = "AE-RP2040" usb_STRING_MANUFACTURER = "AKIZUKI DENSHI" ) var ( usb_VID uint16 = 0x2E8A usb_PID uint16 = 0x000A ) ================================================ FILE: src/machine/board_arduino.go ================================================ //go:build arduino package machine // Digital pins, marked as plain numbers on the board. const ( D0 = PD0 // RX D1 = PD1 // TX D2 = PD2 D3 = PD3 D4 = PD4 D5 = PD5 D6 = PD6 D7 = PD7 D8 = PB0 D9 = PB1 D10 = PB2 D11 = PB3 D12 = PB4 D13 = PB5 ) // LED on the Arduino const LED Pin = D13 // ADC on the Arduino const ( ADC0 Pin = PC0 ADC1 Pin = PC1 ADC2 Pin = PC2 ADC3 Pin = PC3 ADC4 Pin = PC4 // Used by TWI for SDA ADC5 Pin = PC5 // Used by TWI for SCL ) // UART pins const ( UART_TX_PIN Pin = PD1 UART_RX_PIN Pin = PD0 ) ================================================ FILE: src/machine/board_arduino_leonardo.go ================================================ //go:build arduino_leonardo package machine // Return the current CPU frequency in hertz. func CPUFrequency() uint32 { return 16000000 } // Digital pins, marked as plain numbers on the board. const ( D0 = PD2 // RX D1 = PD3 // TX D2 = PD1 D3 = PD0 D4 = PD4 D5 = PC6 D6 = PD7 D7 = PE6 D8 = PB4 D9 = PB5 D10 = PB6 D11 = PB7 D12 = PD6 D13 = PC7 ) // LED on the Arduino const LED Pin = D13 // ADC on the Arduino const ( ADC0 Pin = PF7 ADC1 Pin = PF6 ADC2 Pin = PF5 ADC3 Pin = PF4 ADC4 Pin = PF1 ADC5 Pin = PF0 ) ================================================ FILE: src/machine/board_arduino_mega1280.go ================================================ //go:build arduino_mega1280 package machine // Return the current CPU frequency in hertz. func CPUFrequency() uint32 { return 16000000 } const ( A0 Pin = PF0 A1 Pin = PF1 A2 Pin = PF2 A3 Pin = PF3 A4 Pin = PF4 A5 Pin = PF5 A6 Pin = PF6 A7 Pin = PF7 A8 Pin = PK0 A9 Pin = PK1 A10 Pin = PK2 A11 Pin = PK3 A12 Pin = PK4 A13 Pin = PK5 A14 Pin = PK6 A15 Pin = PK7 // Analog Input ADC0 Pin = PF0 ADC1 Pin = PF1 ADC2 Pin = PF2 ADC3 Pin = PF3 ADC4 Pin = PF4 ADC5 Pin = PF5 ADC6 Pin = PF6 ADC7 Pin = PF7 ADC8 Pin = PK0 ADC9 Pin = PK1 ADC10 Pin = PK2 ADC11 Pin = PK3 ADC12 Pin = PK4 ADC13 Pin = PK5 ADC14 Pin = PK6 ADC15 Pin = PK7 // Digital pins D0 Pin = PE0 D1 Pin = PE1 D2 Pin = PE4 D3 Pin = PE5 D4 Pin = PG5 D5 Pin = PE3 D6 Pin = PH3 D7 Pin = PH4 D8 Pin = PH5 D9 Pin = PH6 D10 Pin = PB4 D11 Pin = PB5 D12 Pin = PB6 D13 Pin = PB7 D14 Pin = PJ1 D15 Pin = PJ0 D16 Pin = PH1 D17 Pin = PH0 D18 Pin = PD3 D19 Pin = PD2 D20 Pin = PD1 D21 Pin = PD0 D22 Pin = PA0 D23 Pin = PA1 D24 Pin = PA2 D25 Pin = PA3 D26 Pin = PA4 D27 Pin = PA5 D28 Pin = PA6 D29 Pin = PA7 D30 Pin = PC7 D31 Pin = PC6 D32 Pin = PC5 D33 Pin = PC4 D34 Pin = PC3 D35 Pin = PC2 D36 Pin = PC1 D37 Pin = PC0 D38 Pin = PD7 D39 Pin = PG2 D40 Pin = PG1 D41 Pin = PG0 D42 Pin = PL7 D43 Pin = PL6 D44 Pin = PL5 D45 Pin = PL4 D46 Pin = PL3 D47 Pin = PL2 D48 Pin = PL1 D49 Pin = PL0 D50 Pin = PB3 D51 Pin = PB2 D52 Pin = PB1 D53 Pin = PB0 AREF Pin = NoPin LED Pin = PB7 ) ================================================ FILE: src/machine/board_arduino_mega2560.go ================================================ //go:build arduino_mega2560 package machine import ( "device/avr" "runtime/interrupt" ) // Return the current CPU frequency in hertz. func CPUFrequency() uint32 { return 16000000 } const ( A0 Pin = PF0 A1 Pin = PF1 A2 Pin = PF2 A3 Pin = PF3 A4 Pin = PF4 A5 Pin = PF5 A6 Pin = PF6 A7 Pin = PF7 A8 Pin = PK0 A9 Pin = PK1 A10 Pin = PK2 A11 Pin = PK3 A12 Pin = PK4 A13 Pin = PK5 A14 Pin = PK6 A15 Pin = PK7 // Analog Input ADC0 Pin = PF0 ADC1 Pin = PF1 ADC2 Pin = PF2 ADC3 Pin = PF3 ADC4 Pin = PF4 ADC5 Pin = PF5 ADC6 Pin = PF6 ADC7 Pin = PF7 ADC8 Pin = PK0 ADC9 Pin = PK1 ADC10 Pin = PK2 ADC11 Pin = PK3 ADC12 Pin = PK4 ADC13 Pin = PK5 ADC14 Pin = PK6 ADC15 Pin = PK7 // Digital pins D0 Pin = PE0 D1 Pin = PE1 D2 Pin = PE4 D3 Pin = PE5 D4 Pin = PG5 D5 Pin = PE3 D6 Pin = PH3 D7 Pin = PH4 D8 Pin = PH5 D9 Pin = PH6 D10 Pin = PB4 D11 Pin = PB5 D12 Pin = PB6 D13 Pin = PB7 D14 Pin = PJ1 // TX3 D15 Pin = PJ0 // RX3 D16 Pin = PH1 // TX2 D17 Pin = PH0 // RX2 D18 Pin = PD3 // TX1 D19 Pin = PD2 // RX1 D20 Pin = PD1 D21 Pin = PD0 D22 Pin = PA0 D23 Pin = PA1 D24 Pin = PA2 D25 Pin = PA3 D26 Pin = PA4 D27 Pin = PA5 D28 Pin = PA6 D29 Pin = PA7 D30 Pin = PC7 D31 Pin = PC6 D32 Pin = PC5 D33 Pin = PC4 D34 Pin = PC3 D35 Pin = PC2 D36 Pin = PC1 D37 Pin = PC0 D38 Pin = PD7 D39 Pin = PG2 D40 Pin = PG1 D41 Pin = PG0 D42 Pin = PL7 D43 Pin = PL6 D44 Pin = PL5 D45 Pin = PL4 D46 Pin = PL3 D47 Pin = PL2 D48 Pin = PL1 D49 Pin = PL0 D50 Pin = PB3 D51 Pin = PB2 D52 Pin = PB1 D53 Pin = PB0 AREF Pin = NoPin LED Pin = PB7 ) // UART pins const ( UART_TX_PIN Pin = UART0_TX_PIN UART_RX_PIN Pin = UART0_RX_PIN UART0_TX_PIN Pin = D1 UART0_RX_PIN Pin = D0 UART1_TX_PIN Pin = D18 UART1_RX_PIN Pin = D19 UART2_TX_PIN Pin = D16 UART2_RX_PIN Pin = D17 UART3_TX_PIN Pin = D14 UART3_RX_PIN Pin = D15 ) var ( UART1 = &_UART1 _UART1 = UART{ Buffer: NewRingBuffer(), dataReg: avr.UDR1, baudRegH: avr.UBRR1H, baudRegL: avr.UBRR1L, statusRegA: avr.UCSR1A, statusRegB: avr.UCSR1B, statusRegC: avr.UCSR1C, } UART2 = &_UART2 _UART2 = UART{ Buffer: NewRingBuffer(), dataReg: avr.UDR2, baudRegH: avr.UBRR2H, baudRegL: avr.UBRR2L, statusRegA: avr.UCSR2A, statusRegB: avr.UCSR2B, statusRegC: avr.UCSR2C, } UART3 = &_UART3 _UART3 = UART{ Buffer: NewRingBuffer(), dataReg: avr.UDR3, baudRegH: avr.UBRR3H, baudRegL: avr.UBRR3L, statusRegA: avr.UCSR3A, statusRegB: avr.UCSR3B, statusRegC: avr.UCSR3C, } ) func init() { interrupt.New(irq_USART1_RX, _UART1.handleInterrupt) interrupt.New(irq_USART2_RX, _UART2.handleInterrupt) interrupt.New(irq_USART3_RX, _UART3.handleInterrupt) } ================================================ FILE: src/machine/board_arduino_mkr1000.go ================================================ //go:build arduino_mkr1000 // This contains the pin mappings for the Arduino MKR1000 board. // // For more information, see: https://store.arduino.cc/usa/arduino-mkr1000-with-headers-mounted package machine // used to reset into bootloader const resetMagicValue = 0x07738135 // GPIO Pins const ( D0 Pin = PA22 // PWM available D1 Pin = PA23 // PWM available D2 Pin = PA10 // PWM available D3 Pin = PA11 // PWM available D4 Pin = PB10 // PWM available D5 Pin = PB11 // PWM available D6 Pin = PA20 // PWM available D7 Pin = PA21 // PWM available D8 Pin = PA16 // PWM available D9 Pin = PA17 D10 Pin = PA19 // PWM available D11 Pin = PA08 // SDA D12 Pin = PA09 // PWM available, SCL D13 Pin = PB23 // RX D14 Pin = PB22 // TX RX0 Pin = PB23 // UART2 RX TX1 Pin = PB22 // UART2 TX ) // Analog pins const ( A0 Pin = PA02 // ADC0/AIN[0] A1 Pin = PB02 // AIN[10] A2 Pin = PB03 // AIN[11] A3 Pin = PA04 // AIN[04] A4 Pin = PA05 // AIN[05] A5 Pin = PA06 // AIN[06] A6 Pin = PA07 // AIN[07] ) const ( LED = D6 ) // USBCDC pins const ( USBCDC_DM_PIN Pin = PA24 USBCDC_DP_PIN Pin = PA25 ) // UART1 pins const ( UART_TX_PIN Pin = PB22 UART_RX_PIN Pin = PB23 ) // I2C pins const ( SDA_PIN Pin = D11 // SDA SCL_PIN Pin = D12 // SCL ) // SPI pins const ( SPI0_SCK_PIN Pin = D9 // SCK: S1 SPI0_SDO_PIN Pin = D8 // SDO: S1 SPI0_SDI_PIN Pin = D10 // SDI: S1 ) // I2S pins const ( I2S_SCK_PIN Pin = PA10 I2S_SDO_PIN Pin = PA07 I2S_SDI_PIN = NoPin I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino MKR1000 ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Arduino MKR1000" usb_STRING_MANUFACTURER = "Arduino" ) var ( usb_VID uint16 = 0x2341 usb_PID uint16 = 0x804e ) ================================================ FILE: src/machine/board_arduino_mkrwifi1010.go ================================================ //go:build arduino_mkrwifi1010 // This contains the pin mappings for the Arduino MKR WiFi 1010 board. // // For more information, see: https://store.arduino.cc/usa/mkr-wifi-1010 package machine // used to reset into bootloader const resetMagicValue = 0x07738135 // GPIO Pins const ( D0 Pin = PA22 // PWM available D1 Pin = PA23 // PWM available D2 Pin = PA10 // PWM available D3 Pin = PA11 // PWM available D4 Pin = PB10 // PWM available D5 Pin = PB11 // PWM available D6 Pin = PA20 // PWM available D7 Pin = PA21 // PWM available D8 Pin = PA16 // PWM available D9 Pin = PA17 D10 Pin = PA19 // PWM available D11 Pin = PA08 // SDA D12 Pin = PA09 // PWM available, SCL D13 Pin = PB23 // RX D14 Pin = PB22 // TX RX0 Pin = PB23 // UART1 RX TX1 Pin = PB22 // UART1 TX ) // Analog pins const ( A0 Pin = PA02 // ADC0/AIN[0] A1 Pin = PB02 // AIN[10] A2 Pin = PB03 // AIN[11] A3 Pin = PA04 // AIN[04] A4 Pin = PA05 // AIN[05] A5 Pin = PA06 // AIN[06] A6 Pin = PA07 // AIN[07] ) const ( LED = D6 ) // USBCDC pins const ( USBCDC_DM_PIN Pin = PA24 USBCDC_DP_PIN Pin = PA25 ) // UART1 pins const ( UART_TX_PIN Pin = PB22 UART_RX_PIN Pin = PB23 ) // I2C pins const ( SDA_PIN Pin = D11 // SDA SCL_PIN Pin = D12 // SCL ) // SPI pins const ( SPI0_SCK_PIN Pin = D9 // SCK: S1 SPI0_SDO_PIN Pin = D8 // SDO: S1 SPI0_SDI_PIN Pin = D10 // SDI: S1 ) // I2S pins const ( I2S_SCK_PIN Pin = PA10 I2S_SDO_PIN Pin = PA07 I2S_SDI_PIN = NoPin I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino MKR WiFi 1010. ) // NINA-W102 Pins const ( NINA_SDO Pin = PA12 NINA_SDI Pin = PA13 NINA_CS Pin = PA14 NINA_SCK Pin = PA15 NINA_GPIO0 Pin = PA27 NINA_RESETN Pin = PB08 NINA_ACK Pin = PA28 NINA_TX Pin = PA22 NINA_RX Pin = PA23 ) // UART on the Arduino MKR WiFi 1010. var UART1 = &sercomUSART5 // I2C on the Arduino MKR WiFi 1010. var ( I2C0 = sercomI2CM2 ) // SPI on the Arduino MKR WiFi 1010. var ( SPI0 = sercomSPIM1 SPI1 = sercomSPIM4 NINA_SPI = SPI1 ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Arduino MKR WiFi 1010" usb_STRING_MANUFACTURER = "Arduino" ) var ( usb_VID uint16 = 0x2341 usb_PID uint16 = 0x8054 ) var ( DefaultUART = UART1 ) ================================================ FILE: src/machine/board_arduino_nano.go ================================================ //go:build arduino_nano package machine // Digital pins. const ( D0 = PD0 // RX0 D1 = PD1 // TX1 D2 = PD2 D3 = PD3 D4 = PD4 D5 = PD5 D6 = PD6 D7 = PD7 D8 = PB0 D9 = PB1 D10 = PB2 D11 = PB3 D12 = PB4 D13 = PB5 ) // LED on the Arduino const LED Pin = D13 // ADC on the Arduino const ( ADC0 Pin = PC0 ADC1 Pin = PC1 ADC2 Pin = PC2 ADC3 Pin = PC3 ADC4 Pin = PC4 // Used by TWI for SDA ADC5 Pin = PC5 // Used by TWI for SCL ) // UART pins const ( UART_TX_PIN Pin = PD1 UART_RX_PIN Pin = PD0 ) ================================================ FILE: src/machine/board_arduino_nano33.go ================================================ //go:build arduino_nano33 // This contains the pin mappings for the Arduino Nano33 IoT board. // // For more information, see: https://store.arduino.cc/nano-33-iot package machine // used to reset into bootloader const resetMagicValue = 0x07738135 // GPIO Pins const ( RX0 Pin = PB23 // UART2 RX TX1 Pin = PB22 // UART2 TX D2 Pin = PB10 // PWM available D3 Pin = PB11 // PWM available D4 Pin = PA07 D5 Pin = PA05 // PWM available D6 Pin = PA04 // PWM available D7 Pin = PA06 D8 Pin = PA18 D9 Pin = PA20 // PWM available D10 Pin = PA21 // PWM available D11 Pin = PA16 // PWM available D12 Pin = PA19 // PWM available D13 Pin = PA17 ) // Analog pins const ( A0 Pin = PA02 // ADC/AIN[0] A1 Pin = PB02 // ADC/AIN[10] A2 Pin = PA11 // ADC/AIN[19] A3 Pin = PA10 // ADC/AIN[18], A4 Pin = PB08 // ADC/AIN[2], SCL: SERCOM2/PAD[1] A5 Pin = PB09 // ADC/AIN[3], SDA: SERCOM2/PAD[1] A6 Pin = PA09 // ADC/AIN[17] A7 Pin = PB03 // ADC/AIN[11] ) const ( LED = D13 ) // USBCDC pins const ( USBCDC_DM_PIN Pin = PA24 USBCDC_DP_PIN Pin = PA25 ) // UART1 pins const ( UART_TX_PIN Pin = PA22 UART_RX_PIN Pin = PA23 ) // UART1 on the Arduino Nano 33 connects to the onboard NINA-W102 WiFi chip. var UART1 = &sercomUSART3 // UART2 on the Arduino Nano 33 connects to the normal TX/RX pins. var UART2 = &sercomUSART5 // UART_NINA on the Arduino Nano 33 connects to the NINA HCI. var UART_NINA = &sercomUSART2 // I2C pins const ( SDA_PIN Pin = A4 // SDA: SERCOM4/PAD[1] SCL_PIN Pin = A5 // SCL: SERCOM4/PAD[1] ) // I2C on the Arduino Nano 33. var ( I2C0 = sercomI2CM4 ) // SPI pins const ( SPI0_SCK_PIN Pin = D13 // SCK: SERCOM1/PAD[1] SPI0_SDO_PIN Pin = D11 // SDO: SERCOM1/PAD[0] SPI0_SDI_PIN Pin = D12 // SDI: SERCOM1/PAD[3] ) // SPI on the Arduino Nano 33. var SPI0 = sercomSPIM1 // SPI1 is connected to the NINA-W102 chip on the Arduino Nano 33. var ( SPI1 = sercomSPIM2 NINA_SPI = SPI1 ) // NINA-W102 Pins const ( NINA_SDO Pin = PA12 NINA_SDI Pin = PA13 NINA_CS Pin = PA14 NINA_SCK Pin = PA15 NINA_GPIO0 Pin = PA27 NINA_RESETN Pin = PA08 NINA_ACK Pin = PA28 NINA_TX Pin = PA12 NINA_RX Pin = PA13 NINA_RTS Pin = PA14 NINA_CTS Pin = PA15 ) // NINA-W102 settings const ( NINA_BAUDRATE = 912600 NINA_RESET_INVERTED = true NINA_SOFT_FLOWCONTROL = false ) // I2S pins const ( I2S_SCK_PIN Pin = PA10 I2S_SDO_PIN Pin = PA08 I2S_SDI_PIN = NoPin I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino Nano 33. ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Arduino NANO 33 IoT" usb_STRING_MANUFACTURER = "Arduino" ) var ( usb_VID uint16 = 0x2341 usb_PID uint16 = 0x8057 ) ================================================ FILE: src/machine/board_arduino_zero.go ================================================ //go:build sam && atsamd21 && arduino_zero package machine // used to reset into bootloader const resetMagicValue = 0x07738135 // GPIO Pins - Digital Low const ( D0 = PA11 // RX D1 = PA10 // TX D2 = PA14 D3 = PA09 // PWM available D4 = PA08 // PWM available D5 = PA15 // PWM available D6 = PA20 // PWM available D7 = PA21 ) // GPIO Pins - Digital High const ( D8 = PA06 // PWM available D9 = PA07 // PWM available D10 = PA18 // PWM available D11 = PA16 // PWM available D12 = PA19 // PWM available D13 = PA17 // PWM available ) // ADC pins const ( AREF Pin = PA03 ADC0 Pin = PA02 ADC1 Pin = PB08 ADC2 Pin = PB09 ADC3 Pin = PA04 ADC4 Pin = PA05 ADC5 Pin = PB02 ) // LEDs on the Arduino Zero const ( LED = LED1 LED1 Pin = D13 LED2 Pin = PA27 // TX LED LED3 Pin = PB03 // RX LED ) // SPI pins - EDBG connected const ( SPI0_SDO_PIN Pin = PA16 // MOSI: SERCOM1/PAD[0] SPI0_SDI_PIN Pin = PA19 // MISO: SERCOM1/PAD[2] SPI0_SCK_PIN Pin = PA17 // SCK: SERCOM1/PAD[3] ) // SPI pins (Legacy ICSP) const ( SPI1_SDO_PIN Pin = PB10 // MOSI: SERCOM4/PAD[2] - Pin 4 SPI1_SDI_PIN Pin = PA12 // MISO: SERCOM4/PAD[0] - Pin 1 SPI1_SCK_PIN Pin = PB11 // SCK: SERCOM4/PAD[3] - Pin 3 ) // I2C pins - EDBG connected const ( SDA_PIN Pin = PA22 // SDA: SERCOM3/PAD[0] - Pin 20 SCL_PIN Pin = PA23 // SCL: SERCOM3/PAD[1] - Pin 21 ) // I2S pins - might not be exposed const ( I2S_SCK_PIN Pin = PA10 I2S_SDO_PIN Pin = PA07 I2S_SDI_PIN = NoPin I2S_WS_PIN Pin = PA11 ) // UART0 pins - EDBG connected const ( UART_RX_PIN Pin = D0 UART_TX_PIN Pin = D1 ) // 'native' USB port pins const ( USBCDC_DM_PIN Pin = PA24 USBCDC_DP_PIN Pin = PA25 ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Arduino Zero" usb_STRING_MANUFACTURER = "Arduino LLC" usb_VID uint16 = 0x2341 usb_PID uint16 = 0x804d ) // 32.768 KHz Crystal const ( XIN32 Pin = PA00 XOUT32 Pin = PA01 ) ================================================ FILE: src/machine/board_atmega328p.go ================================================ //go:build (avr && atmega328p) || arduino || arduino_nano package machine // Return the current CPU frequency in hertz. func CPUFrequency() uint32 { return 16000000 } const ( // Note: start at port B because there is no port A. portB Pin = iota * 8 portC portD ) const ( PB0 = portB + 0 PB1 = portB + 1 // peripherals: Timer1 channel A PB2 = portB + 2 // peripherals: Timer1 channel B PB3 = portB + 3 // peripherals: Timer2 channel A PB4 = portB + 4 PB5 = portB + 5 PB6 = portB + 6 PB7 = portB + 7 PC0 = portC + 0 PC1 = portC + 1 PC2 = portC + 2 PC3 = portC + 3 PC4 = portC + 4 // peripherals: I2C0 SDA PC5 = portC + 5 // peripherals: I2C0 SCL PC6 = portC + 6 PC7 = portC + 7 PD0 = portD + 0 PD1 = portD + 1 PD2 = portD + 2 PD3 = portD + 3 // peripherals: Timer2 channel B PD4 = portD + 4 PD5 = portD + 5 // peripherals: Timer0 channel B PD6 = portD + 6 // peripherals: Timer0 channel A PD7 = portD + 7 ) ================================================ FILE: src/machine/board_atmega328pb.go ================================================ //go:build avr && atmega328pb package machine // Return the current CPU frequency in hertz. func CPUFrequency() uint32 { return 16000000 } const ( // Note: start at port B because there is no port A. portB Pin = iota * 8 portC portD portE ) const ( PB0 = portB + 0 PB1 = portB + 1 // peripherals: Timer1 channel A PB2 = portB + 2 // peripherals: Timer1 channel B PB3 = portB + 3 // peripherals: Timer2 channel A PB4 = portB + 4 PB5 = portB + 5 PB6 = portB + 6 PB7 = portB + 7 PC0 = portC + 0 PC1 = portC + 1 PC2 = portC + 2 PC3 = portC + 3 PC4 = portC + 4 // peripherals: I2C0 SDA PC5 = portC + 5 // peripherals: I2C0 SCL PC6 = portC + 6 PC7 = portC + 7 PD0 = portD + 0 PD1 = portD + 1 PD2 = portD + 2 PD3 = portD + 3 // peripherals: Timer2 channel B PD4 = portD + 4 PD5 = portD + 5 // peripherals: Timer0 channel B PD6 = portD + 6 // peripherals: Timer0 channel A PD7 = portD + 7 PE0 = portE + 0 // peripherals: I2C1 SDA PE1 = portE + 1 // peripherals: I2C1 SCL PE2 = portE + 2 PE3 = portE + 3 PE4 = portE + 4 PE5 = portE + 5 PE6 = portE + 6 PE7 = portE + 7 ) ================================================ FILE: src/machine/board_atsamd21.go ================================================ //go:build (sam && atsamd21) || arduino_nano33 || circuitplay_express package machine // Return the current CPU frequency in hertz. func CPUFrequency() uint32 { return 48000000 } // Note that the below pins have a few I2C pins listed that according to the // datasheet don't support I2C yet they are used in practice on boards from // Adafruit and Arduino. See machine_atsamd21_simulator.go for details. // Hardware pins const ( PA00 Pin = 0 // peripherals: TCC2 channel 0, sercomI2CM1 SDA PA01 Pin = 1 // peripherals: TCC2 channel 1, sercomI2CM1 SCL PA02 Pin = 2 PA03 Pin = 3 PA04 Pin = 4 // peripherals: TCC0 channel 0 PA05 Pin = 5 // peripherals: TCC0 channel 1 PA06 Pin = 6 // peripherals: TCC1 channel 0 PA07 Pin = 7 // peripherals: TCC1 channel 1 PA08 Pin = 8 // peripherals: TCC0 channel 0, TCC1 channel 2, sercomI2CM0 SDA, sercomI2CM2 SDA PA09 Pin = 9 // peripherals: TCC0 channel 1, TCC1 channel 3, sercomI2CM0 SCL, sercomI2CM2 SCL PA10 Pin = 10 // peripherals: TCC1 channel 0, TCC0 channel 2 PA11 Pin = 11 // peripherals: TCC1 channel 1, TCC0 channel 3 PA12 Pin = 12 // peripherals: TCC2 channel 0, TCC0 channel 2, sercomI2CM2 SDA, sercomI2CM4 SDA PA13 Pin = 13 // peripherals: TCC2 channel 1, TCC0 channel 3, sercomI2CM2 SCL, sercomI2CM4 SCL PA14 Pin = 14 // peripherals: TCC0 channel 0 PA15 Pin = 15 // peripherals: TCC0 channel 1 PA16 Pin = 16 // peripherals: TCC2 channel 0, TCC0 channel 2, sercomI2CM1 SDA, sercomI2CM3 SDA PA17 Pin = 17 // peripherals: TCC2 channel 1, TCC0 channel 3, sercomI2CM1 SCL, sercomI2CM3 SCL PA18 Pin = 18 // peripherals: TCC0 channel 2 PA19 Pin = 19 // peripherals: TCC0 channel 3 PA20 Pin = 20 // peripherals: TCC0 channel 2 PA21 Pin = 21 // peripherals: TCC0 channel 3 PA22 Pin = 22 // peripherals: TCC0 channel 0, sercomI2CM3 SDA, sercomI2CM5 SDA PA23 Pin = 23 // peripherals: TCC0 channel 1, sercomI2CM3 SCL, sercomI2CM5 SCL PA24 Pin = 24 // peripherals: TCC1 channel 2 PA25 Pin = 25 // peripherals: TCC1 channel 3 PA26 Pin = 26 PA27 Pin = 27 PA28 Pin = 28 PA29 Pin = 29 PA30 Pin = 30 // peripherals: TCC1 channel 0 PA31 Pin = 31 // peripherals: TCC1 channel 1 PB00 Pin = 32 PB01 Pin = 33 PB02 Pin = 34 // peripherals: sercomI2CM5 SDA PB03 Pin = 35 // peripherals: sercomI2CM5 SCL PB04 Pin = 36 PB05 Pin = 37 PB06 Pin = 38 PB07 Pin = 39 PB08 Pin = 40 // peripherals: sercomI2CM4 SDA PB09 Pin = 41 // peripherals: sercomI2CM4 SCL PB10 Pin = 42 // peripherals: TCC0 channel 0 PB11 Pin = 43 // peripherals: TCC0 channel 1 PB12 Pin = 44 // peripherals: TCC0 channel 2, sercomI2CM4 SDA PB13 Pin = 45 // peripherals: TCC0 channel 3, sercomI2CM4 SCL PB14 Pin = 46 PB15 Pin = 47 PB16 Pin = 48 // peripherals: TCC0 channel 0, sercomI2CM5 SDA PB17 Pin = 49 // peripherals: TCC0 channel 1, sercomI2CM5 SCL PB18 Pin = 50 PB19 Pin = 51 PB20 Pin = 52 PB21 Pin = 53 PB22 Pin = 54 PB23 Pin = 55 PB24 Pin = 56 PB25 Pin = 57 PB26 Pin = 58 PB27 Pin = 59 PB28 Pin = 60 PB29 Pin = 61 PB30 Pin = 62 // peripherals: TCC0 channel 0, TCC1 channel 2, sercomI2CM5 SDA PB31 Pin = 63 // peripherals: TCC0 channel 1, TCC1 channel 3, sercomI2CM5 SCL ) ================================================ FILE: src/machine/board_atsame54-xpro.go ================================================ //go:build atsame54_xpro package machine import ( "device/sam" ) // Definition for compatibility, but not used const resetMagicValue = 0x00000000 const ( LED = PC18 BUTTON = PB31 ) const ( // https://ww1.microchip.com/downloads/en/DeviceDoc/70005321A.pdf // Extension Header EXT1 EXT1_PIN3_ADC_P = PB04 EXT1_PIN4_ADC_N = PB05 EXT1_PIN5_GPIO1 = PA06 EXT1_PIN6_GPIO2 = PA07 EXT1_PIN7_PWM_P = PB08 EXT1_PIN8_PWM_N = PB09 EXT1_PIN9_IRQ = PB07 EXT1_PIN9_GPIO = PB07 EXT1_PIN10_SPI_SS_B = PA27 EXT1_PIN10_GPIO = PA27 EXT1_PIN11_TWI_SDA = PA22 EXT1_PIN12_TWI_SCL = PA23 EXT1_PIN13_UART_RX = PA05 EXT1_PIN14_UART_TX = PA04 EXT1_PIN15_SPI_SS_A = PB28 EXT1_PIN16_SPI_SDO = PB27 EXT1_PIN17_SPI_SDI = PB29 EXT1_PIN18_SPI_SCK = PB26 // Extension Header EXT2 EXT2_PIN3_ADC_P = PB00 EXT2_PIN4_ADC_N = PA03 EXT2_PIN5_GPIO1 = PB01 EXT2_PIN6_GPIO2 = PB06 EXT2_PIN7_PWM_P = PB14 EXT2_PIN8_PWM_N = PB15 EXT2_PIN9_IRQ = PD00 EXT2_PIN9_GPIO = PD00 EXT2_PIN10_SPI_SS_B = PB02 EXT2_PIN10_GPIO = PB02 EXT2_PIN11_TWI_SDA = PD08 EXT2_PIN12_TWI_SCL = PD09 EXT2_PIN13_UART_RX = PB17 EXT2_PIN14_UART_TX = PB16 EXT2_PIN15_SPI_SS_A = PC06 EXT2_PIN16_SPI_SDO = PC04 EXT2_PIN17_SPI_SDI = PC07 EXT2_PIN18_SPI_SCK = PC05 // Extension Header EXT3 EXT3_PIN3_ADC_P = PC02 EXT3_PIN4_ADC_N = PC03 EXT3_PIN5_GPIO1 = PC01 EXT3_PIN6_GPIO2 = PC10 EXT3_PIN7_PWM_P = PD10 EXT3_PIN8_PWM_N = PD11 EXT3_PIN9_IRQ = PC30 EXT3_PIN9_GPIO = PC30 EXT3_PIN10_SPI_SS_B = PC31 EXT3_PIN10_GPIO = PC31 EXT3_PIN11_TWI_SDA = PD08 EXT3_PIN12_TWI_SCL = PD09 EXT3_PIN13_UART_RX = PC23 EXT3_PIN14_UART_TX = PC22 EXT3_PIN15_SPI_SS_A = PC14 EXT3_PIN16_SPI_SDO = PC04 EXT3_PIN17_SPI_SDI = PC07 EXT3_PIN18_SPI_SCK = PC05 // SD_CARD SD_CARD_MCDA0 = PB18 SD_CARD_MCDA1 = PB19 SD_CARD_MCDA2 = PB20 SD_CARD_MCDA3 = PB21 SD_CARD_MCCK = PA21 SD_CARD_MCCDA = PA20 SD_CARD_DETECT = PD20 SD_CARD_PROTECT = PD21 // I2C I2C_SDA = PD08 I2C_SCL = PD09 // CAN CAN0_TX = PA22 CAN0_RX = PA23 CAN1_STANDBY = PC13 CAN1_TX = PB12 CAN1_RX = PB13 CAN_STANDBY = CAN1_STANDBY CAN_TX = CAN1_TX CAN_RX = CAN1_RX // PDEC PDEC_PHASE_A = PC16 PDEC_PHASE_B = PC17 PDEC_INDEX = PC18 // PCC PCC_I2C_SDA = PD08 PCC_I2C_SCL = PD09 PCC_VSYNC_DEN1 = PA12 PCC_HSYNC_DEN2 = PA13 PCC_CLK = PA14 PCC_XCLK = PA15 PCC_DATA00 = PA16 PCC_DATA01 = PA17 PCC_DATA02 = PA18 PCC_DATA03 = PA19 PCC_DATA04 = PA20 PCC_DATA05 = PA21 PCC_DATA06 = PA22 PCC_DATA07 = PA23 PCC_DATA08 = PB14 PCC_DATA09 = PB15 PCC_RESET = PC12 PCC_PWDN = PC11 // Ethernet ETHERNET_TXCK = PA14 ETHERNET_TXEN = PA17 ETHERNET_TX0 = PA18 ETHERNET_TX1 = PA19 ETHERNET_RXER = PA15 ETHERNET_RX0 = PA13 ETHERNET_RX1 = PA12 ETHERNET_RXDV = PC20 ETHERNET_MDIO = PC12 ETHERNET_MDC = PC11 ETHERNET_INT = PD12 ETHERNET_RESET = PC21 PIN_QT_BUTTON = PA16 PIN_BTN0 = PB31 PIN_ETH_LED = PC15 PIN_LED0 = PC18 PIN_ADC_DAC = PA02 PIN_VBUS_DETECT = PC00 PIN_USB_ID = PC19 ) // USBCDC pins const ( USBCDC_DM_PIN = PA24 USBCDC_DP_PIN = PA25 ) // UART pins const ( // Extension Header EXT1 UART_TX_PIN = PA04 // TX : SERCOM0/PAD[0] UART_RX_PIN = PA05 // RX : SERCOM0/PAD[1] // Extension Header EXT2 UART2_TX_PIN = PB16 // TX : SERCOM5/PAD[0] UART2_RX_PIN = PB17 // RX : SERCOM5/PAD[1] // Extension Header EXT3 UART3_TX_PIN = PC22 // TX : SERCOM1/PAD[0] UART3_RX_PIN = PC23 // RX : SERCOM1/PAD[1] // Virtual COM Port UART4_TX_PIN = PB25 // TX : SERCOM2/PAD[0] UART4_RX_PIN = PB24 // RX : SERCOM2/PAD[1] ) // I2C pins const ( // Extension Header EXT1 SDA0_PIN = PA22 // SDA: SERCOM3/PAD[0] SCL0_PIN = PA23 // SCL: SERCOM3/PAD[1] // Extension Header EXT2 SDA1_PIN = PD08 // SDA: SERCOM7/PAD[0] SCL1_PIN = PD09 // SCL: SERCOM7/PAD[1] // Extension Header EXT3 SDA2_PIN = PD08 // SDA: SERCOM7/PAD[0] SCL2_PIN = PD09 // SCL: SERCOM7/PAD[1] // Data Gateway Interface SDA_DGI_PIN = PD08 // SDA: SERCOM7/PAD[0] SCL_DGI_PIN = PD09 // SCL: SERCOM7/PAD[1] SDA_PIN = SDA0_PIN SCL_PIN = SCL0_PIN ) // SPI pins const ( // Extension Header EXT1 SPI0_SCK_PIN = PB26 // SCK: SERCOM4/PAD[1] SPI0_SDO_PIN = PB27 // SDO: SERCOM4/PAD[0] SPI0_SDI_PIN = PB29 // SDI: SERCOM4/PAD[3] SPI0_SS_PIN = PB28 // SS : SERCOM4/PAD[2] // Extension Header EXT2 SPI1_SCK_PIN = PC05 // SCK: SERCOM6/PAD[1] SPI1_SDO_PIN = PC04 // SDO: SERCOM6/PAD[0] SPI1_SDI_PIN = PC07 // SDI: SERCOM6/PAD[3] SPI1_SS_PIN = PC06 // SS : SERCOM6/PAD[2] // Extension Header EXT3 SPI2_SCK_PIN = PC05 // SCK: SERCOM6/PAD[1] SPI2_SDO_PIN = PC04 // SDO: SERCOM6/PAD[0] SPI2_SDI_PIN = PC07 // SDI: SERCOM6/PAD[3] SPI2_SS_PIN = PC14 // SS : GPIO // Data Gateway Interface SPI_DGI_SCK_PIN = PC05 // SCK: SERCOM6/PAD[1] SPI_DGI_SDO_PIN = PC04 // SDO: SERCOM6/PAD[0] SPI_DGI_SDI_PIN = PC07 // SDI: SERCOM6/PAD[3] SPI_DGI_SS_PIN = PD01 // SS : GPIO ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "SAM E54 Xplained Pro" usb_STRING_MANUFACTURER = "Atmel" ) var ( usb_VID uint16 = 0x03EB usb_PID uint16 = 0x2404 ) // UART on the SAM E54 Xplained Pro var ( // Extension Header EXT1 UART1 = &sercomUSART0 // Extension Header EXT2 UART2 = &sercomUSART5 // Extension Header EXT3 UART3 = &sercomUSART1 // EDBG Virtual COM Port UART4 = &sercomUSART2 ) // I2C on the SAM E54 Xplained Pro var ( // Extension Header EXT1 I2C0 = sercomI2CM3 // Extension Header EXT2 I2C1 = sercomI2CM7 // Extension Header EXT3 I2C2 = sercomI2CM7 // Data Gateway Interface I2C3 = sercomI2CM7 ) // SPI on the SAM E54 Xplained Pro var ( // Extension Header EXT1 SPI0 = sercomSPIM4 // Extension Header EXT2 SPI1 = sercomSPIM6 // Extension Header EXT3 SPI2 = sercomSPIM6 // Data Gateway Interface SPI3 = sercomSPIM6 ) // CAN on the SAM E54 Xplained Pro var ( CAN0 = CAN{ Bus: sam.CAN0, } CAN1 = CAN{ Bus: sam.CAN1, } ) ================================================ FILE: src/machine/board_badger2040-w.go ================================================ //go:build badger2040_w // This contains the pin mappings for the Badger 2040 W board. // // For more information, see: https://shop.pimoroni.com/products/badger-2040-w // Also // - Badger 2040 W schematic: https://cdn.shopify.com/s/files/1/0174/1800/files/badger_w_schematic.pdf?v=1675859004 package machine const ( LED Pin = GPIO22 BUTTON_A Pin = GPIO12 BUTTON_B Pin = GPIO13 BUTTON_C Pin = GPIO14 BUTTON_UP Pin = GPIO15 BUTTON_DOWN Pin = GPIO11 BUTTON_USER Pin = NoPin // Not available on Badger 2040 W EPD_BUSY_PIN Pin = GPIO26 EPD_RESET_PIN Pin = GPIO21 EPD_DC_PIN Pin = GPIO20 EPD_CS_PIN Pin = GPIO17 EPD_SCK_PIN Pin = GPIO18 EPD_SDO_PIN Pin = GPIO19 VBUS_DETECT Pin = GPIO24 VREF_POWER Pin = GPIO27 VREF_1V24 Pin = GPIO28 VBAT_SENSE Pin = GPIO29 ENABLE_3V3 Pin = GPIO10 BATTERY = VBAT_SENSE RTC_ALARM = GPIO8 ) // I2C pins const ( I2C0_SDA_PIN Pin = GPIO4 I2C0_SCL_PIN Pin = GPIO5 I2C1_SDA_PIN Pin = NoPin I2C1_SCL_PIN Pin = NoPin ) // SPI pins. const ( SPI0_SCK_PIN Pin = GPIO18 SPI0_SDO_PIN Pin = GPIO19 SPI0_SDI_PIN Pin = GPIO16 SPI1_SCK_PIN Pin = NoPin SPI1_SDO_PIN Pin = NoPin SPI1_SDI_PIN Pin = NoPin ) // QSPI pins¿? const ( /* TODO SPI0_SD0_PIN Pin = QSPI_SD0 SPI0_SD1_PIN Pin = QSPI_SD1 SPI0_SD2_PIN Pin = QSPI_SD2 SPI0_SD3_PIN Pin = QSPI_SD3 SPI0_SCK_PIN Pin = QSPI_SCLKGPIO6 SPI0_CS_PIN Pin = QSPI_CS */ ) // Onboard crystal oscillator frequency, in MHz. const ( xoscFreq = 12 // MHz ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Badger 2040 W" usb_STRING_MANUFACTURER = "Pimoroni" ) var ( usb_VID uint16 = 0x2e8a usb_PID uint16 = 0x0003 ) // UART pins const ( UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) var DefaultUART = UART0 ================================================ FILE: src/machine/board_badger2040.go ================================================ //go:build badger2040 // This contains the pin mappings for the Badger 2040 board. // // For more information, see: https://shop.pimoroni.com/products/badger-2040 // Also // - Badger 2040 schematic: https://cdn.shopify.com/s/files/1/0174/1800/files/badger_2040_schematic.pdf?v=1645702148 package machine const ( LED Pin = GPIO25 BUTTON_A Pin = GPIO12 BUTTON_B Pin = GPIO13 BUTTON_C Pin = GPIO14 BUTTON_UP Pin = GPIO15 BUTTON_DOWN Pin = GPIO11 BUTTON_USER Pin = GPIO23 EPD_BUSY_PIN Pin = GPIO26 EPD_RESET_PIN Pin = GPIO21 EPD_DC_PIN Pin = GPIO20 EPD_CS_PIN Pin = GPIO17 EPD_SCK_PIN Pin = GPIO18 EPD_SDO_PIN Pin = GPIO19 VBUS_DETECT Pin = GPIO24 VREF_POWER Pin = GPIO27 VREF_1V24 Pin = GPIO28 VBAT_SENSE Pin = GPIO29 ENABLE_3V3 Pin = GPIO10 BATTERY = VBAT_SENSE ) // I2C pins const ( I2C0_SDA_PIN Pin = GPIO4 I2C0_SCL_PIN Pin = GPIO5 I2C1_SDA_PIN Pin = NoPin I2C1_SCL_PIN Pin = NoPin ) // SPI pins. const ( SPI0_SCK_PIN Pin = GPIO18 SPI0_SDO_PIN Pin = GPIO19 SPI0_SDI_PIN Pin = GPIO16 SPI1_SCK_PIN Pin = NoPin SPI1_SDO_PIN Pin = NoPin SPI1_SDI_PIN Pin = NoPin ) // QSPI pins¿? const ( /* TODO SPI0_SD0_PIN Pin = QSPI_SD0 SPI0_SD1_PIN Pin = QSPI_SD1 SPI0_SD2_PIN Pin = QSPI_SD2 SPI0_SD3_PIN Pin = QSPI_SD3 SPI0_SCK_PIN Pin = QSPI_SCLKGPIO6 SPI0_CS_PIN Pin = QSPI_CS */ ) // Onboard crystal oscillator frequency, in MHz. const ( xoscFreq = 12 // MHz ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Badger 2040" usb_STRING_MANUFACTURER = "Pimoroni" ) var ( usb_VID uint16 = 0x2e8a usb_PID uint16 = 0x0003 ) // UART pins const ( UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) var DefaultUART = UART0 ================================================ FILE: src/machine/board_bluemicro840.go ================================================ //go:build bluemicro840 package machine const HasLowFrequencyCrystal = true // GPIO Pins const ( D006 = P0_06 D008 = P0_08 D015 = P0_15 D017 = P0_17 D020 = P0_20 D013 = P0_13 D024 = P0_24 D009 = P0_09 D010 = P0_10 D106 = P1_06 D031 = P0_31 // AIN7; P0.31 (AIN7) is used to read the voltage of the battery via ADC. It can’t be used for any other function. D012 = P0_12 // VCC 3.3V; P0.12 on VCC shuts off the power to VCC when you set it to high; This saves on battery immensely for LEDs of all kinds that eat power even when off D030 = P0_30 D026 = P0_26 D029 = P0_29 D002 = P0_02 D113 = P1_13 D003 = P0_03 D028 = P0_28 D111 = P1_11 ) // Analog Pins const ( AIN0 = P0_02 AIN1 = P0_03 AIN2 = P0_04 // Not Connected AIN3 = P0_05 // Not Connected AIN4 = P0_28 AIN5 = P0_29 AIN6 = P0_30 AIN7 = P0_31 // Battery ) const ( LED1 Pin = P1_04 // Red LED LED2 Pin = P1_10 // Blue LED LED Pin = LED1 ) // UART0 pins (logical UART1) - Maps to same location as Pro Micro const ( UART_RX_PIN = P0_08 UART_TX_PIN = P0_06 ) // I2C pins const ( SDA_PIN = P0_15 // I2C0 external SCL_PIN = P0_17 // I2C0 external ) // SPI pins const ( SPI0_SCK_PIN = P1_13 // SCK SPI0_SDI_PIN = P0_03 // SDI SPI0_SDO_PIN = P0_28 // SDO ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "bluemicro840" usb_STRING_MANUFACTURER = "BlueMicro" ) var ( usb_VID uint16 = 0x1d50 usb_PID uint16 = 0x6161 ) ================================================ FILE: src/machine/board_bluepill.go ================================================ //go:build bluepill package machine import ( "device/stm32" "runtime/interrupt" ) // Pins printed on the silkscreen const ( C13 = PC13 C14 = PC14 C15 = PC15 A0 = PA0 A1 = PA1 A2 = PA2 A3 = PA3 A4 = PA4 A5 = PA5 A6 = PA6 A7 = PA7 B0 = PB0 B1 = PB1 B10 = PB10 B11 = PB11 B12 = PB12 B13 = PB13 B14 = PB14 B15 = PB15 A8 = PA8 A9 = PA9 A10 = PA10 A11 = PA11 A12 = PA12 A13 = PA13 A14 = PA14 A15 = PA15 B3 = PB3 B4 = PB4 B5 = PB5 B6 = PB6 B7 = PB7 B8 = PB8 B9 = PB9 ) // Analog Pins const ( ADC0 = PA0 ADC1 = PA1 ADC2 = PA2 ADC3 = PA3 ADC4 = PA4 ADC5 = PA5 ADC6 = PA6 ADC7 = PA7 ADC8 = PB0 ADC9 = PB1 ) const ( // This board does not have a user button, so // use first GPIO pin by default BUTTON = PA0 LED = PC13 ) var DefaultUART = UART1 // UART pins const ( UART_TX_PIN = PA9 UART_RX_PIN = PA10 UART_ALT_TX_PIN = PB6 UART_ALT_RX_PIN = PB7 ) var ( // USART1 is the first hardware serial port on the STM32. UART1 = &_UART1 _UART1 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART1, } UART2 = &_UART2 _UART2 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART2, } ) func init() { UART1.Interrupt = interrupt.New(stm32.IRQ_USART1, _UART1.handleInterrupt) UART2.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART2.handleInterrupt) } // SPI pins const ( SPI0_SCK_PIN = PA5 SPI0_SDO_PIN = PA7 SPI0_SDI_PIN = PA6 ) // I2C pins const ( I2C0_SDA_PIN = PB7 I2C0_SCL_PIN = PB6 ) ================================================ FILE: src/machine/board_btt_skr_pico.go ================================================ //go:build btt_skr_pico // This contains the pin mappings for the BigTreeTech SKR Pico. // // Purchase link: https://biqu.equipment/products/btt-skr-pico-v1-0 // Board schematic: https://github.com/bigtreetech/SKR-Pico/blob/master/Hardware/BTT%20SKR%20Pico%20V1.0-SCH.pdf // Pin diagram: https://github.com/bigtreetech/SKR-Pico/blob/master/Hardware/BTT%20SKR%20Pico%20V1.0-PIN.pdf package machine // TMC stepper driver motor direction. // X/Y/Z/E refers to motors for X/Y/Z and the extruder. const ( X_DIR = GPIO10 Y_DIR = GPIO5 Z_DIR = GPIO28 E_DIR = GPIO13 ) // TMC stepper driver motor step const ( X_STEP = GPIO11 Y_STEP = GPIO6 Z_STEP = GPIO19 E_STEP = GPIO14 ) // TMC stepper driver enable const ( X_ENABLE = GPIO12 Y_ENABLE = GPIO7 Z_ENABLE = GPIO2 E_ENABLE = GPIO15 ) // TMC stepper driver UART const ( TMC_UART_TX = UART1_TX_PIN TMC_UART_RX = UART1_RX_PIN ) // Endstops const ( X_ENDSTOP = GPIO4 Y_ENDSTOP = GPIO3 Z_ENDSTOP = GPIO25 E_ENDSTOP = GPIO16 ) // Fan PWM const ( FAN1_PWM = GPIO17 FAN2_PWM = GPIO18 FAN3_PWM = GPIO20 ) // Heater PWM const ( HEATER_BED_PWM = GPIO21 HEATER_EXTRUDER_PWM = GPIO23 ) // Thermistors const ( THERM_BED = GPIO26 // Bed heater THERM_EXTRUDER = GPIO27 // Toolhead heater ) // Misc const ( RGB = GPIO24 // Neopixel SERVO = GPIO29 // Servo PROBE = GPIO22 // Probe ) // Onboard crystal oscillator frequency, in MHz. const ( xoscFreq = 12 // MHz ) // I2C. We don't have this available const ( I2C0_SDA_PIN = NoPin I2C0_SCL_PIN = NoPin I2C1_SDA_PIN = NoPin I2C1_SCL_PIN = NoPin ) // SPI. We don't have this available const ( SPI0_SCK_PIN = NoPin SPI0_SDO_PIN = NoPin SPI0_SDI_PIN = NoPin SPI1_SCK_PIN = NoPin SPI1_SDO_PIN = NoPin SPI1_SDI_PIN = NoPin ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "SKR Pico" usb_STRING_MANUFACTURER = "BigTreeTech" ) var ( usb_VID uint16 = 0x2e8a usb_PID uint16 = 0x0003 ) // UART pins const ( UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART1_TX_PIN = GPIO8 UART1_RX_PIN = GPIO9 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) var DefaultUART = UART0 ================================================ FILE: src/machine/board_challenger_rp2040.go ================================================ //go:build challenger_rp2040 package machine const ( LED = GPIO24 // Onboard crystal oscillator frequency, in MHz. xoscFreq = 12 // MHz ) // GPIO Pins const ( D5 = GPIO2 D6 = GPIO3 D9 = GPIO4 D10 = GPIO5 D11 = GPIO6 D12 = GPIO7 D13 = GPIO8 ) // Analog pins const ( A0 = ADC0 A1 = ADC1 A2 = ADC2 A3 = ADC3 ) // I2C Pins. const ( I2C0_SDA_PIN = GPIO24 I2C0_SCL_PIN = GPIO25 I2C1_SDA_PIN = GPIO2 I2C1_SCL_PIN = GPIO3 SDA_PIN = I2C1_SDA_PIN SCL_PIN = I2C1_SCL_PIN ) // SPI default pins const ( // Default Serial Clock Bus 0 for SPI communications SPI0_SCK_PIN = GPIO22 // Default Serial Out Bus 0 for SPI communications SPI0_SDO_PIN = GPIO23 // Tx // Default Serial In Bus 0 for SPI communications SPI0_SDI_PIN = GPIO20 // Rx // Default Serial Clock Bus 1 for SPI communications SPI1_SCK_PIN = GPIO10 // Default Serial Out Bus 1 for SPI communications SPI1_SDO_PIN = GPIO11 // Tx // Default Serial In Bus 1 for SPI communications SPI1_SDI_PIN = GPIO12 // Rx ) // LoRa default pins const ( LORA_CS = GPIO9 LORA_SCK = GPIO10 LORA_SDO = GPIO11 LORA_SDI = GPIO12 LORA_RESET = GPIO13 LORA_DIO0 = GPIO14 LORA_DIO1 = GPIO15 LORA_DIO2 = GPIO18 ) // UART pins const ( UART0_TX_PIN = GPIO16 UART0_RX_PIN = GPIO17 UART1_TX_PIN = GPIO4 UART1_RX_PIN = GPIO5 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) var DefaultUART = UART0 // USB identifiers const ( usb_STRING_PRODUCT = "Challenger 2040 LoRa" usb_STRING_MANUFACTURER = "iLabs" ) var ( usb_VID uint16 = 0x2e8a usb_PID uint16 = 0x1023 ) ================================================ FILE: src/machine/board_circuitplay_bluefruit.go ================================================ //go:build circuitplay_bluefruit package machine const HasLowFrequencyCrystal = false // GPIO Pins const ( D0 = P0_30 D1 = P0_14 D2 = P0_05 D3 = P0_04 D4 = P1_02 D5 = P1_15 D6 = P0_02 D7 = P1_06 D8 = P0_13 D9 = P0_29 D10 = P0_03 D11 = P1_04 D12 = P0_26 D13 = P1_14 ) // Analog Pins const ( A1 = P0_02 A2 = P0_29 A3 = P0_03 A4 = P0_04 A5 = P0_05 A6 = P0_30 A7 = P0_14 A8 = P0_28 A9 = P0_31 ) const ( LED = D13 NEOPIXELS = D8 WS2812 = D8 BUTTONA = D4 BUTTONB = D5 SLIDER = D7 // built-in slide switch BUTTON = BUTTONA BUTTON1 = BUTTONB LIGHTSENSOR = A8 TEMPSENSOR = A9 ) // UART0 pins (logical UART1) const ( UART_TX_PIN = P0_14 // PORTB UART_RX_PIN = P0_30 // PORTB ) // I2C pins const ( SDA_PIN = P0_05 // I2C0 external SCL_PIN = P0_04 // I2C0 external SDA1_PIN = P1_10 // I2C1 internal SCL1_PIN = P1_12 // I2C1 internal ) // SPI pins (internal flash) const ( SPI0_SCK_PIN = P0_19 // SCK SPI0_SDO_PIN = P0_21 // SDO SPI0_SDI_PIN = P0_23 // SDI ) // PDM pins const ( PDM_CLK_PIN = P0_17 // CLK PDM_DIN_PIN = P0_16 // DIN ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Adafruit Circuit Playground Bluefruit" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x8045 ) var ( DefaultUART = UART0 ) ================================================ FILE: src/machine/board_circuitplay_express.go ================================================ //go:build circuitplay_express package machine // used to reset into bootloader const resetMagicValue = 0xf01669ef // GPIO Pins const ( D0 = PB09 D1 = PB08 D2 = PB02 D3 = PB03 D4 = PA28 D5 = PA14 D6 = PA05 D7 = PA15 D8 = PB23 D9 = PA06 D10 = PA07 D11 = NoPin // does not seem to exist D12 = PA02 D13 = PA17 // PWM available ) // Analog Pins const ( A0 = PA02 // ADC/AIN[0] A1 = PA05 // PWM available, also ADC/AIN[5] A2 = PA06 // PWM available, also ADC/AIN[6] A3 = PA07 // PWM available, also ADC/AIN[7] A4 = PB03 // PORTB A5 = PB02 // PORTB A6 = PB09 // PORTB A7 = PB08 // PORTB A8 = PA11 // ADC/AIN[19] A9 = PA09 // ADC/AIN[17] A10 = PA04 ) const ( LED = D13 NEOPIXELS = D8 WS2812 = D8 BUTTONA = D4 BUTTONB = D5 SLIDER = D7 // built-in slide switch BUTTON = BUTTONA BUTTON1 = BUTTONB LIGHTSENSOR = A8 TEMPSENSOR = A9 PROXIMITY = A10 ) // USBCDC pins (logical UART0) const ( USBCDC_DM_PIN = PA24 USBCDC_DP_PIN = PA25 ) // UART0 pins (logical UART1) const ( UART_TX_PIN = PB08 // PORTB UART_RX_PIN = PB09 // PORTB ) // UART1 on the Circuit Playground Express. var ( UART1 = &sercomUSART4 DefaultUART = UART1 ) // I2C pins const ( SDA_PIN = PB02 // I2C0 external SCL_PIN = PB03 // I2C0 external SDA1_PIN = PA00 // I2C1 internal SCL1_PIN = PA01 // I2C1 internal ) // I2C on the Circuit Playground Express. var ( I2C0 = sercomI2CM5 // external device I2C1 = sercomI2CM1 // internal device ) // SPI pins (internal flash) const ( SPI0_SCK_PIN = PA21 // SCK: SERCOM3/PAD[3] SPI0_SDO_PIN = PA20 // SDO: SERCOM3/PAD[2] SPI0_SDI_PIN = PA16 // SDI: SERCOM3/PAD[0] ) // SPI on the Circuit Playground Express. var SPI0 = sercomSPIM3 // I2S pins const ( I2S_SCK_PIN = PA10 I2S_SDO_PIN = PA08 I2S_SDI_PIN = NoPin I2S_WS_PIN = NoPin // no WS, instead uses SCK to sync ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Adafruit Circuit Playground Express" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x8018 ) ================================================ FILE: src/machine/board_clue_alpha.go ================================================ //go:build clue_alpha package machine const HasLowFrequencyCrystal = false // GPIO Pins const ( D0 = P0_04 D1 = P0_05 D2 = P0_03 D3 = P0_28 D4 = P0_02 D5 = P1_02 D6 = P1_09 D7 = P0_07 D8 = P1_07 D9 = P0_27 D10 = P0_30 D11 = P1_10 D12 = P0_31 D13 = P0_08 D14 = P0_06 D15 = P0_26 D16 = P0_29 D17 = P1_01 D18 = P0_16 D19 = P0_25 D20 = P0_24 D29 = P0_14 D30 = P0_15 D31 = P0_12 D32 = P0_13 D33 = P1_03 D34 = P1_05 D35 = P0_00 D36 = P0_01 D37 = P0_19 D38 = P0_20 D39 = P0_17 D40 = P0_22 D41 = P0_23 D42 = P0_21 D43 = P0_10 D44 = P0_09 D45 = P1_06 D46 = P1_00 ) // Analog Pins const ( A0 = D12 A1 = D16 A2 = D0 A3 = D1 A4 = D2 A5 = D3 A6 = D4 A7 = D10 ) const ( LED = D17 LED1 = LED LED2 = D43 NEOPIXEL = D18 WS2812 = D18 BUTTON_LEFT = D5 BUTTON_RIGHT = D11 // 240x240 ST7789 display is connected to these pins (use RowOffset = 80) TFT_SCK = D29 TFT_SDO = D30 TFT_CS = D31 TFT_DC = D32 TFT_RESET = D33 TFT_LITE = D34 PDM_DAT = D35 PDM_CLK = D36 QSPI_SCK = D37 QSPI_CS = D38 QSPI_DATA0 = D39 QSPI_DATA1 = D40 QSPI_DATA2 = D41 QSPI_DATA3 = D42 SPEAKER = D46 ) // UART0 pins (logical UART1) const ( UART_RX_PIN = D0 UART_TX_PIN = D1 ) // I2C pins const ( SDA_PIN = D20 // I2C0 external SCL_PIN = D19 // I2C0 external ) // SPI pins const ( SPI0_SCK_PIN = D13 // SCK SPI0_SDO_PIN = D15 // SDO SPI0_SDI_PIN = D14 // SDI ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Adafruit CLUE" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x8072 ) var ( DefaultUART = UART0 ) ================================================ FILE: src/machine/board_digispark.go ================================================ //go:build digispark package machine // Return the current CPU frequency in hertz. func CPUFrequency() uint32 { return 16000000 } const ( P0 Pin = PB0 P1 Pin = PB1 P2 Pin = PB2 P3 Pin = PB3 P4 Pin = PB4 P5 Pin = PB5 LED = P1 ) ================================================ FILE: src/machine/board_elecrow-rp2040-w5.go ================================================ //go:build elecrow_rp2040 // This file contains the pin mappings for the Elecrow Pico rp2040 W5 boards. // // Elecrow Pico rp2040 W5 is a microcontroller using the Raspberry Pi RP2040 // chip and rtl8720d Wifi chip. // // - https://www.elecrow.com/wiki/PICO_W5_RP2040_Dev_Board.html package machine // GPIO pins const ( GP0 Pin = GPIO0 GP1 Pin = GPIO1 GP2 Pin = GPIO2 GP3 Pin = GPIO3 GP4 Pin = GPIO4 GP5 Pin = GPIO5 GP6 Pin = GPIO6 GP7 Pin = GPIO7 GP8 Pin = GPIO8 GP9 Pin = GPIO9 GP10 Pin = GPIO10 GP11 Pin = GPIO11 GP12 Pin = GPIO12 GP13 Pin = GPIO13 GP14 Pin = GPIO14 GP15 Pin = GPIO15 GP16 Pin = GPIO16 GP17 Pin = GPIO17 GP18 Pin = GPIO18 GP19 Pin = GPIO19 GP20 Pin = GPIO20 GP21 Pin = GPIO21 GP22 Pin = GPIO22 GP26 Pin = GPIO26 GP27 Pin = GPIO27 GP28 Pin = GPIO28 // Onboard LED LED Pin = GPIO25 // Onboard crystal oscillator frequency, in MHz. xoscFreq = 12 // MHz ) // I2C Default pins on Raspberry Pico. const ( I2C0_SDA_PIN = GP4 I2C0_SCL_PIN = GP5 I2C1_SDA_PIN = GP2 I2C1_SCL_PIN = GP3 ) // SPI default pins const ( // Default Serial Clock Bus 0 for SPI communications SPI0_SCK_PIN = GPIO18 // Default Serial Out Bus 0 for SPI communications SPI0_SDO_PIN = GPIO19 // Tx // Default Serial In Bus 0 for SPI communications SPI0_SDI_PIN = GPIO16 // Rx // Default Serial Clock Bus 1 for SPI communications SPI1_SCK_PIN = GPIO10 // Default Serial Out Bus 1 for SPI communications SPI1_SDO_PIN = GPIO11 // Tx // Default Serial In Bus 1 for SPI communications SPI1_SDI_PIN = GPIO12 // Rx ) // UART pins const ( UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART1_TX_PIN = GPIO4 // Wired to rtl8720d UART1_Tx UART1_RX_PIN = GPIO5 // Wired to rtl8720n UART1_Rx UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) var DefaultUART = UART0 // USB identifiers const ( usb_STRING_PRODUCT = "Pico" usb_STRING_MANUFACTURER = "Raspberry Pi" ) var ( usb_VID uint16 = 0x2E8A usb_PID uint16 = 0x000A ) ================================================ FILE: src/machine/board_elecrow-rp2350-w5.go ================================================ //go:build elecrow_rp2350 // This file contains the pin mappings for the Elecrow Pico rp2350 W5 boards. // // Elecrow Pico rp2350 W5 is a microcontroller using the Raspberry Pi RP2350 // chip and rtl8720d Wifi chip. // // - https://www.elecrow.com/pico-w5-microcontroller-development-boards-rp2350-microcontroller-board.html package machine // GPIO pins const ( GP0 Pin = GPIO0 GP1 Pin = GPIO1 GP2 Pin = GPIO2 GP3 Pin = GPIO3 GP4 Pin = GPIO4 GP5 Pin = GPIO5 GP6 Pin = GPIO6 GP7 Pin = GPIO7 GP8 Pin = GPIO8 GP9 Pin = GPIO9 GP10 Pin = GPIO10 GP11 Pin = GPIO11 GP12 Pin = GPIO12 GP13 Pin = GPIO13 GP14 Pin = GPIO14 GP15 Pin = GPIO15 GP16 Pin = GPIO16 GP17 Pin = GPIO17 GP18 Pin = GPIO18 GP19 Pin = GPIO19 GP20 Pin = GPIO20 GP21 Pin = GPIO21 GP22 Pin = GPIO22 GP26 Pin = GPIO26 GP27 Pin = GPIO27 GP28 Pin = GPIO28 // Onboard LED LED Pin = GPIO25 // Onboard crystal oscillator frequency, in MHz. xoscFreq = 12 // MHz ) // I2C Default pins on Raspberry Pico. const ( I2C0_SDA_PIN = GP4 I2C0_SCL_PIN = GP5 I2C1_SDA_PIN = GP2 I2C1_SCL_PIN = GP3 ) // SPI default pins const ( // Default Serial Clock Bus 0 for SPI communications SPI0_SCK_PIN = GPIO18 // Default Serial Out Bus 0 for SPI communications SPI0_SDO_PIN = GPIO19 // Tx // Default Serial In Bus 0 for SPI communications SPI0_SDI_PIN = GPIO16 // Rx // Default Serial Clock Bus 1 for SPI communications SPI1_SCK_PIN = GPIO10 // Default Serial Out Bus 1 for SPI communications SPI1_SDO_PIN = GPIO11 // Tx // Default Serial In Bus 1 for SPI communications SPI1_SDI_PIN = GPIO12 // Rx ) // UART pins const ( UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART1_TX_PIN = GPIO4 // Wired to rtl8720d UART1_Tx UART1_RX_PIN = GPIO5 // Wired to rtl8720n UART1_Rx UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) var DefaultUART = UART0 // USB identifiers const ( usb_STRING_PRODUCT = "Pico2" usb_STRING_MANUFACTURER = "Raspberry Pi" ) var ( usb_VID uint16 = 0x2E8A usb_PID uint16 = 0x000F ) ================================================ FILE: src/machine/board_esp-c3-32s-kit.go ================================================ //go:build esp_c3_32s_kit package machine // See: // * https://www.waveshare.com/w/upload/8/8f/Esp32-c3s_specification.pdf // * https://www.waveshare.com/w/upload/4/46/Nodemcu-esp-c3-32s-kit-schematics.pdf // Digital Pins const ( IO0 = GPIO0 IO1 = GPIO1 IO2 = GPIO2 IO3 = GPIO3 IO4 = GPIO4 IO5 = GPIO5 IO6 = GPIO6 IO7 = GPIO7 IO8 = GPIO8 IO9 = GPIO9 IO18 = GPIO18 IO19 = GPIO19 ) const ( LED_RED = IO3 LED_GREEN = IO4 LED_BLUE = IO5 LED = LED_RED LED1 = LED_RED LED2 = LED_GREEN ) // I2C pins const ( SDA_PIN = NoPin SCL_PIN = NoPin ) ================================================ FILE: src/machine/board_esp32-c3-devkit-rust-1.go ================================================ //go:build esp32_c3_devkit_rust_1 // This file contains the pin mappings for the Espressif ESP32-C3 Development Board for Rust. // // The Espressif ESP32-C3-DevKit-RUST-1 development board is powered // by the Espressif ESP32-C3 SoC featuring an open-source RISC-V architecture. // // Specifications: // SoC: ESP32-C3-MINI-1, 4MB Flash, RISCV-32bit, 160MHz, 400KB SRAM // Wireless: WiFi & Bluetooth 5.0 (BLE) // ICM-42670-P 6-Axis IMU (I2C Addr 0x68) // SHTC3 Humidity and Temperature Sensor (I2C Addr 0x70) // WS2812B LED // GitHub: https://github.com/esp-rs/esp-rust-board // Schematic: https://github.com/esp-rs/esp-rust-board/blob/master/hardware/esp-rust-board/schematic/esp-rust-board.pdf // Datasheet: https://www.espressif.com/sites/default/files/documentation/esp32-c3_datasheet_en.pdf package machine // Digital pins const ( // Pin // Function // ----- // --------------- D0 = GPIO0 // D1 = GPIO1 // D2 = GPIO2 // WS2812 D3 = GPIO3 // D4 = GPIO4 // MTMS D5 = GPIO5 // MTDI D6 = GPIO6 // MTCK D7 = GPIO7 // Red LED / MTDO D8 = GPIO8 // I2C SCL D9 = GPIO9 // Boot Button D10 = GPIO10 // I2C SDA D18 = GPIO18 // USB DM D19 = GPIO19 // USB DP D20 = GPIO20 // UART RX D21 = GPIO21 // UART TX ) // Analog pins const ( A0 = GPIO0 A1 = GPIO1 A2 = GPIO2 A3 = GPIO3 A4 = GPIO4 A5 = GPIO5 ) // Button pin const ( BUTTON = BUTTON_BOOT BUTTON_BOOT = D9 ) // LED pins const ( LED = LED_BUILTIN WS2812 = D2 LED_BUILTIN = D7 ) // I2C pins const ( SCL_PIN = D8 SDA_PIN = D10 ) // USBCDC pins const ( USBCDC_DM_PIN = D18 USBCDC_DP_PIN = D19 ) // UART pins const ( UART_RX_PIN = D20 UART_TX_PIN = D21 ) ================================================ FILE: src/machine/board_esp32-coreboard-v2.go ================================================ //go:build esp32_coreboard_v2 package machine const ( CLK = GPIO6 CMD = GPIO11 IO0 = GPIO0 IO1 = GPIO1 IO2 = GPIO2 IO3 = GPIO3 IO4 = GPIO4 IO5 = GPIO5 IO9 = GPIO9 IO10 = GPIO10 IO16 = GPIO16 IO17 = GPIO17 IO18 = GPIO18 IO19 = GPIO19 IO21 = GPIO21 IO22 = GPIO22 IO23 = GPIO23 IO25 = GPIO25 IO26 = GPIO26 IO27 = GPIO27 IO32 = GPIO32 IO33 = GPIO33 IO34 = GPIO34 IO35 = GPIO35 IO36 = GPIO36 IO39 = GPIO39 RXD = GPIO3 SD0 = GPIO7 SD1 = GPIO8 SD2 = GPIO9 SD3 = GPIO10 SVN = GPIO39 SVP = GPIO36 TCK = GPIO13 TD0 = GPIO15 TDI = GPIO12 TMS = GPIO14 TXD = GPIO1 ) // Built-in LED on some ESP32 boards. const LED = IO2 // SPI pins const ( SPI0_SCK_PIN = IO18 SPI0_SDO_PIN = IO23 SPI0_SDI_PIN = IO19 SPI0_CS0_PIN = IO5 ) // I2C pins const ( SDA_PIN = IO21 SCL_PIN = IO22 ) // ADC pins const ( ADC0 Pin = IO34 ADC1 Pin = IO35 ADC2 Pin = IO36 ADC3 Pin = IO39 ) // UART0 pins const ( UART_TX_PIN = IO1 UART_RX_PIN = IO3 ) // UART1 pins const ( UART1_TX_PIN = IO9 UART1_RX_PIN = IO10 ) // PWM pins const ( PWM0_PIN Pin = IO2 PWM1_PIN Pin = IO0 PWM2_PIN Pin = IO4 ) ================================================ FILE: src/machine/board_esp32c3-12f.go ================================================ //go:build esp32c312f package machine // Built-in RGB LED const ( LED_RED = IO3 LED_GREEN = IO4 LED_BLUE = IO5 LED = LED_RED ) const ( IO0 = GPIO0 IO1 = GPIO1 IO2 = GPIO2 IO3 = GPIO3 IO4 = GPIO4 IO5 = GPIO5 IO6 = GPIO6 IO7 = GPIO7 IO8 = GPIO8 IO9 = GPIO9 IO10 = GPIO10 IO18 = GPIO18 IO19 = GPIO19 RXD = GPIO20 TXD = GPIO21 ) // ADC pins const ( ADC0 Pin = ADC1_0 ADC1 Pin = ADC2_0 ADC1_0 Pin = IO0 ADC1_1 Pin = IO1 ADC1_2 Pin = IO2 ADC1_3 Pin = IO3 ADC1_4 Pin = IO4 ADC2_0 Pin = IO5 ) // UART0 pins const ( UART_TX_PIN = TXD UART_RX_PIN = RXD ) // I2C pins const ( SCL_PIN = NoPin SDA_PIN = NoPin ) ================================================ FILE: src/machine/board_esp32c3-supermini.go ================================================ //go:build esp32c3_supermini // This file contains the pin mappings for the ESP32 supermini boards. // // - https://web.archive.org/web/20240805232453/https://dl.artronshop.co.th/ESP32-C3%20SuperMini%20datasheet.pdf package machine // Digital Pins const ( IO0 = GPIO0 IO1 = GPIO1 IO2 = GPIO2 IO3 = GPIO3 IO4 = GPIO4 IO5 = GPIO5 IO6 = GPIO6 IO7 = GPIO7 IO8 = GPIO8 IO9 = GPIO9 IO10 = GPIO10 IO20 = GPIO20 IO21 = GPIO21 ) // Built-in LED const LED = GPIO8 // Analog pins const ( A0 = GPIO0 A1 = GPIO1 A2 = GPIO2 A3 = GPIO3 A4 = GPIO4 A5 = GPIO5 ) // UART pins const ( UART_RX_PIN = GPIO20 UART_TX_PIN = GPIO21 ) // I2C pins const ( SDA_PIN = GPIO8 SCL_PIN = GPIO9 ) // SPI pins const ( SPI_MISO_PIN = GPIO5 SPI_MOSI_PIN = GPIO6 SPI_SS_PIN = GPIO7 SPI_SCK_PIN = GPIO4 ) ================================================ FILE: src/machine/board_fe310.go ================================================ //go:build hifive1b package machine const ( P00 Pin = 0 P01 Pin = 1 P02 Pin = 2 P03 Pin = 3 P04 Pin = 4 P05 Pin = 5 P06 Pin = 6 P07 Pin = 7 P08 Pin = 8 P09 Pin = 9 P10 Pin = 10 P11 Pin = 11 P12 Pin = 12 // peripherals: I2C0 SDA P13 Pin = 13 // peripherals: I2C0 SCL P14 Pin = 14 P15 Pin = 15 P16 Pin = 16 P17 Pin = 17 P18 Pin = 18 P19 Pin = 19 P20 Pin = 20 P21 Pin = 21 P22 Pin = 22 P23 Pin = 23 P24 Pin = 24 P25 Pin = 25 P26 Pin = 26 P27 Pin = 27 P28 Pin = 28 P29 Pin = 29 P30 Pin = 30 P31 Pin = 31 ) ================================================ FILE: src/machine/board_feather-m0-express.go ================================================ //go:build sam && atsamd21 && feather_m0_express package machine // used to reset into bootloader const resetMagicValue = 0xf01669ef // GPIO Pins const ( D0 = PA11 // UART0 RX D1 = PA10 // UART0 TX D2 = NoPin // does not seem to exist D3 = NoPin // does not seem to exist D4 = NoPin // does not seem to exist D5 = PA15 D6 = PA20 D7 = NoPin // does not seem to exist D8 = PA06 // NEOPIXEL D9 = PA07 D10 = PA18 D11 = PA16 D12 = PA19 D13 = PA17 ) // Analog pins const ( A0 = PA02 // ADC/AIN[0] A1 = PB08 // ADC/AIN[2] A2 = PB09 // ADC/AIN[3] A3 = PA04 // ADC/AIN[4] A4 = PA05 // ADC/AIN[5] A5 = PB02 // ADC/AIN[10] ) const ( LED = D13 NEOPIXEL = D8 ) // USBCDC pins const ( USBCDC_DM_PIN = PA24 USBCDC_DP_PIN = PA25 ) // UART1 pins const ( UART_TX_PIN = D1 UART_RX_PIN = D0 UART1_TX_PIN = D10 UART1_RX_PIN = D12 ) // UART0 on the Feather M0 Express. var UART0 = &sercomUSART0 var UART1 = &sercomUSART1 // I2C pins const ( SDA_PIN = PA22 // SDA: SERCOM3/PAD[0] SCL_PIN = PA23 // SCL: SERCOM3/PAD[1] ) // I2C on the Feather M0 Express. var ( I2C0 = sercomI2CM3 ) // SPI pins const ( SPI0_SCK_PIN = PB11 // SCK: SERCOM4/PAD[3] SPI0_SDO_PIN = PB10 // SDO: SERCOM4/PAD[2] SPI0_SDI_PIN = PA12 // SDI: SERCOM4/PAD[0] ) // SPI on the Feather M0. var SPI0 = sercomSPIM4 // I2S pins const ( I2S_SCK_PIN = PA10 I2S_SDO_PIN = PA07 I2S_SDI_PIN = NoPin I2S_WS_PIN = NoPin // TODO: figure out what this is on Feather M0 Express. ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Adafruit Feather M0 Express" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x801B ) var ( DefaultUART = UART0 ) ================================================ FILE: src/machine/board_feather-m0.go ================================================ //go:build sam && atsamd21 && feather_m0 package machine // used to reset into bootloader const resetMagicValue = 0xf01669ef // GPIO Pins const ( D0 = PA11 // UART0 RX D1 = PA10 // UART0 TX D2 = NoPin // does not seem to exist D3 = PA09 D4 = PA08 D5 = PA15 // PWM available D6 = PA20 // PWM available D7 = NoPin // does not seem to exist D8 = PA06 D9 = PA07 // PWM available D10 = PA18 // can be used for PWM or UART1 TX D11 = PA16 // can be used for PWM or UART1 RX D12 = PA19 // PWM available D13 = PA17 // PWM available ) // Analog pins const ( A0 = PA02 // ADC/AIN[0] A1 = PB08 // ADC/AIN[2] A2 = PB09 // ADC/AIN[3] A3 = PA04 // ADC/AIN[4] A4 = PA05 // ADC/AIN[5] A5 = PB02 // ADC/AIN[10] ) const ( LED = D13 ) // USBCDC pins const ( USBCDC_DM_PIN = PA24 USBCDC_DP_PIN = PA25 ) // UART1 pins const ( UART_TX_PIN = D10 UART_RX_PIN = D11 ) // UART1 on the Feather M0. var UART1 = &sercomUSART1 // I2C pins const ( SDA_PIN = PA22 // SDA: SERCOM3/PAD[0] SCL_PIN = PA23 // SCL: SERCOM3/PAD[1] ) // I2C on the Feather M0. var ( I2C0 = sercomI2CM3 ) // SPI pins const ( SPI0_SCK_PIN = PB11 // SCK: SERCOM4/PAD[3] SPI0_SDO_PIN = PB10 // SDO: SERCOM4/PAD[2] SPI0_SDI_PIN = PA12 // SDI: SERCOM4/PAD[0] ) // SPI on the Feather M0. var SPI0 = sercomSPIM4 // I2S pins const ( I2S_SCK_PIN = PA10 I2S_SDO_PIN = PA08 I2S_SDI_PIN = NoPin I2S_WS_PIN = NoPin // TODO: figure out what this is on Feather M0. ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Adafruit Feather M0 Express" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x801B ) var ( DefaultUART = UART1 ) ================================================ FILE: src/machine/board_feather-m4-can.go ================================================ //go:build feather_m4_can package machine import ( "device/sam" ) // used to reset into bootloader const resetMagicValue = 0xf01669ef // GPIO Pins const ( D0 = PB17 // UART0 RX/PWM available D1 = PB16 // UART0 TX/PWM available D4 = PA14 // PWM available D5 = PA16 // PWM available D6 = PA18 // PWM available D7 = PB03 // neopixel power D8 = PB02 // built-in neopixel D9 = PA19 // PWM available D10 = PA20 // can be used for PWM or UART1 TX D11 = PA21 // can be used for PWM or UART1 RX D12 = PA22 // PWM available D13 = PA23 // PWM available D21 = PA13 // PWM available D22 = PA12 // PWM available D23 = PB22 // PWM available D24 = PB23 // PWM available D25 = PA17 // PWM available ) // Analog pins const ( A0 = PA02 // ADC/AIN[0] A1 = PA05 // ADC/AIN[2] A2 = PB08 // ADC/AIN[3] A3 = PB09 // ADC/AIN[4] A4 = PA04 // ADC/AIN[5] A5 = PA06 // ADC/AIN[10] ) const ( LED = D13 NEOPIXELS = D8 WS2812 = D8 ) // USBCDC pins const ( USBCDC_DM_PIN = PA24 USBCDC_DP_PIN = PA25 ) const ( UART_TX_PIN = D1 UART_RX_PIN = D0 ) const ( UART2_TX_PIN = A4 UART2_RX_PIN = A5 ) // I2C pins const ( SDA_PIN = D22 // SDA: SERCOM2/PAD[0] SCL_PIN = D21 // SCL: SERCOM2/PAD[1] ) // SPI pins const ( SPI0_SCK_PIN = D25 // SCK: SERCOM1/PAD[1] SPI0_SDO_PIN = D24 // SDO: SERCOM1/PAD[3] SPI0_SDI_PIN = D23 // SDI: SERCOM1/PAD[2] ) // CAN pins const ( CAN0_TX = PA22 CAN0_RX = PA23 CAN1_STANDBY = PB12 CAN1_TX = PB14 CAN1_RX = PB15 BOOST_EN = PB13 // power control of CAN1's TCAN1051HGV (H: enable) CAN_STANDBY = CAN1_STANDBY CAN_S = CAN1_STANDBY CAN_TX = CAN1_TX CAN_RX = CAN1_RX ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Adafruit Feather M4 CAN" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x80CD ) var ( UART1 = &sercomUSART5 UART2 = &sercomUSART0 ) func init() { // turn on neopixel D7.Configure(PinConfig{Mode: PinOutput}) D7.High() } // I2C on the Feather M4 CAN. var ( I2C0 = sercomI2CM2 ) // SPI on the Feather M4 CAN. var SPI0 = sercomSPIM1 // CAN on the Feather M4 CAN. var ( CAN0 = CAN{ Bus: sam.CAN0, } CAN1 = CAN{ Bus: sam.CAN1, } ) var ( DefaultUART = UART1 ) ================================================ FILE: src/machine/board_feather-m4.go ================================================ //go:build feather_m4 package machine // used to reset into bootloader const resetMagicValue = 0xf01669ef // GPIO Pins const ( D0 = PB17 // UART0 RX/PWM available D1 = PB16 // UART0 TX/PWM available D4 = PA14 // PWM available D5 = PA16 // PWM available D6 = PA18 // PWM available D8 = PB03 // built-in neopixel D9 = PA19 // PWM available D10 = PA20 // can be used for PWM or UART1 TX D11 = PA21 // can be used for PWM or UART1 RX D12 = PA22 // PWM available D13 = PA23 // PWM available D21 = PA13 // PWM available D22 = PA12 // PWM available D23 = PB22 // PWM available D24 = PB23 // PWM available D25 = PA17 // PWM available ) // Analog pins const ( A0 = PA02 // ADC/AIN[0] A1 = PA05 // ADC/AIN[2] A2 = PB08 // ADC/AIN[3] A3 = PB09 // ADC/AIN[4] A4 = PA04 // ADC/AIN[5] A5 = PA06 // ADC/AIN[10] ) const ( LED = D13 WS2812 = D8 ) // USBCDC pins const ( USBCDC_DM_PIN = PA24 USBCDC_DP_PIN = PA25 ) const ( UART_TX_PIN = D1 UART_RX_PIN = D0 ) const ( UART2_TX_PIN = A4 UART2_RX_PIN = A5 ) var ( UART1 = &sercomUSART5 UART2 = &sercomUSART0 DefaultUART = UART1 ) // I2C pins const ( SDA_PIN = D22 // SDA: SERCOM2/PAD[0] SCL_PIN = D21 // SCL: SERCOM2/PAD[1] ) // I2C on the Feather M4. var ( I2C0 = sercomI2CM2 ) // SPI pins const ( SPI0_SCK_PIN = D25 // SCK: SERCOM1/PAD[1] SPI0_SDO_PIN = D24 // SDO: SERCOM1/PAD[3] SPI0_SDI_PIN = D23 // SDI: SERCOM1/PAD[2] ) // SPI on the Feather M4. var SPI0 = sercomSPIM1 // USB CDC identifiers const ( usb_STRING_PRODUCT = "Adafruit Feather M4" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x8022 ) ================================================ FILE: src/machine/board_feather-nrf52840-sense.go ================================================ //go:build feather_nrf52840_sense package machine const HasLowFrequencyCrystal = false // GPIO Pins const ( D0 = P0_25 // UART TX D1 = P0_24 // UART RX D2 = P0_10 // NFC2 D3 = P1_11 D4 = P1_10 // LED2 D5 = P1_08 D6 = P0_07 D7 = P1_02 // Button D8 = P0_16 // NeoPixel D9 = P0_26 D10 = P0_27 D11 = P0_06 D12 = P0_08 D13 = P1_09 // LED1 D14 = P0_04 // A0 D15 = P0_05 // A1 D16 = P0_30 // A2 D17 = P0_28 // A3 D18 = P0_02 // A4 D19 = P0_03 // A5 D20 = P0_29 // Battery D21 = P0_31 // AREF D22 = P0_12 // I2C SDA D23 = P0_11 // I2C SCL D24 = P0_15 // SPI MISO D25 = P0_13 // SPI MOSI D26 = P0_14 // SPI SCK D27 = P0_19 // QSPI CLK D28 = P0_20 // QSPI CS D29 = P0_17 // QSPI Data 0 D30 = P0_22 // QSPI Data 1 D31 = P0_23 // QSPI Data 2 D32 = P0_21 // QSPI Data 3 D33 = P0_09 // NFC1 (test point on bottom of board) ) // Analog Pins const ( A0 = D14 A1 = D15 A2 = D16 A3 = D17 A4 = D18 A5 = D19 A6 = D20 // Battery A7 = D21 // ARef ) const ( LED = D13 LED1 = LED LED2 = D4 NEOPIXEL = D8 WS2812 = D8 BUTTON = D7 QSPI_SCK = D27 QSPI_CS = D28 QSPI_DATA0 = D29 QSPI_DATA1 = D30 QSPI_DATA2 = D31 QSPI_DATA3 = D32 ) // UART0 pins (logical UART1) const ( UART_RX_PIN = D1 UART_TX_PIN = D0 ) // I2C pins const ( SDA_PIN = D22 // I2C0 external SCL_PIN = D23 // I2C0 external ) // SPI pins const ( SPI0_SCK_PIN = D26 // SCK SPI0_SDO_PIN = D25 // SDO SPI0_SDI_PIN = D24 // SDI ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Feather nRF52840 Express" usb_STRING_MANUFACTURER = "Adafruit Industries LLC" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x8088 ) var ( DefaultUART = UART0 ) ================================================ FILE: src/machine/board_feather-nrf52840.go ================================================ //go:build feather_nrf52840 package machine const HasLowFrequencyCrystal = true // GPIO Pins const ( D0 = P0_25 // UART TX D1 = P0_24 // UART RX D2 = P0_10 // NFC2 D3 = P1_15 // LED1 D4 = P1_10 // LED2 D5 = P1_08 D6 = P0_07 D7 = P1_02 // Button D8 = P0_16 // NeoPixel D9 = P0_26 D10 = P0_27 D11 = P0_06 D12 = P0_08 D13 = P1_09 D14 = P0_04 // A0 D15 = P0_05 // A1 D16 = P0_30 // A2 D17 = P0_28 // A3 D18 = P0_02 // A4 D19 = P0_03 // A5 D20 = P0_29 // Battery D21 = P0_31 // AREF D22 = P0_12 // I2C SDA D23 = P0_11 // I2C SCL D24 = P0_15 // SPI MISO D25 = P0_13 // SPI MOSI D26 = P0_14 // SPI SCK D27 = P0_19 // QSPI CLK D28 = P0_20 // QSPI CS D29 = P0_17 // QSPI Data 0 D30 = P0_22 // QSPI Data 1 D31 = P0_23 // QSPI Data 2 D32 = P0_21 // QSPI Data 3 D33 = P0_09 // NFC1 (test point on bottom of board) ) // Analog Pins const ( A0 = D14 A1 = D15 A2 = D16 A3 = D17 A4 = D18 A5 = D19 A6 = D20 // Battery A7 = D21 // ARef ) const ( LED = D3 LED1 = LED LED2 = D4 NEOPIXEL = D8 WS2812 = D8 BUTTON = D7 QSPI_SCK = D27 QSPI_CS = D28 QSPI_DATA0 = D29 QSPI_DATA1 = D30 QSPI_DATA2 = D31 QSPI_DATA3 = D32 ) // UART0 pins (logical UART1) const ( UART_RX_PIN = D1 UART_TX_PIN = D0 ) // I2C pins const ( SDA_PIN = D22 // I2C0 external SCL_PIN = D23 // I2C0 external ) // SPI pins const ( SPI0_SCK_PIN = D26 // SCK SPI0_SDO_PIN = D25 // SDO SPI0_SDI_PIN = D24 // SDI ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Feather nRF52840 Express" usb_STRING_MANUFACTURER = "Adafruit Industries LLC" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x802A ) var ( DefaultUART = UART0 ) ================================================ FILE: src/machine/board_feather-stm32f405.go ================================================ //go:build feather_stm32f405 package machine import ( "device/stm32" "runtime/interrupt" ) const ( NUM_DIGITAL_IO_PINS = 39 NUM_ANALOG_IO_PINS = 7 ) // Digital pins const ( // Arduino pin = MCU port pin // primary functions (alternate functions) D0 = PB11 // USART3 RX, PWM TIM2_CH4 (I2C2 SDA) D1 = PB10 // USART3 TX, PWM TIM2_CH3 (I2C2 SCL, I2S2 BCK) D2 = PB3 // GPIO, SPI3 FLASH SCK D3 = PB4 // GPIO, SPI3 FLASH MISO D4 = PB5 // GPIO, SPI3 FLASH MOSI D5 = PC7 // GPIO, PWM TIM3_CH2 (USART6 RX, I2S3 MCK) D6 = PC6 // GPIO, PWM TIM3_CH1 (USART6 TX, I2S2 MCK) D7 = PA15 // GPIO, SPI3 FLASH CS D8 = PC0 // GPIO, Neopixel D9 = PB8 // GPIO, PWM TIM4_CH3 (CAN1 RX, I2C1 SCL) D10 = PB9 // GPIO, PWM TIM4_CH4 (CAN1 TX, I2C1 SDA, I2S2 WSL) D11 = PC3 // GPIO (I2S2 SD, SPI2 MOSI) D12 = PC2 // GPIO (I2S2ext SD, SPI2 MISO) D13 = PC1 // GPIO, Builtin LED D14 = PB7 // I2C1 SDA, PWM TIM4_CH2 (USART1 RX) D15 = PB6 // I2C1 SCL, PWM TIM4_CH1 (USART1 TX, CAN2 TX) D16 = PA4 // A0 (DAC OUT1) D17 = PA5 // A1 (DAC OUT2, SPI1 SCK) D18 = PA6 // A2, PWM TIM3_CH1 (SPI1 MISO) D19 = PA7 // A3, PWM TIM3_CH2 (SPI1 MOSI) D20 = PC4 // A4 D21 = PC5 // A5 D22 = PA3 // A6 D23 = PB13 // SPI2 SCK, PWM TIM1_CH1N (I2S2 BCK, CAN2 TX) D24 = PB14 // SPI2 MISO, PWM TIM1_CH2N (I2S2ext SD) D25 = PB15 // SPI2 MOSI, PWM TIM1_CH3N (I2S2 SD) D26 = PC8 // SDIO D27 = PC9 // SDIO D28 = PC10 // SDIO D29 = PC11 // SDIO D30 = PC12 // SDIO D31 = PD2 // SDIO D32 = PB12 // SD Detect D33 = PC14 // OSC32 D34 = PC15 // OSC32 D35 = PA11 // USB D+ D36 = PA12 // USB D- D37 = PA13 // SWDIO D38 = PA14 // SWCLK ) // Analog pins const ( A0 = D16 // ADC12 IN4 A1 = D17 // ADC12 IN5 A2 = D18 // ADC12 IN6 A3 = D19 // ADC12 IN7 A4 = D20 // ADC12 IN14 A5 = D21 // ADC12 IN15 A6 = D22 // VBAT ) func init() { initLED() initUART() initSPI() initI2C() } // -- LEDs --------------------------------------------------------------------- const ( NUM_BOARD_LED = 1 NUM_BOARD_NEOPIXEL = 1 LED_RED = D13 LED_NEOPIXEL = D8 LED_BUILTIN = LED_RED LED = LED_BUILTIN WS2812 = D8 ) func initLED() {} // -- UART --------------------------------------------------------------------- const ( // #===========#==========#==============#============#=======#=======# // | Interface | Hardware | Bus(Freq) | RX/TX Pins | AltFn | Alias | // #===========#==========#==============#============#=======#=======# // | UART1 | USART3 | APB1(42 MHz) | D0/D1 | 7 | ~ | // | UART2 | USART6 | APB2(84 MHz) | D5/D6 | 8 | ~ | // | UART3 | USART1 | APB2(84 MHz) | D14/D15 | 7 | ~ | // | --------- | -------- | ------------ | ---------- | ----- | ----- | // | UART0 | USART3 | APB1(42 MHz) | D0/D1 | 7 | UART1 | // #===========#==========#==============#============#=======#=======# NUM_UART_INTERFACES = 3 UART1_RX_PIN = D0 // UART1 = hardware: USART3 UART1_TX_PIN = D1 // UART2_RX_PIN = D5 // UART2 = hardware: USART6 UART2_TX_PIN = D6 // UART3_RX_PIN = D14 // UART3 = hardware: USART1 UART3_TX_PIN = D15 // UART0_RX_PIN = UART1_RX_PIN // UART0 = alias: UART1 UART0_TX_PIN = UART1_TX_PIN // UART_RX_PIN = UART0_RX_PIN // default/primary UART pins UART_TX_PIN = UART0_TX_PIN // ) var ( UART1 = &_UART1 _UART1 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART3, TxAltFuncSelector: AF7_USART1_2_3, RxAltFuncSelector: AF7_USART1_2_3, } UART2 = &_UART2 _UART2 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART6, TxAltFuncSelector: AF8_USART4_5_6, RxAltFuncSelector: AF8_USART4_5_6, } UART3 = &_UART3 _UART3 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART1, TxAltFuncSelector: AF7_USART1_2_3, RxAltFuncSelector: AF7_USART1_2_3, } DefaultUART = UART1 ) func initUART() { UART1.Interrupt = interrupt.New(stm32.IRQ_USART3, _UART1.handleInterrupt) UART2.Interrupt = interrupt.New(stm32.IRQ_USART6, _UART2.handleInterrupt) UART3.Interrupt = interrupt.New(stm32.IRQ_USART1, _UART3.handleInterrupt) } // -- SPI ---------------------------------------------------------------------- const ( // #===========#==========#==============#==================#=======#=======# // | Interface | Hardware | Bus(Freq) | SCK/SDI/SDO Pins | AltFn | Alias | // #===========#==========#==============#==================#=======#=======# // | SPI1 | SPI2 | APB1(42 MHz) | D23/D24/D25 | 5 | ~ | // | SPI2 | SPI3 | APB1(42 MHz) | D2/D3/D4 | 6 | ~ | // | SPI3 | SPI1 | APB2(84 MHz) | D17/D18/D19 | 5 | ~ | // | --------- | -------- | ------------ | ---------------- | ----- | ----- | // | SPI0 | SPI2 | APB1(42 MHz) | D23/D24/D25 | 5 | SPI1 | // #===========#==========#==============#==================#=======#=======# NUM_SPI_INTERFACES = 3 SPI1_SCK_PIN = D23 // SPI1_SDI_PIN = D24 // SPI1 = hardware: SPI2 SPI1_SDO_PIN = D25 // SPI2_SCK_PIN = D2 // SPI2_SDI_PIN = D3 // SPI2 = hardware: SPI3 SPI2_SDO_PIN = D4 // SPI3_SCK_PIN = D17 // SPI3_SDI_PIN = D18 // SPI3 = hardware: SPI1 SPI3_SDO_PIN = D19 // SPI0_SCK_PIN = SPI1_SCK_PIN // SPI0_SDI_PIN = SPI1_SDI_PIN // SPI0 = alias: SPI1 SPI0_SDO_PIN = SPI1_SDO_PIN // SPI_SCK_PIN = SPI0_SCK_PIN // SPI_SDI_PIN = SPI0_SDI_PIN // default/primary SPI pins SPI_SDO_PIN = SPI0_SDO_PIN // ) var ( SPI1 = &SPI{ Bus: stm32.SPI2, AltFuncSelector: AF5_SPI1_SPI2, } SPI2 = &SPI{ Bus: stm32.SPI3, AltFuncSelector: AF6_SPI3, } SPI3 = &SPI{ Bus: stm32.SPI1, AltFuncSelector: AF5_SPI1_SPI2, } SPI0 = SPI1 ) func initSPI() {} // -- I2C ---------------------------------------------------------------------- const ( // #===========#==========#==============#==============#=======#=======# // | Interface | Hardware | Bus(Freq) | SDA/SCL Pins | AltFn | Alias | // #===========#==========#==============#==============#=======#=======# // | I2C1 | I2C1 | APB1(42 MHz) | D14/D15 | 4 | ~ | // | I2C2 | I2C2 | APB1(42 MHz) | D0/D1 | 4 | ~ | // | I2C3 | I2C1 | APB1(42 MHz) | D9/D10 | 4 | ~ | // | --------- | -------- | ------------ | ------------ | ----- | ----- | // | I2C0 | I2C1 | APB1(42 MHz) | D14/D15 | 4 | I2C1 | // #===========#==========#==============#==============#=======#=======# NUM_I2C_INTERFACES = 3 I2C1_SDA_PIN = D14 // I2C1 = hardware: I2C1 I2C1_SCL_PIN = D15 // I2C2_SDA_PIN = D0 // I2C2 = hardware: I2C2 I2C2_SCL_PIN = D1 // I2C3_SDA_PIN = D9 // I2C3 = hardware: I2C1 I2C3_SCL_PIN = D10 // (interface duplicated on second pair of pins) I2C0_SDA_PIN = I2C1_SDA_PIN // I2C0 = alias: I2C1 I2C0_SCL_PIN = I2C1_SCL_PIN // I2C_SDA_PIN = I2C0_SDA_PIN // default/primary I2C pins I2C_SCL_PIN = I2C0_SCL_PIN // SDA_PIN = I2C0_SDA_PIN SCL_PIN = I2C0_SCL_PIN ) var ( I2C1 = &I2C{ Bus: stm32.I2C1, AltFuncSelector: AF4_I2C1_2_3, } I2C2 = &I2C{ Bus: stm32.I2C2, AltFuncSelector: AF4_I2C1_2_3, } I2C3 = &I2C{ Bus: stm32.I2C1, AltFuncSelector: AF4_I2C1_2_3, } I2C0 = I2C1 ) func initI2C() {} ================================================ FILE: src/machine/board_feather_rp2040.go ================================================ //go:build feather_rp2040 package machine // Onboard crystal oscillator frequency, in MHz. const xoscFreq = 12 // MHz // GPIO Pins const ( D4 = GPIO6 D5 = GPIO7 D6 = GPIO8 D9 = GPIO9 D10 = GPIO10 D11 = GPIO11 D12 = GPIO12 D13 = GPIO13 D24 = GPIO24 D25 = GPIO25 ) // Analog pins const ( A0 = GPIO26 A1 = GPIO27 A2 = GPIO28 A3 = GPIO29 ) const LED = GPIO13 // I2C Pins. const ( I2C0_SDA_PIN = GPIO24 I2C0_SCL_PIN = GPIO25 I2C1_SDA_PIN = GPIO2 I2C1_SCL_PIN = GPIO3 SDA_PIN = I2C1_SDA_PIN SCL_PIN = I2C1_SCL_PIN ) // SPI default pins const ( // Default Serial Clock Bus 0 for SPI communications SPI0_SCK_PIN = GPIO18 // Default Serial Out Bus 0 for SPI communications SPI0_SDO_PIN = GPIO19 // Tx // Default Serial In Bus 0 for SPI communications SPI0_SDI_PIN = GPIO20 // Rx // Default Serial Clock Bus 1 for SPI communications SPI1_SCK_PIN = GPIO10 // Default Serial Out Bus 1 for SPI communications SPI1_SDO_PIN = GPIO11 // Tx // Default Serial In Bus 1 for SPI communications SPI1_SDI_PIN = GPIO12 // Rx ) // UART pins const ( UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART1_TX_PIN = GPIO8 UART1_RX_PIN = GPIO9 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) var DefaultUART = UART0 // USB identifiers const ( usb_STRING_PRODUCT = "Feather RP2040" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x80F1 ) ================================================ FILE: src/machine/board_gemma-m0.go ================================================ //go:build sam && atsamd21 && gemma_m0 package machine // Used to reset into bootloader. const resetMagicValue = 0xf01669ef // GPIO Pins. const ( D0 = PA04 // SERCOM0/PAD[0] D1 = PA02 D2 = PA05 // SERCOM0/PAD[1] D3 = PA00 // DotStar LED: SERCOM1/PAD[0]: APA102/MOSI D4 = PA01 // DotStar LED: SERCOM1/PAD[1]: APA102/SCK D11 = PA30 // Flash Access: SERCOM1/PAD[2] D12 = PA31 // Flash Access: SERCOM1/PAD[3] D13 = PA23 // LED: SERCOM3/PAD[1] SERCOM5/PAD[1] ) // Analog pins. const ( A0 = D1 A1 = D2 A2 = D0 ) const ( LED = PA23 ) // USBCDC pins. const ( USBCDC_DM_PIN = PA24 USBCDC_DP_PIN = PA25 ) // UART0 pins. const ( UART_TX_PIN = PA04 // TX: SERCOM0/PAD[0] UART_RX_PIN = PA05 // RX: SERCOM0/PAD[1] ) // UART0s on the Gemma M0. var UART0 = &sercomUSART0 // SPI pins. const ( SPI0_SCK_PIN = PA05 // SCK: SERCOM0/PAD[1] SPI0_SDO_PIN = PA04 // MOSI: SERCOM0/PAD[0] SPI0_SDI_PIN = NoPin SPI0_CS_PIN = NoPin ) // SPI on the Gemma M0. var SPI0 = sercomSPIM0 // SPI pins for DotStar LED (using APA102 software SPI) and Flash. const ( SPI1_SCK_PIN = PA01 // SCK: SERCOM1/PAD[0] SPI1_SDO_PIN = PA00 // MOSI: SERCOM1/PAD[1] SPI1_SDI_PIN = PA31 // MISO: SERCOM1/PAD[3] SPI1_CS_PIN = PA30 // CS: SERCOM1/PAD[2] ) // I2C pins. const ( SDA_PIN = PA04 // SDA: SERCOM0/PAD[0] SCL_PIN = PA05 // SCL: SERCOM0/PAD[1] ) // I2C on the Gemma M0. var ( I2C0 = sercomI2CM0 ) // I2S (not connected, needed for atsamd21). const ( I2S_SCK_PIN = NoPin I2S_SDO_PIN = NoPin I2S_SDI_PIN = NoPin I2S_WS_PIN = NoPin ) // USB CDC identifiers. const ( usb_STRING_PRODUCT = "Adafruit Gemma M0" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x801E ) var ( DefaultUART = UART0 ) ================================================ FILE: src/machine/board_gnse.go ================================================ //go:build gnse package machine import ( "device/stm32" "runtime/interrupt" ) const ( LED_RED = PB5 LED_GREEN = PB6 LED_BLUE = PB7 LED1 = LED_RED // Red LED2 = LED_GREEN // Green LED3 = LED_BLUE // Blue LED = LED_GREEN // Default BUTTON = PB3 BUZZER = PA15 VBATT_ADC = PB2 SENSOR_EN = PB12 FLASH_EN = PC13 // SPI0 SPI0_NSS_PIN = PA4 SPI0_SCK_PIN = PA5 SPI0_SDO_PIN = PA6 SPI0_SDI_PIN = PA7 //MCU USART2 UART2_RX_PIN = PA3 UART2_TX_PIN = PA2 // DEFAULT USART UART_RX_PIN = UART2_RX_PIN UART_TX_PIN = UART2_TX_PIN // I2C1 pins // I2C1 is connected to Flash, Accelerometer, Env. Sensor, Crypto Element) I2C1_SCL_PIN = PA9 I2C1_SDA_PIN = PA10 I2C1_ALT_FUNC = 4 // I2C2 pins // I2C2 is expansion J10 QWIIC Connector I2C2_SCL_PIN = PA12 I2C2_SDA_PIN = PA11 I2C2_ALT_FUNC = 4 // I2C0 alias for I2C1 I2C0_SDA_PIN = I2C1_SDA_PIN I2C0_SCL_PIN = I2C1_SCL_PIN ) var ( // STM32 UART2 is connected to the embedded STLINKV3 Virtual Com Port UART0 = &_UART0 _UART0 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART2, TxAltFuncSelector: 7, RxAltFuncSelector: 7, } DefaultUART = UART0 // I2C Busses I2C1 = &I2C{ Bus: stm32.I2C1, AltFuncSelector: I2C1_ALT_FUNC, } I2C2 = &I2C{ Bus: stm32.I2C2, AltFuncSelector: I2C2_ALT_FUNC, } I2C0 = I2C1 // SPI SPI3 = &SPI{ Bus: stm32.SPI3, } ) func init() { // Enable UARTs Interrupts UART0.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART0.handleInterrupt) } ================================================ FILE: src/machine/board_gopher-arcade.go ================================================ //go:build gopher_arcade package machine // Return the current CPU frequency in hertz. func CPUFrequency() uint32 { return 8000000 } const ( P5 Pin = PB0 P6 Pin = PB1 P7 Pin = PB2 P2 Pin = PB3 P3 Pin = PB4 P1 Pin = PB5 LED = P1 BUTTON_LEFT = P7 BUTTON_RIGHT = P5 SPEAKER = P6 ) ================================================ FILE: src/machine/board_gopher-badge.go ================================================ //go:build gopher_badge // This contains the pin mappings for the Gopher Badge. // // For more information, see: https://gopherbadge.com/ package machine const ( /*ADC0 Pin = GPIO26 ADC1 Pin = GPIO27 ADC2 Pin = GPIO28 GPIO4 Pin = GPIO4 GPIO5 Pin = GPIO5 GPIO6 Pin = GPIO6 GPIO7 Pin = GPIO7 GPIO8 Pin = GPIO8 GPIO9 Pin = GPIO9*/ PENIRQ Pin = GPIO13 LED Pin = GPIO2 NEOPIXELS Pin = GPIO15 WS2812 Pin = GPIO15 BUTTON_A Pin = GPIO10 BUTTON_B Pin = GPIO11 BUTTON_LEFT Pin = GPIO25 BUTTON_UP Pin = GPIO24 BUTTON_RIGHT Pin = GPIO22 BUTTON_DOWN Pin = GPIO23 TFT_RST Pin = GPIO21 TFT_SDI Pin = GPIO19 TFT_SDO Pin = GPIO16 TFT_CS Pin = GPIO17 TFT_SCL Pin = GPIO18 TFT_WRX Pin = GPIO20 TFT_BACKLIGHT Pin = GPIO12 SPEAKER Pin = GPIO14 SPEAKER_ENABLE Pin = GPIO3 ) // I2C pins const ( I2C0_SDA_PIN Pin = GPIO0 I2C0_SCL_PIN Pin = GPIO1 I2C1_SDA_PIN Pin = NoPin I2C1_SCL_PIN Pin = NoPin ) // SPI pins. const ( SPI0_SCK_PIN Pin = GPIO18 SPI0_SDO_PIN Pin = GPIO19 SPI0_SDI_PIN Pin = GPIO16 SPI1_SCK_PIN Pin = NoPin SPI1_SDO_PIN Pin = NoPin SPI1_SDI_PIN Pin = NoPin ) // Onboard crystal oscillator frequency, in MHz. const ( xoscFreq = 12 // MHz ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Gopher Badge" usb_STRING_MANUFACTURER = "TinyGo" ) var ( usb_VID uint16 = 0x2e8a usb_PID uint16 = 0x0003 ) // UART pins const ( UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART1_TX_PIN = GPIO4 UART1_RX_PIN = GPIO5 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) var DefaultUART = UART1 ================================================ FILE: src/machine/board_grandcentral-m4.go ================================================ //go:build grandcentral_m4 package machine // Digital pins const ( // = Pin Alt. Function SERCOM PWM Timer Interrupt // ------ -------------------- -------- ----------- ----------- D0 = PB25 // UART1 RX 0[1] EXTI9 D1 = PB24 // UART1 TX 0[0] EXTI8 D2 = PC18 // TCC0[2] EXTI2 D3 = PC19 // TCC0[3] EXTI3 D4 = PC20 // TCC0[4] EXTI4 D5 = PC21 // TCC0[5] EXTI5 D6 = PD20 // TCC1[0] EXTI10 D7 = PD21 // TCC1[1] EXTI11 D8 = PB18 // TCC1[0] EXTI2 D9 = PB02 // TC6[0] EXTI3 D10 = PB22 // TC7[0] EXTI6 D11 = PB23 // EXTI7 D12 = PB00 // TC7[0] EXTI0 D13 = PB01 // On-board LED TC7[1] EXTI1 D14 = PB16 // UART4 TX, I2S0 SCK 5[0] TC6[0] EXTI0 D15 = PB17 // UART4 RX, I2S0 MCK 5[1] EXTI1 D16 = PC22 // UART3 TX 1[0] EXTI6 D17 = PC23 // UART3 RX 1[1] EXTI6 D18 = PB12 // UART2 TX 4[0] TCC3[0] EXTI12 D19 = PB13 // UART2 RX 4[1] TCC3[1] EXTI13 D20 = PB20 // I2C0 SDA 3[0] EXTI4 D21 = PB21 // I2C0 SCL 3[1] EXTI5 D22 = PD12 // EXTI7 D23 = PA15 // TCC2[1] EXTI15 D24 = PC17 // I2C1 SCL 6[1] TCC0[1] EXTI1 D25 = PC16 // I2C1 SDA 6[0] TCC0[0] EXTI0 D26 = PA12 // PCC DEN1 TC2[0] EXTI12 D27 = PA13 // PCC DEN2 TC2[1] EXTI13 D28 = PA14 // PCC CLK TCC2[0] EXTI14 D29 = PB19 // PCC XCLK EXTI3 D30 = PA23 // PCC D7 TC4[1] EXTI7 D31 = PA22 // PCC D6, I2S0 SDI TC4[0] EXTI6 D32 = PA21 // PCC D5, I2S0 SDO EXTI5 D33 = PA20 // PCC D4, I2S0 FS EXTI4 D34 = PA19 // PCC D3 TC3[1] EXTI3 D35 = PA18 // PCC D2 TC3[0] EXTI2 D36 = PA17 // PCC D1 EXTI1 D37 = PA16 // PCC D0 EXTI0 D38 = PB15 // PCC D9 TCC4[1] EXTI15 D39 = PB14 // PCC D8 TCC4[0] EXTI14 D40 = PC13 // PCC D11 EXTI13 D41 = PC12 // PCC D10 EXTI12 D42 = PC15 // PCC D13 EXTI15 D43 = PC14 // PCC D12 EXTI14 D44 = PC11 // EXTI11 D45 = PC10 // EXTI10 D46 = PC06 // EXTI6 D47 = PC07 // EXTI5 D48 = PC04 // EXTI4 D49 = PC05 // EXTI5 D50 = PD11 // SPI0 SDI 7[3] EXTI11 D51 = PD08 // SPI0 SDO 7[0] EXTI8 D52 = PD09 // SPI0 SCK 7[1] EXTI9 D53 = PD10 // SPI0 CS EXTI10 D54 = PB05 // ADC1 (A8) EXTI5 D55 = PB06 // ADC1 (A9) EXTI6 D56 = PB07 // ADC1 (A10) EXTI7 D57 = PB08 // ADC1 (A11) EXTI8 D58 = PB09 // ADC1 (A12) EXTI9 D59 = PA04 // ADC0 (A13) TC0[0] EXTI4 D60 = PA06 // ADC0 (A14) TC1[0] EXTI6 D61 = PA07 // ADC0 (A15) TC1[1] EXTI7 D62 = PB20 // I2C0 SDA 3[0] TCC1[2] EXTI4 D63 = PB21 // I2C0 SCL 3[1] TCC1[3] EXTI5 D64 = PD11 // SPI0 SDI 7[3] EXTI6 D65 = PD08 // SPI0 SDO 7[0] EXTI3 D66 = PD09 // SPI0 SCK 7[1] EXTI4 D67 = PA02 // ADC0 (A0), DAC0 EXTI2 D68 = PA05 // ADC0 (A1), DAC1 EXTI5 D69 = PB03 // ADC0 (A2) TC6[1] EXTI3 D70 = PC00 // ADC1 (A3) EXTI0 D71 = PC01 // ADC1 (A4) EXTI1 D72 = PC02 // ADC1 (A5) EXTI2 D73 = PC03 // ADC1 (A6) EXTI3 D74 = PB04 // ADC1 (A7) EXTI4 D75 = PC31 // UART RX LED D76 = PC30 // UART TX LED D77 = PA27 // USB HOST EN D78 = PA24 // USB DM EXTI8 D79 = PA25 // USB DP EXTI9 D80 = PB29 // SD/SPI1 SDI 2[3] D81 = PB27 // SD/SPI1 SCK 2[1] D82 = PB26 // SD/SPI1 SDO 2[0] D83 = PB28 // SD/SPI1 CS D84 = PA03 // AREF EXTI3 D85 = PA02 // DAC0 EXTI2 D86 = PA05 // DAC1 EXTI5 D87 = PB01 // On-board LED (D13) TC7[1] EXTI1 D88 = PC24 // On-board NeoPixel D89 = PB10 // QSPI SCK EXTI10 D90 = PB11 // QSPI CS EXTI11 D91 = PA08 // QSPI ID0 EXTI(NMI) D92 = PA09 // QSPI ID1 EXTI9 D93 = PA10 // QSPI ID2 EXTI10 D94 = PA11 // QSPI ID3 EXTI11 D95 = PB31 // SD Detect EXTI15 D96 = PB30 // SWO EXTI14 ) // Analog pins const ( A0 = D67 // (PA02) ADC0 ch. 0, A1 = D68 // (PA05) ADC0 ch. 5, A2 = D69 // (PB03) ADC0 ch. 15 A3 = D70 // (PC00) ADC1 ch. 10 A4 = D71 // (PC01) ADC1 ch. 11 A5 = D72 // (PC02) ADC1 ch. 4 A6 = D73 // (PC03) ADC1 ch. 5 A7 = D74 // (PB04) ADC1 ch. 6 A8 = D54 // (PB05) ADC1 ch. 7 A9 = D55 // (PB06) ADC1 ch. 8 A10 = D56 // (PB07) ADC1 ch. 9 A11 = D57 // (PB08) ADC1 ch. 0 A12 = D58 // (PB09) ADC1 ch. 1 A13 = D59 // (PA04) ADC0 ch. 4 A14 = D60 // (PA06) ADC0 ch. 6 A15 = D61 // (PA07) ADC0 ch. 7 AREF = D84 // (PA03) ) // LED pins const ( LED_PIN = D13 // (PB01), also on D87 UART_RX_LED_PIN = D75 // (PC31) UART_TX_LED_PIN = D76 // (PC30) NEOPIXEL_PIN = D88 // (PC24) // aliases used by examples and drivers LED = LED_PIN LED_RX = UART_RX_LED_PIN LED_TX = UART_TX_LED_PIN NEOPIXEL = NEOPIXEL_PIN WS2812 = NEOPIXEL_PIN ) // UART pins const ( UART1_RX_PIN = D0 // (PB25) UART1_TX_PIN = D1 // (PB24) UART2_RX_PIN = D19 // (PB13) UART2_TX_PIN = D18 // (PB12) UART3_RX_PIN = D17 // (PC23) UART3_TX_PIN = D16 // (PC22) UART4_RX_PIN = D15 // (PB17) UART4_TX_PIN = D14 // (PB16) UART_RX_PIN = UART1_RX_PIN // default pins UART_TX_PIN = UART1_TX_PIN // ) // UART on the Grand Central M4 var ( UART1 = &sercomUSART0 UART2 = &sercomUSART4 UART3 = &sercomUSART1 UART4 = &sercomUSART5 DefaultUART = UART1 ) // SPI pins const ( SPI0_SCK_PIN = D66 // (PD09), also on D52 SPI0_SDO_PIN = D65 // (PD08), also on D51 SPI0_SDI_PIN = D64 // (PD11), also on D50 SPI0_CS_PIN = D53 // (PD10) SPI1_SCK_PIN = D81 // (PB27) SPI1_SDO_PIN = D82 // (PB26) SPI1_SDI_PIN = D80 // (PB29) SPI_SCK_PIN = SPI0_SCK_PIN // default pins SPI_SDO_PIN = SPI0_SDO_PIN // SPI_SDI_PIN = SPI0_SDI_PIN // SPI_CS_PIN = SPI0_CS_PIN // ) // SPI on the Grand Central M4 var ( SPI0 = sercomSPIM7 SPI1 = sercomSPIM2 // SD card ) // I2C pins const ( I2C0_SDA_PIN = D62 // (PB20), also on D20 I2C0_SCL_PIN = D63 // (PB21), also on D21 I2C1_SDA_PIN = D25 // (PC16) I2C1_SCL_PIN = D24 // (PC17) I2C_SDA_PIN = I2C0_SDA_PIN // default pins I2C_SCL_PIN = I2C0_SCL_PIN // SDA_PIN = I2C_SDA_PIN // unconventional pin names SCL_PIN = I2C_SCL_PIN // (required by machine_atsamd51.go) ) // I2C on the Grand Central M4 var ( I2C0 = sercomI2CM3 I2C1 = sercomI2CM6 ) // I2S pins const ( I2S0_SCK_PIN = D14 // (PB16) I2S0_MCK_PIN = D15 // (PB17) I2S0_FS_PIN = D33 // (PA20) I2S0_SDO_PIN = D32 // (PA21) I2S0_SDI_PIN = D31 // (PA22) I2S_SCK_PIN = I2S0_SCK_PIN // default pins I2S_WS_PIN = I2S0_FS_PIN // I2S_SDO_PIN = I2S0_SDO_PIN I2S_SDI_PIN = NoPin ) // SD card pins const ( SD0_SCK_PIN = D81 // (PB27) SD0_SDO_PIN = D82 // (PB26) SD0_SDI_PIN = D80 // (PB29) SD0_CS_PIN = D83 // (PB28) SD0_DET_PIN = D95 // (PB31) SDCARD_SCK_PIN = SD0_SCK_PIN // default pins SDCARD_SDO_PIN = SD0_SDO_PIN // SDCARD_SDI_PIN = SD0_SDI_PIN // SDCARD_CS_PIN = SD0_CS_PIN // SDCARD_DET_PIN = SD0_DET_PIN // ) // Other peripheral constants const ( resetMagicValue = 0xF01669EF // Used to reset into bootloader ) // USB CDC pins const ( USBCDC_HOSTEN_PIN = D77 // (PA27) host enable USBCDC_DM_PIN = D78 // (PA24) D- USBCDC_DP_PIN = D79 // (PA25) D+ ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Adafruit Grand Central M4" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x8031 ) ================================================ FILE: src/machine/board_hifive1b.go ================================================ //go:build hifive1b package machine const ( D0 = P16 D1 = P17 D2 = P18 D3 = P19 // Green LED/PWM (PWM1_PWM1) D4 = P20 // PWM (PWM1_PWM0) D5 = P21 // Blue LED/PWM (PWM1_PWM2) D6 = P22 // Red LED/PWM (PWM1_PWM3) D7 = P16 D8 = NoPin // PWM? D9 = P01 D10 = P02 // SPI1_CS0 D11 = P03 // SPI1_DQ0 D12 = P04 // SPI1_DQ1 D13 = P05 // SPI1_SCK D14 = NoPin // not connected D15 = P09 // does not seem to work? D16 = P10 // PWM (PWM2_PWM0) D17 = P11 // PWM (PWM2_PWM1) D18 = P12 // SDA (I2C0_SDA)/PWM (PWM2_PWM2) D19 = P13 // SDL (I2C0_SCL)/PWM (PWM2_PWM3) ) const ( LED = LED1 LED1 = LED_RED LED2 = LED_GREEN LED3 = LED_BLUE LED_RED = P22 LED_GREEN = P19 LED_BLUE = P21 ) var DefaultUART = UART0 const ( // TODO: figure out the pin numbers for these. UART_TX_PIN = D1 UART_RX_PIN = D0 ) // SPI pins const ( SPI0_SCK_PIN = NoPin SPI0_SDO_PIN = NoPin SPI0_SDI_PIN = NoPin SPI1_SCK_PIN = D13 SPI1_SDO_PIN = D11 SPI1_SDI_PIN = D12 ) // I2C pins const ( I2C0_SDA_PIN = D18 I2C0_SCL_PIN = D19 ) ================================================ FILE: src/machine/board_hifive1b_baremetal.go ================================================ //go:build fe310 && hifive1b package machine import "device/sifive" // SPI on the HiFive1. var ( SPI1 = &SPI{ Bus: sifive.QSPI1, } ) ================================================ FILE: src/machine/board_hw-651.go ================================================ //go:build hw_651 package machine // No-name brand board based on the nRF51822 chip with low frequency crystal on board. // Pinout (reverse engineered from the board) can be found here: // https://aviatorahmet.blogspot.com/2020/12/pinout-of-nrf51822-board.html // https://cr0wg4n.medium.com/pinout-nrf51822-board-hw-651-78da2eda8894 const HasLowFrequencyCrystal = true var DefaultUART = UART0 // GPIO pins on header J1 const ( J1_01 = P0_21 J1_03 = P0_23 J1_04 = P0_22 J1_05 = P0_25 J1_06 = P0_24 J1_09 = P0_29 J1_10 = P0_28 J1_11 = P0_30 J1_13 = P0_00 J1_15 = P0_02 J1_17 = P0_04 J1_16 = P0_01 J1_18 = P0_03 ) // GPIO pins on header J2 const ( J2_01 = P0_20 J2_03 = P0_18 J2_04 = P0_19 J2_07 = P0_16 J2_08 = P0_15 J2_09 = P0_14 J2_10 = P0_13 J2_11 = P0_12 J2_12 = P0_11 J2_13 = P0_10 J2_14 = P0_09 J2_15 = P0_08 J2_16 = P0_07 J2_17 = P0_06 J2_18 = P0_05 ) // UART pins const ( UART_TX_PIN = P0_24 // J1_06 on the board UART_RX_PIN = P0_25 // J1_05 on the board ) // ADC pins const ( ADC0 = P0_03 // J1_18 on the board ADC1 = P0_02 // J1_15 on the board ADC2 = P0_01 // J1_16 on the board ADC3 = P0_04 // J1_17 on the board ADC4 = P0_05 // J2_18 on the board ADC5 = P0_06 // J2_17 on the board ) // I2C pins const ( SDA_PIN = P0_30 // J1_11 on the board SCL_PIN = P0_00 // J1_13 on the board ) // SPI pins const ( SPI0_SCK_PIN = P0_23 // J1_03 on the board SPI0_SDO_PIN = P0_21 // J1_01 on the board SPI0_SDI_PIN = P0_22 // J1_04 on the board ) ================================================ FILE: src/machine/board_itsybitsy-m0.go ================================================ //go:build sam && atsamd21 && itsybitsy_m0 package machine // used to reset into bootloader const resetMagicValue = 0xf01669ef // GPIO Pins const ( D0 = PA11 // UART0 RX D1 = PA10 // UART0 TX D2 = PA14 D3 = PA09 // PWM available D4 = PA08 // PWM available D5 = PA15 // PWM available D6 = PA20 // PWM available D7 = PA21 // PWM available D8 = PA06 // PWM available D9 = PA07 // PWM available D10 = PA18 // can be used for PWM or UART1 TX D11 = PA16 // can be used for PWM or UART1 RX D12 = PA19 // PWM available D13 = PA17 // PWM available ) // Analog pins const ( A0 = PA02 // ADC/AIN[0] A1 = PB08 // ADC/AIN[2] A2 = PB09 // ADC/AIN[3] A3 = PA04 // ADC/AIN[4] A4 = PA05 // ADC/AIN[5] A5 = PB02 // ADC/AIN[10] ) const ( LED = D13 ) // USBCDC pins const ( USBCDC_DM_PIN = PA24 USBCDC_DP_PIN = PA25 ) // UART1 pins const ( UART_TX_PIN = D10 UART_RX_PIN = D11 ) // UART1 on the ItsyBitsy M0. var ( UART1 = &sercomUSART1 ) // I2C pins const ( SDA_PIN = PA22 // SDA: SERCOM3/PAD[0] SCL_PIN = PA23 // SCL: SERCOM3/PAD[1] ) // I2C on the ItsyBitsy M0. var ( I2C0 = sercomI2CM3 ) // SPI pins const ( SPI0_SCK_PIN = PB11 // SCK: SERCOM4/PAD[3] SPI0_SDO_PIN = PB10 // SDO: SERCOM4/PAD[2] SPI0_SDI_PIN = PA12 // SDI: SERCOM4/PAD[0] ) // SPI on the ItsyBitsy M0. var SPI0 = sercomSPIM4 // "Internal" SPI pins; SPI flash is attached to these on ItsyBitsy M0 const ( SPI1_CS_PIN = PA27 SPI1_SCK_PIN = PB23 SPI1_SDO_PIN = PB22 SPI1_SDI_PIN = PB03 ) // "Internal" SPI on Sercom 5 var SPI1 = sercomSPIM5 // I2S pins const ( I2S_SCK_PIN = PA10 I2S_SDO_PIN = PA08 I2S_SDI_PIN = NoPin I2S_WS_PIN = NoPin // TODO: figure out what this is on ItsyBitsy M0. ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Adafruit ItsyBitsy M0 Express" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x800F ) var ( DefaultUART = UART1 ) ================================================ FILE: src/machine/board_itsybitsy-m4.go ================================================ //go:build itsybitsy_m4 package machine // used to reset into bootloader const resetMagicValue = 0xf01669ef // GPIO Pins const ( D0 = PA16 // UART0 RX/PWM available D1 = PA17 // UART0 TX/PWM available D2 = PA07 D3 = PB22 D4 = PA14 // PWM available D5 = PA15 // PWM available D6 = PB02 // dotStar clock D7 = PA18 // PWM available D8 = PB03 // dotStar data D9 = PA19 // PWM available D10 = PA20 // can be used for PWM or UART1 TX D11 = PA21 // can be used for PWM or UART1 RX D12 = PA23 // PWM available D13 = PA22 // PWM available ) // Analog pins const ( A0 = PA02 // ADC/AIN[0] A1 = PA05 // ADC/AIN[2] A2 = PB08 // ADC/AIN[3] A3 = PB09 // ADC/AIN[4] A4 = PA04 // ADC/AIN[5] A5 = PA06 // ADC/AIN[10] ) const ( LED = D13 ) // USBCDC pins const ( USBCDC_DM_PIN = PA24 USBCDC_DP_PIN = PA25 ) // UART1 pins const ( UART_TX_PIN = D1 UART_RX_PIN = D0 ) const ( UART2_TX_PIN = A4 UART2_RX_PIN = D2 ) var ( UART1 = &sercomUSART3 UART2 = &sercomUSART0 DefaultUART = UART1 ) // I2C pins const ( SDA_PIN = PA12 // SDA: SERCOM2/PAD[0] SCL_PIN = PA13 // SCL: SERCOM2/PAD[1] ) // I2C on the ItsyBitsy M4. var ( I2C0 = sercomI2CM2 ) // SPI pins const ( SPI0_SCK_PIN = PA01 // SCK: SERCOM1/PAD[1] SPI0_SDO_PIN = PA00 // SDO: SERCOM1/PAD[0] SPI0_SDI_PIN = PB23 // SDI: SERCOM1/PAD[3] ) // SPI on the ItsyBitsy M4. var SPI0 = sercomSPIM1 // USB CDC identifiers const ( usb_STRING_PRODUCT = "Adafruit ItsyBitsy M4" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x802B ) ================================================ FILE: src/machine/board_itsybitsy-nrf52840.go ================================================ //go:build itsybitsy_nrf52840 package machine const HasLowFrequencyCrystal = true // GPIO Pins const ( D0 = P0_25 // UART TX D1 = P0_24 // UART RX D2 = P1_02 D3 = P0_06 // LED1 D4 = P0_29 // Button D5 = P0_27 D6 = P1_09 // DotStar Clock D7 = P1_08 D8 = P0_08 // DotStar Data D9 = P0_07 D10 = P0_05 D11 = P0_26 D12 = P0_11 D13 = P0_12 D14 = P0_04 // A0 D15 = P0_30 // A1 D16 = P0_28 // A2 D17 = P0_31 // A3 D18 = P0_02 // A4 D19 = P0_03 // A5 D20 = P0_05 // A6 D21 = P0_16 // I2C SDA D22 = P0_14 // I2C SCL D23 = P0_20 // SPI SDI D24 = P0_15 // SPI SDO D25 = P0_13 // SPI SCK D26 = P0_19 // QSPI SCK D27 = P0_23 // QSPI CS D28 = P0_21 // QSPI Data 0 D29 = P0_22 // QSPI Data 1 D30 = P1_00 // QSPI Data 2 D31 = P0_17 // QSPI Data 3 ) // Analog Pins const ( A0 = D14 A1 = D15 A2 = D16 A3 = D17 A4 = D18 A5 = D19 A6 = D20 ) const ( LED = D3 LED1 = LED BUTTON = D4 QSPI_SCK = D26 QSPI_CS = D27 QSPI_DATA0 = D28 QSPI_DATA1 = D29 QSPI_DATA2 = D30 QSPI_DATA3 = D31 ) // UART0 pins (logical UART1) const ( UART_RX_PIN = D0 UART_TX_PIN = D1 ) // I2C pins const ( SDA_PIN = D21 // I2C0 external SCL_PIN = D22 // I2C0 external ) // SPI pins const ( SPI0_SCK_PIN = D25 SPI0_SDO_PIN = D24 SPI0_SDI_PIN = D23 ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Adafruit ItsyBitsy nRF52840 Express" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x8051 ) var ( DefaultUART = UART0 ) ================================================ FILE: src/machine/board_k210.go ================================================ //go:build maixbit // Chip datasheet: https://s3.cn-north-1.amazonaws.com.cn/dl.kendryte.com/documents/kendryte_datasheet_20181011163248_en.pdf package machine // K210 IO pins. const ( P00 Pin = 0 P01 Pin = 1 P02 Pin = 2 P03 Pin = 3 P04 Pin = 4 P05 Pin = 5 P06 Pin = 6 P07 Pin = 7 P08 Pin = 8 P09 Pin = 9 P10 Pin = 10 P11 Pin = 11 P12 Pin = 12 P13 Pin = 13 P14 Pin = 14 P15 Pin = 15 P16 Pin = 16 P17 Pin = 17 P18 Pin = 18 P19 Pin = 19 P20 Pin = 20 P21 Pin = 21 P22 Pin = 22 P23 Pin = 23 P24 Pin = 24 P25 Pin = 25 P26 Pin = 26 P27 Pin = 27 P28 Pin = 28 P29 Pin = 29 P30 Pin = 30 P31 Pin = 31 P32 Pin = 32 P33 Pin = 33 P34 Pin = 34 P35 Pin = 35 P36 Pin = 36 P37 Pin = 37 P38 Pin = 38 P39 Pin = 39 P40 Pin = 40 P41 Pin = 41 P42 Pin = 42 P43 Pin = 43 P44 Pin = 44 P45 Pin = 45 P46 Pin = 46 P47 Pin = 47 ) type FPIOAFunction uint8 // Every pin on the Kendryte K210 is assigned to an FPIOA function. // Each pin can be configured with every function below. const ( FUNC_JTAG_TCLK FPIOAFunction = 0 // JTAG Test Clock FUNC_JTAG_TDI FPIOAFunction = 1 // JTAG Test Data In FUNC_JTAG_TMS FPIOAFunction = 2 // JTAG Test Mode Select FUNC_JTAG_TDO FPIOAFunction = 3 // JTAG Test Data Out FUNC_SPI0_D0 FPIOAFunction = 4 // SPI0 Data 0 FUNC_SPI0_D1 FPIOAFunction = 5 // SPI0 Data 1 FUNC_SPI0_D2 FPIOAFunction = 6 // SPI0 Data 2 FUNC_SPI0_D3 FPIOAFunction = 7 // SPI0 Data 3 FUNC_SPI0_D4 FPIOAFunction = 8 // SPI0 Data 4 FUNC_SPI0_D5 FPIOAFunction = 9 // SPI0 Data 5 FUNC_SPI0_D6 FPIOAFunction = 10 // SPI0 Data 6 FUNC_SPI0_D7 FPIOAFunction = 11 // SPI0 Data 7 FUNC_SPI0_SS0 FPIOAFunction = 12 // SPI0 Chip Select 0 FUNC_SPI0_SS1 FPIOAFunction = 13 // SPI0 Chip Select 1 FUNC_SPI0_SS2 FPIOAFunction = 14 // SPI0 Chip Select 2 FUNC_SPI0_SS3 FPIOAFunction = 15 // SPI0 Chip Select 3 FUNC_SPI0_ARB FPIOAFunction = 16 // SPI0 Arbitration FUNC_SPI0_SCLK FPIOAFunction = 17 // SPI0 Serial Clock FUNC_UARTHS_RX FPIOAFunction = 18 // UART High speed Receiver FUNC_UARTHS_TX FPIOAFunction = 19 // UART High speed Transmitter FUNC_RESV6 FPIOAFunction = 20 // Reserved function FUNC_RESV7 FPIOAFunction = 21 // Reserved function FUNC_CLK_SPI1 FPIOAFunction = 22 // Clock SPI1 FUNC_CLK_I2C1 FPIOAFunction = 23 // Clock I2C1 FUNC_GPIOHS0 FPIOAFunction = 24 // GPIO High speed 0 FUNC_GPIOHS1 FPIOAFunction = 25 // GPIO High speed 1 FUNC_GPIOHS2 FPIOAFunction = 26 // GPIO High speed 2 FUNC_GPIOHS3 FPIOAFunction = 27 // GPIO High speed 3 FUNC_GPIOHS4 FPIOAFunction = 28 // GPIO High speed 4 FUNC_GPIOHS5 FPIOAFunction = 29 // GPIO High speed 5 FUNC_GPIOHS6 FPIOAFunction = 30 // GPIO High speed 6 FUNC_GPIOHS7 FPIOAFunction = 31 // GPIO High speed 7 FUNC_GPIOHS8 FPIOAFunction = 32 // GPIO High speed 8 FUNC_GPIOHS9 FPIOAFunction = 33 // GPIO High speed 9 FUNC_GPIOHS10 FPIOAFunction = 34 // GPIO High speed 10 FUNC_GPIOHS11 FPIOAFunction = 35 // GPIO High speed 11 FUNC_GPIOHS12 FPIOAFunction = 36 // GPIO High speed 12 FUNC_GPIOHS13 FPIOAFunction = 37 // GPIO High speed 13 FUNC_GPIOHS14 FPIOAFunction = 38 // GPIO High speed 14 FUNC_GPIOHS15 FPIOAFunction = 39 // GPIO High speed 15 FUNC_GPIOHS16 FPIOAFunction = 40 // GPIO High speed 16 FUNC_GPIOHS17 FPIOAFunction = 41 // GPIO High speed 17 FUNC_GPIOHS18 FPIOAFunction = 42 // GPIO High speed 18 FUNC_GPIOHS19 FPIOAFunction = 43 // GPIO High speed 19 FUNC_GPIOHS20 FPIOAFunction = 44 // GPIO High speed 20 FUNC_GPIOHS21 FPIOAFunction = 45 // GPIO High speed 21 FUNC_GPIOHS22 FPIOAFunction = 46 // GPIO High speed 22 FUNC_GPIOHS23 FPIOAFunction = 47 // GPIO High speed 23 FUNC_GPIOHS24 FPIOAFunction = 48 // GPIO High speed 24 FUNC_GPIOHS25 FPIOAFunction = 49 // GPIO High speed 25 FUNC_GPIOHS26 FPIOAFunction = 50 // GPIO High speed 26 FUNC_GPIOHS27 FPIOAFunction = 51 // GPIO High speed 27 FUNC_GPIOHS28 FPIOAFunction = 52 // GPIO High speed 28 FUNC_GPIOHS29 FPIOAFunction = 53 // GPIO High speed 29 FUNC_GPIOHS30 FPIOAFunction = 54 // GPIO High speed 30 FUNC_GPIOHS31 FPIOAFunction = 55 // GPIO High speed 31 FUNC_GPIO0 FPIOAFunction = 56 // GPIO pin 0 FUNC_GPIO1 FPIOAFunction = 57 // GPIO pin 1 FUNC_GPIO2 FPIOAFunction = 58 // GPIO pin 2 FUNC_GPIO3 FPIOAFunction = 59 // GPIO pin 3 FUNC_GPIO4 FPIOAFunction = 60 // GPIO pin 4 FUNC_GPIO5 FPIOAFunction = 61 // GPIO pin 5 FUNC_GPIO6 FPIOAFunction = 62 // GPIO pin 6 FUNC_GPIO7 FPIOAFunction = 63 // GPIO pin 7 FUNC_UART1_RX FPIOAFunction = 64 // UART1 Receiver FUNC_UART1_TX FPIOAFunction = 65 // UART1 Transmitter FUNC_UART2_RX FPIOAFunction = 66 // UART2 Receiver FUNC_UART2_TX FPIOAFunction = 67 // UART2 Transmitter FUNC_UART3_RX FPIOAFunction = 68 // UART3 Receiver FUNC_UART3_TX FPIOAFunction = 69 // UART3 Transmitter FUNC_SPI1_D0 FPIOAFunction = 70 // SPI1 Data 0 FUNC_SPI1_D1 FPIOAFunction = 71 // SPI1 Data 1 FUNC_SPI1_D2 FPIOAFunction = 72 // SPI1 Data 2 FUNC_SPI1_D3 FPIOAFunction = 73 // SPI1 Data 3 FUNC_SPI1_D4 FPIOAFunction = 74 // SPI1 Data 4 FUNC_SPI1_D5 FPIOAFunction = 75 // SPI1 Data 5 FUNC_SPI1_D6 FPIOAFunction = 76 // SPI1 Data 6 FUNC_SPI1_D7 FPIOAFunction = 77 // SPI1 Data 7 FUNC_SPI1_SS0 FPIOAFunction = 78 // SPI1 Chip Select 0 FUNC_SPI1_SS1 FPIOAFunction = 79 // SPI1 Chip Select 1 FUNC_SPI1_SS2 FPIOAFunction = 80 // SPI1 Chip Select 2 FUNC_SPI1_SS3 FPIOAFunction = 81 // SPI1 Chip Select 3 FUNC_SPI1_ARB FPIOAFunction = 82 // SPI1 Arbitration FUNC_SPI1_SCLK FPIOAFunction = 83 // SPI1 Serial Clock FUNC_SPI_PERIPHERAL_D0 FPIOAFunction = 84 // SPI Peripheral Data 0 FUNC_SPI_PERIPHERAL_SS FPIOAFunction = 85 // SPI Peripheral Select FUNC_SPI_PERIPHERAL_SCLK FPIOAFunction = 86 // SPI Peripheral Serial Clock FUNC_I2S0_MCLK FPIOAFunction = 87 // I2S0 Main Clock FUNC_I2S0_SCLK FPIOAFunction = 88 // I2S0 Serial Clock(BCLK) FUNC_I2S0_WS FPIOAFunction = 89 // I2S0 Word Select(LRCLK) FUNC_I2S0_IN_D0 FPIOAFunction = 90 // I2S0 Serial Data Input 0 FUNC_I2S0_IN_D1 FPIOAFunction = 91 // I2S0 Serial Data Input 1 FUNC_I2S0_IN_D2 FPIOAFunction = 92 // I2S0 Serial Data Input 2 FUNC_I2S0_IN_D3 FPIOAFunction = 93 // I2S0 Serial Data Input 3 FUNC_I2S0_OUT_D0 FPIOAFunction = 94 // I2S0 Serial Data Output 0 FUNC_I2S0_OUT_D1 FPIOAFunction = 95 // I2S0 Serial Data Output 1 FUNC_I2S0_OUT_D2 FPIOAFunction = 96 // I2S0 Serial Data Output 2 FUNC_I2S0_OUT_D3 FPIOAFunction = 97 // I2S0 Serial Data Output 3 FUNC_I2S1_MCLK FPIOAFunction = 98 // I2S1 Main Clock FUNC_I2S1_SCLK FPIOAFunction = 99 // I2S1 Serial Clock(BCLK) FUNC_I2S1_WS FPIOAFunction = 100 // I2S1 Word Select(LRCLK) FUNC_I2S1_IN_D0 FPIOAFunction = 101 // I2S1 Serial Data Input 0 FUNC_I2S1_IN_D1 FPIOAFunction = 102 // I2S1 Serial Data Input 1 FUNC_I2S1_IN_D2 FPIOAFunction = 103 // I2S1 Serial Data Input 2 FUNC_I2S1_IN_D3 FPIOAFunction = 104 // I2S1 Serial Data Input 3 FUNC_I2S1_OUT_D0 FPIOAFunction = 105 // I2S1 Serial Data Output 0 FUNC_I2S1_OUT_D1 FPIOAFunction = 106 // I2S1 Serial Data Output 1 FUNC_I2S1_OUT_D2 FPIOAFunction = 107 // I2S1 Serial Data Output 2 FUNC_I2S1_OUT_D3 FPIOAFunction = 108 // I2S1 Serial Data Output 3 FUNC_I2S2_MCLK FPIOAFunction = 109 // I2S2 Main Clock FUNC_I2S2_SCLK FPIOAFunction = 110 // I2S2 Serial Clock(BCLK) FUNC_I2S2_WS FPIOAFunction = 111 // I2S2 Word Select(LRCLK) FUNC_I2S2_IN_D0 FPIOAFunction = 112 // I2S2 Serial Data Input 0 FUNC_I2S2_IN_D1 FPIOAFunction = 113 // I2S2 Serial Data Input 1 FUNC_I2S2_IN_D2 FPIOAFunction = 114 // I2S2 Serial Data Input 2 FUNC_I2S2_IN_D3 FPIOAFunction = 115 // I2S2 Serial Data Input 3 FUNC_I2S2_OUT_D0 FPIOAFunction = 116 // I2S2 Serial Data Output 0 FUNC_I2S2_OUT_D1 FPIOAFunction = 117 // I2S2 Serial Data Output 1 FUNC_I2S2_OUT_D2 FPIOAFunction = 118 // I2S2 Serial Data Output 2 FUNC_I2S2_OUT_D3 FPIOAFunction = 119 // I2S2 Serial Data Output 3 FUNC_RESV0 FPIOAFunction = 120 // Reserved function FUNC_RESV1 FPIOAFunction = 121 // Reserved function FUNC_RESV2 FPIOAFunction = 122 // Reserved function FUNC_RESV3 FPIOAFunction = 123 // Reserved function FUNC_RESV4 FPIOAFunction = 124 // Reserved function FUNC_RESV5 FPIOAFunction = 125 // Reserved function FUNC_I2C0_SCLK FPIOAFunction = 126 // I2C0 Serial Clock FUNC_I2C0_SDA FPIOAFunction = 127 // I2C0 Serial Data FUNC_I2C1_SCLK FPIOAFunction = 128 // I2C1 Serial Clock FUNC_I2C1_SDA FPIOAFunction = 129 // I2C1 Serial Data FUNC_I2C2_SCLK FPIOAFunction = 130 // I2C2 Serial Clock FUNC_I2C2_SDA FPIOAFunction = 131 // I2C2 Serial Data FUNC_CMOS_XCLK FPIOAFunction = 132 // DVP System Clock FUNC_CMOS_RST FPIOAFunction = 133 // DVP System Reset FUNC_CMOS_PWDN FPIOAFunction = 134 // DVP Power Down Mode FUNC_CMOS_VSYNC FPIOAFunction = 135 // DVP Vertical Sync FUNC_CMOS_HREF FPIOAFunction = 136 // DVP Horizontal Reference output FUNC_CMOS_PCLK FPIOAFunction = 137 // Pixel Clock FUNC_CMOS_D0 FPIOAFunction = 138 // Data Bit 0 FUNC_CMOS_D1 FPIOAFunction = 139 // Data Bit 1 FUNC_CMOS_D2 FPIOAFunction = 140 // Data Bit 2 FUNC_CMOS_D3 FPIOAFunction = 141 // Data Bit 3 FUNC_CMOS_D4 FPIOAFunction = 142 // Data Bit 4 FUNC_CMOS_D5 FPIOAFunction = 143 // Data Bit 5 FUNC_CMOS_D6 FPIOAFunction = 144 // Data Bit 6 FUNC_CMOS_D7 FPIOAFunction = 145 // Data Bit 7 FUNC_SCCB_SCLK FPIOAFunction = 146 // SCCB Serial Clock FUNC_SCCB_SDA FPIOAFunction = 147 // SCCB Serial Data FUNC_UART1_CTS FPIOAFunction = 148 // UART1 Clear To Send FUNC_UART1_DSR FPIOAFunction = 149 // UART1 Data Set Ready FUNC_UART1_DCD FPIOAFunction = 150 // UART1 Data Carrier Detect FUNC_UART1_RI FPIOAFunction = 151 // UART1 Ring Indicator FUNC_UART1_SIR_IN FPIOAFunction = 152 // UART1 Serial Infrared Input FUNC_UART1_DTR FPIOAFunction = 153 // UART1 Data Terminal Ready FUNC_UART1_RTS FPIOAFunction = 154 // UART1 Request To Send FUNC_UART1_OUT2 FPIOAFunction = 155 // UART1 User-designated Output 2 FUNC_UART1_OUT1 FPIOAFunction = 156 // UART1 User-designated Output 1 FUNC_UART1_SIR_OUT FPIOAFunction = 157 // UART1 Serial Infrared Output FUNC_UART1_BAUD FPIOAFunction = 158 // UART1 Transmit Clock Output FUNC_UART1_RE FPIOAFunction = 159 // UART1 Receiver Output Enable FUNC_UART1_DE FPIOAFunction = 160 // UART1 Driver Output Enable FUNC_UART1_RS485_EN FPIOAFunction = 161 // UART1 RS485 Enable FUNC_UART2_CTS FPIOAFunction = 162 // UART2 Clear To Send FUNC_UART2_DSR FPIOAFunction = 163 // UART2 Data Set Ready FUNC_UART2_DCD FPIOAFunction = 164 // UART2 Data Carrier Detect FUNC_UART2_RI FPIOAFunction = 165 // UART2 Ring Indicator FUNC_UART2_SIR_IN FPIOAFunction = 166 // UART2 Serial Infrared Input FUNC_UART2_DTR FPIOAFunction = 167 // UART2 Data Terminal Ready FUNC_UART2_RTS FPIOAFunction = 168 // UART2 Request To Send FUNC_UART2_OUT2 FPIOAFunction = 169 // UART2 User-designated Output 2 FUNC_UART2_OUT1 FPIOAFunction = 170 // UART2 User-designated Output 1 FUNC_UART2_SIR_OUT FPIOAFunction = 171 // UART2 Serial Infrared Output FUNC_UART2_BAUD FPIOAFunction = 172 // UART2 Transmit Clock Output FUNC_UART2_RE FPIOAFunction = 173 // UART2 Receiver Output Enable FUNC_UART2_DE FPIOAFunction = 174 // UART2 Driver Output Enable FUNC_UART2_RS485_EN FPIOAFunction = 175 // UART2 RS485 Enable FUNC_UART3_CTS FPIOAFunction = 176 // UART3 Clear To Send FUNC_UART3_DSR FPIOAFunction = 177 // UART3 Data Set Ready FUNC_UART3_DCD FPIOAFunction = 178 // UART3 Data Carrier Detect FUNC_UART3_RI FPIOAFunction = 179 // UART3 Ring Indicator FUNC_UART3_SIR_IN FPIOAFunction = 180 // UART3 Serial Infrared Input FUNC_UART3_DTR FPIOAFunction = 181 // UART3 Data Terminal Ready FUNC_UART3_RTS FPIOAFunction = 182 // UART3 Request To Send FUNC_UART3_OUT2 FPIOAFunction = 183 // UART3 User-designated Output 2 FUNC_UART3_OUT1 FPIOAFunction = 184 // UART3 User-designated Output 1 FUNC_UART3_SIR_OUT FPIOAFunction = 185 // UART3 Serial Infrared Output FUNC_UART3_BAUD FPIOAFunction = 186 // UART3 Transmit Clock Output FUNC_UART3_RE FPIOAFunction = 187 // UART3 Receiver Output Enable FUNC_UART3_DE FPIOAFunction = 188 // UART3 Driver Output Enable FUNC_UART3_RS485_EN FPIOAFunction = 189 // UART3 RS485 Enable FUNC_TIMER0_TOGGLE1 FPIOAFunction = 190 // TIMER0 Toggle Output 1 FUNC_TIMER0_TOGGLE2 FPIOAFunction = 191 // TIMER0 Toggle Output 2 FUNC_TIMER0_TOGGLE3 FPIOAFunction = 192 // TIMER0 Toggle Output 3 FUNC_TIMER0_TOGGLE4 FPIOAFunction = 193 // TIMER0 Toggle Output 4 FUNC_TIMER1_TOGGLE1 FPIOAFunction = 194 // TIMER1 Toggle Output 1 FUNC_TIMER1_TOGGLE2 FPIOAFunction = 195 // TIMER1 Toggle Output 2 FUNC_TIMER1_TOGGLE3 FPIOAFunction = 196 // TIMER1 Toggle Output 3 FUNC_TIMER1_TOGGLE4 FPIOAFunction = 197 // TIMER1 Toggle Output 4 FUNC_TIMER2_TOGGLE1 FPIOAFunction = 198 // TIMER2 Toggle Output 1 FUNC_TIMER2_TOGGLE2 FPIOAFunction = 199 // TIMER2 Toggle Output 2 FUNC_TIMER2_TOGGLE3 FPIOAFunction = 200 // TIMER2 Toggle Output 3 FUNC_TIMER2_TOGGLE4 FPIOAFunction = 201 // TIMER2 Toggle Output 4 FUNC_CLK_SPI2 FPIOAFunction = 202 // Clock SPI2 FUNC_CLK_I2C2 FPIOAFunction = 203 // Clock I2C2 FUNC_INTERNAL0 FPIOAFunction = 204 // Internal function signal 0 FUNC_INTERNAL1 FPIOAFunction = 205 // Internal function signal 1 FUNC_INTERNAL2 FPIOAFunction = 206 // Internal function signal 2 FUNC_INTERNAL3 FPIOAFunction = 207 // Internal function signal 3 FUNC_INTERNAL4 FPIOAFunction = 208 // Internal function signal 4 FUNC_INTERNAL5 FPIOAFunction = 209 // Internal function signal 5 FUNC_INTERNAL6 FPIOAFunction = 210 // Internal function signal 6 FUNC_INTERNAL7 FPIOAFunction = 211 // Internal function signal 7 FUNC_INTERNAL8 FPIOAFunction = 212 // Internal function signal 8 FUNC_INTERNAL9 FPIOAFunction = 213 // Internal function signal 9 FUNC_INTERNAL10 FPIOAFunction = 214 // Internal function signal 10 FUNC_INTERNAL11 FPIOAFunction = 215 // Internal function signal 11 FUNC_INTERNAL12 FPIOAFunction = 216 // Internal function signal 12 FUNC_INTERNAL13 FPIOAFunction = 217 // Internal function signal 13 FUNC_INTERNAL14 FPIOAFunction = 218 // Internal function signal 14 FUNC_INTERNAL15 FPIOAFunction = 219 // Internal function signal 15 FUNC_INTERNAL16 FPIOAFunction = 220 // Internal function signal 16 FUNC_INTERNAL17 FPIOAFunction = 221 // Internal function signal 17 FUNC_CONSTANT FPIOAFunction = 222 // Constant function FUNC_INTERNAL18 FPIOAFunction = 223 // Internal function signal 18 FUNC_DEBUG0 FPIOAFunction = 224 // Debug function 0 FUNC_DEBUG1 FPIOAFunction = 225 // Debug function 1 FUNC_DEBUG2 FPIOAFunction = 226 // Debug function 2 FUNC_DEBUG3 FPIOAFunction = 227 // Debug function 3 FUNC_DEBUG4 FPIOAFunction = 228 // Debug function 4 FUNC_DEBUG5 FPIOAFunction = 229 // Debug function 5 FUNC_DEBUG6 FPIOAFunction = 230 // Debug function 6 FUNC_DEBUG7 FPIOAFunction = 231 // Debug function 7 FUNC_DEBUG8 FPIOAFunction = 232 // Debug function 8 FUNC_DEBUG9 FPIOAFunction = 233 // Debug function 9 FUNC_DEBUG10 FPIOAFunction = 234 // Debug function 10 FUNC_DEBUG11 FPIOAFunction = 235 // Debug function 11 FUNC_DEBUG12 FPIOAFunction = 236 // Debug function 12 FUNC_DEBUG13 FPIOAFunction = 237 // Debug function 13 FUNC_DEBUG14 FPIOAFunction = 238 // Debug function 14 FUNC_DEBUG15 FPIOAFunction = 239 // Debug function 15 FUNC_DEBUG16 FPIOAFunction = 240 // Debug function 16 FUNC_DEBUG17 FPIOAFunction = 241 // Debug function 17 FUNC_DEBUG18 FPIOAFunction = 242 // Debug function 18 FUNC_DEBUG19 FPIOAFunction = 243 // Debug function 19 FUNC_DEBUG20 FPIOAFunction = 244 // Debug function 20 FUNC_DEBUG21 FPIOAFunction = 245 // Debug function 21 FUNC_DEBUG22 FPIOAFunction = 246 // Debug function 22 FUNC_DEBUG23 FPIOAFunction = 247 // Debug function 23 FUNC_DEBUG24 FPIOAFunction = 248 // Debug function 24 FUNC_DEBUG25 FPIOAFunction = 249 // Debug function 25 FUNC_DEBUG26 FPIOAFunction = 250 // Debug function 26 FUNC_DEBUG27 FPIOAFunction = 251 // Debug function 27 FUNC_DEBUG28 FPIOAFunction = 252 // Debug function 28 FUNC_DEBUG29 FPIOAFunction = 253 // Debug function 29 FUNC_DEBUG30 FPIOAFunction = 254 // Debug function 30 FUNC_DEBUG31 FPIOAFunction = 255 // Debug function 31 ) // These are the default FPIOA values for each function. // (source: https://github.com/kendryte/kendryte-standalone-sdk/blob/develop/lib/drivers/fpioa.c#L69) var fpioaFuncDefaults [256]uint32 = [256]uint32{ 0x00900000, 0x00900001, 0x00900002, 0x00001f03, 0x00b03f04, 0x00b03f05, 0x00b03f06, 0x00b03f07, 0x00b03f08, 0x00b03f09, 0x00b03f0a, 0x00b03f0b, 0x00001f0c, 0x00001f0d, 0x00001f0e, 0x00001f0f, 0x03900010, 0x00001f11, 0x00900012, 0x00001f13, 0x00900014, 0x00900015, 0x00001f16, 0x00001f17, 0x00901f18, 0x00901f19, 0x00901f1a, 0x00901f1b, 0x00901f1c, 0x00901f1d, 0x00901f1e, 0x00901f1f, 0x00901f20, 0x00901f21, 0x00901f22, 0x00901f23, 0x00901f24, 0x00901f25, 0x00901f26, 0x00901f27, 0x00901f28, 0x00901f29, 0x00901f2a, 0x00901f2b, 0x00901f2c, 0x00901f2d, 0x00901f2e, 0x00901f2f, 0x00901f30, 0x00901f31, 0x00901f32, 0x00901f33, 0x00901f34, 0x00901f35, 0x00901f36, 0x00901f37, 0x00901f38, 0x00901f39, 0x00901f3a, 0x00901f3b, 0x00901f3c, 0x00901f3d, 0x00901f3e, 0x00901f3f, 0x00900040, 0x00001f41, 0x00900042, 0x00001f43, 0x00900044, 0x00001f45, 0x00b03f46, 0x00b03f47, 0x00b03f48, 0x00b03f49, 0x00b03f4a, 0x00b03f4b, 0x00b03f4c, 0x00b03f4d, 0x00001f4e, 0x00001f4f, 0x00001f50, 0x00001f51, 0x03900052, 0x00001f53, 0x00b03f54, 0x00900055, 0x00900056, 0x00001f57, 0x00001f58, 0x00001f59, 0x0090005a, 0x0090005b, 0x0090005c, 0x0090005d, 0x00001f5e, 0x00001f5f, 0x00001f60, 0x00001f61, 0x00001f62, 0x00001f63, 0x00001f64, 0x00900065, 0x00900066, 0x00900067, 0x00900068, 0x00001f69, 0x00001f6a, 0x00001f6b, 0x00001f6c, 0x00001f6d, 0x00001f6e, 0x00001f6f, 0x00900070, 0x00900071, 0x00900072, 0x00900073, 0x00001f74, 0x00001f75, 0x00001f76, 0x00001f77, 0x00000078, 0x00000079, 0x0000007a, 0x0000007b, 0x0000007c, 0x0000007d, 0x0099107e, 0x0099107f, 0x00991080, 0x00991081, 0x00991082, 0x00991083, 0x00001f84, 0x00001f85, 0x00001f86, 0x00900087, 0x00900088, 0x00900089, 0x0090008a, 0x0090008b, 0x0090008c, 0x0090008d, 0x0090008e, 0x0090008f, 0x00900090, 0x00900091, 0x00993092, 0x00993093, 0x00900094, 0x00900095, 0x00900096, 0x00900097, 0x00900098, 0x00001f99, 0x00001f9a, 0x00001f9b, 0x00001f9c, 0x00001f9d, 0x00001f9e, 0x00001f9f, 0x00001fa0, 0x00001fa1, 0x009000a2, 0x009000a3, 0x009000a4, 0x009000a5, 0x009000a6, 0x00001fa7, 0x00001fa8, 0x00001fa9, 0x00001faa, 0x00001fab, 0x00001fac, 0x00001fad, 0x00001fae, 0x00001faf, 0x009000b0, 0x009000b1, 0x009000b2, 0x009000b3, 0x009000b4, 0x00001fb5, 0x00001fb6, 0x00001fb7, 0x00001fb8, 0x00001fb9, 0x00001fba, 0x00001fbb, 0x00001fbc, 0x00001fbd, 0x00001fbe, 0x00001fbf, 0x00001fc0, 0x00001fc1, 0x00001fc2, 0x00001fc3, 0x00001fc4, 0x00001fc5, 0x00001fc6, 0x00001fc7, 0x00001fc8, 0x00001fc9, 0x00001fca, 0x00001fcb, 0x00001fcc, 0x00001fcd, 0x00001fce, 0x00001fcf, 0x00001fd0, 0x00001fd1, 0x00001fd2, 0x00001fd3, 0x00001fd4, 0x009000d5, 0x009000d6, 0x009000d7, 0x009000d8, 0x009100d9, 0x00991fda, 0x009000db, 0x009000dc, 0x009000dd, 0x000000de, 0x009000df, 0x00001fe0, 0x00001fe1, 0x00001fe2, 0x00001fe3, 0x00001fe4, 0x00001fe5, 0x00001fe6, 0x00001fe7, 0x00001fe8, 0x00001fe9, 0x00001fea, 0x00001feb, 0x00001fec, 0x00001fed, 0x00001fee, 0x00001fef, 0x00001ff0, 0x00001ff1, 0x00001ff2, 0x00001ff3, 0x00001ff4, 0x00001ff5, 0x00001ff6, 0x00001ff7, 0x00001ff8, 0x00001ff9, 0x00001ffa, 0x00001ffb, 0x00001ffc, 0x00001ffd, 0x00001ffe, 0x00001fff, } ================================================ FILE: src/machine/board_kb2040.go ================================================ //go:build kb2040 package machine // Onboard crystal oscillator frequency, in MHz. const xoscFreq = 12 // MHz // GPIO Pins const ( D0 = GPIO0 D1 = GPIO1 D2 = GPIO2 D3 = GPIO3 D4 = GPIO4 D5 = GPIO5 D6 = GPIO6 D7 = GPIO7 D8 = GPIO8 D9 = GPIO9 D10 = GPIO10 ) // Analog pins const ( A0 = GPIO26 A1 = GPIO27 A2 = GPIO28 A3 = GPIO29 ) // Note: there is no user-controllable LED on the KB2040 board // const LED = notConnected // I2C Pins. const ( I2C0_SDA_PIN = GPIO12 I2C0_SCL_PIN = GPIO13 I2C1_SDA_PIN = GPIO2 I2C1_SCL_PIN = GPIO3 SDA_PIN = I2C0_SDA_PIN SCL_PIN = I2C0_SCL_PIN ) // SPI default pins const ( // Default Serial Clock Bus 0 for SPI communications SPI0_SCK_PIN = GPIO18 // Default Serial Out Bus 0 for SPI communications SPI0_SDO_PIN = GPIO19 // Tx // Default Serial In Bus 0 for SPI communications SPI0_SDI_PIN = GPIO20 // Rx // Default Serial Clock Bus 1 for SPI communications SPI1_SCK_PIN = GPIO26 // Default Serial Out Bus 1 for SPI communications SPI1_SDO_PIN = GPIO27 // Tx // Default Serial In Bus 1 for SPI communications SPI1_SDI_PIN = GPIO28 // Rx ) // UART pins const ( UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART1_TX_PIN = GPIO8 UART1_RX_PIN = GPIO9 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) var DefaultUART = UART0 // USB identifiers const ( usb_STRING_PRODUCT = "KB2040" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x8106 ) ================================================ FILE: src/machine/board_lgt92.go ================================================ //go:build lgt92 package machine import ( "device/stm32" "runtime/interrupt" ) const ( LED1 = PA12 LED2 = PA8 LED3 = PA11 LED_RED = LED1 LED_BLUE = LED2 LED_GREEN = LED3 // Default led LED = LED1 BUTTON = PB14 // LG GPS module GPS_STANDBY_PIN = PB3 GPS_RESET_PIN = PB4 GPS_POWER_PIN = PB5 MEMS_ACCEL_CS = PE3 MEMS_ACCEL_INT1 = PE0 MEMS_ACCEL_INT2 = PE1 // SPI SPI1_SCK_PIN = PA5 SPI1_SDI_PIN = PA6 SPI1_SDO_PIN = PA7 SPI0_SCK_PIN = SPI1_SCK_PIN SPI0_SDI_PIN = SPI1_SDI_PIN SPI0_SDO_PIN = SPI1_SDO_PIN // LORA RFM95 Radio RFM95_DIO0_PIN = PC13 // TinyGo UART is MCU LPUSART1 UART_RX_PIN = PA13 UART_TX_PIN = PA14 // TinyGo UART1 is MCU USART1 UART1_RX_PIN = PB6 UART1_TX_PIN = PB7 // MPU9250 Nine-Axis (Gyro + Accelerometer + Compass) I2C0_SCL_PIN = PA9 I2C0_SDA_PIN = PA10 ) var DefaultUART = UART0 var ( // Console UART (LPUSART1) UART0 = &_UART0 _UART0 = UART{ Buffer: NewRingBuffer(), Bus: stm32.LPUART1, TxAltFuncSelector: 6, RxAltFuncSelector: 6, } // Gps UART UART1 = &_UART1 _UART1 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART1, TxAltFuncSelector: 0, RxAltFuncSelector: 0, } // MPU9250 Nine-Axis (Gyro + Accelerometer + Compass) I2C1 = &I2C{ Bus: stm32.I2C1, AltFuncSelector: 6, } I2C0 = I2C1 // SPI SPI0 = &SPI{ Bus: stm32.SPI1, } SPI1 = SPI0 ) func init() { // Enable UARTs Interrupts UART0.Interrupt = interrupt.New(stm32.IRQ_AES_RNG_LPUART1, _UART0.handleInterrupt) UART1.Interrupt = interrupt.New(stm32.IRQ_USART1, _UART1.handleInterrupt) } ================================================ FILE: src/machine/board_lorae5.go ================================================ //go:build lorae5 package machine import ( "device/stm32" "runtime/interrupt" ) const ( // We assume a LED is connected on PB5 LED = PB5 // Default LED // Set the POWER_EN3V3 pin to high to turn // on the 3.3V power for all peripherals POWER_EN3V3 = PA9 // Set the POWER_EN5V pin to high to turn // on the 5V bus power for all peripherals POWER_EN5V = PB10 ) // SubGhz (SPI3) const ( SPI0_NSS_PIN = PA4 SPI0_SCK_PIN = PA5 SPI0_SDO_PIN = PA6 SPI0_SDI_PIN = PA7 ) // UARTS const ( // MCU USART1 UART1_TX_PIN = PB6 UART1_RX_PIN = PB7 // MCU USART2 UART2_TX_PIN = PA2 UART2_RX_PIN = PA3 // DEFAULT USART UART_TX_PIN = UART1_TX_PIN UART_RX_PIN = UART1_RX_PIN // I2C2 pins I2C2_SCL_PIN = PB15 I2C2_SDA_PIN = PA15 I2C2_ALT_FUNC = 4 // I2C0 alias for I2C2 I2C0_SDA_PIN = I2C2_SDA_PIN I2C0_SCL_PIN = I2C2_SCL_PIN ) var ( // Console UART UART0 = &_UART0 _UART0 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART1, TxAltFuncSelector: AF7_USART1_2, RxAltFuncSelector: AF7_USART1_2, } DefaultUART = UART0 // Since we treat UART1 as zero, let's also call it by the real name UART1 = UART0 // UART2 UART2 = &_UART2 _UART2 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART2, TxAltFuncSelector: AF7_USART1_2, RxAltFuncSelector: AF7_USART1_2, } // I2C Busses I2C2 = &I2C{ Bus: stm32.I2C2, AltFuncSelector: I2C2_ALT_FUNC, } // Set "default" I2C bus to I2C2 I2C0 = I2C2 // SPI SPI3 = &SPI{ Bus: stm32.SPI3, } ) func init() { // Enable UARTs Interrupts UART0.Interrupt = interrupt.New(stm32.IRQ_USART1, _UART0.handleInterrupt) UART2.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART2.handleInterrupt) } ================================================ FILE: src/machine/board_m5paper.go ================================================ //go:build m5paper package machine const ( IO0 = GPIO0 IO1 = GPIO1 IO2 = GPIO2 IO3 = GPIO3 IO4 = GPIO4 IO5 = GPIO5 IO6 = GPIO6 IO7 = GPIO7 IO8 = GPIO8 IO9 = GPIO9 IO10 = GPIO10 IO11 = GPIO11 IO12 = GPIO12 IO13 = GPIO13 IO14 = GPIO14 IO15 = GPIO15 IO16 = GPIO16 IO17 = GPIO17 IO18 = GPIO18 IO19 = GPIO19 IO21 = GPIO21 IO22 = GPIO22 IO23 = GPIO23 IO25 = GPIO25 IO26 = GPIO26 IO27 = GPIO27 IO32 = GPIO32 IO33 = GPIO33 IO34 = GPIO34 IO35 = GPIO35 IO36 = GPIO36 IO37 = GPIO37 IO38 = GPIO38 IO39 = GPIO39 ) const ( POWER_PIN = IO2 EXT_POWER_PIN = IO5 EPD_POWER_PIN = IO23 // Buttons BUTTON_RIGHT = IO39 BUTTON_PUSH = IO38 BUTTON_LEFT = IO37 BUTTON = BUTTON_PUSH // Touch Screen Interrupt TOUCH_INT = IO36 ) // SPI pins const ( SPI0_SCK_PIN = IO14 SPI0_SDO_PIN = IO12 SPI0_SDI_PIN = IO13 // EPD (IT8951) EPD_SCK_PIN = SPI0_SCK_PIN EPD_SDO_PIN = SPI0_SDO_PIN EPD_SDI_PIN = SPI0_SDI_PIN EPD_CS_PIN = IO15 EPD_BUSY_PIN = IO27 // SD CARD SDCARD_SCK_PIN = SPI0_SCK_PIN SDCARD_SDO_PIN = SPI0_SDO_PIN SDCARD_SDI_PIN = SPI0_SDI_PIN SDCARD_CS_PIN = IO4 ) // I2C pins const ( SDA0_PIN = IO21 SCL0_PIN = IO22 SDA_PIN = SDA0_PIN SCL_PIN = SCL0_PIN I2C_TEMP_ADDR = 0x44 // temperature sensor (SHT30) I2C_CLOCK_ADDR = 0x51 // real time clock (BM8563) I2C_TOUCH_ADDR = 0x5D // touch screen controller (GT911) ) // ADC pins const ( ADC1 Pin = IO35 ADC2 Pin = IO36 BATTERY_ADC_PIN = ADC1 ) // DAC pins const ( DAC1 Pin = IO25 DAC2 Pin = IO26 ) // UART pins const ( // UART0 (CP2104) UART0_TX_PIN = IO1 UART0_RX_PIN = IO3 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) ================================================ FILE: src/machine/board_m5stack.go ================================================ //go:build m5stack package machine const ( // GND | ADC G35 // GND | ADC G36 // GND | RST EN // G23 MOSI | DAC/SPK G25 // G19 MISO | DAC G26 // G18 SCK | 3.3V // G3 RXD1 | TXD1 G1 // G16 RXD2 | TXD2 G17 // G21 SDA | DCL G22 // G2 GPIO | GPIO G5 // G12 IIS_SK | IIS_WS G13 // G15 IIS_OUT | IIS_MK G0 // HPWR | IIS_IN G34 // HPWR | 5V // HPWR | BATTERY IO0 = GPIO0 IO1 = GPIO1 IO2 = GPIO2 IO3 = GPIO3 IO4 = GPIO4 IO5 = GPIO5 IO6 = GPIO6 IO7 = GPIO7 IO8 = GPIO8 IO9 = GPIO9 IO10 = GPIO10 IO11 = GPIO11 IO12 = GPIO12 IO13 = GPIO13 IO14 = GPIO14 IO15 = GPIO15 IO16 = GPIO16 IO17 = GPIO17 IO18 = GPIO18 IO19 = GPIO19 IO21 = GPIO21 IO22 = GPIO22 IO23 = GPIO23 IO25 = GPIO25 IO26 = GPIO26 IO27 = GPIO27 IO32 = GPIO32 IO33 = GPIO33 IO34 = GPIO34 IO35 = GPIO35 IO36 = GPIO36 IO37 = GPIO37 IO38 = GPIO38 IO39 = GPIO39 ) const ( // Buttons BUTTON_A = IO39 BUTTON_B = IO38 BUTTON_C = IO37 BUTTON = BUTTON_A // Speaker SPEAKER_PIN = IO25 ) // SPI pins const ( SPI0_SCK_PIN = IO18 SPI0_SDO_PIN = IO23 SPI0_SDI_PIN = IO19 SPI0_CS0_PIN = IO14 // LCD (ILI9342C) LCD_SCK_PIN = SPI0_SCK_PIN LCD_SDO_PIN = SPI0_SDO_PIN LCD_SDI_PIN = SPI0_SDI_PIN // NoPin ? LCD_SS_PIN = SPI0_CS0_PIN LCD_DC_PIN = IO27 LCD_RST_PIN = IO33 LCD_BL_PIN = IO32 // SD CARD SDCARD_SCK_PIN = SPI0_SCK_PIN SDCARD_SDO_PIN = SPI0_SDO_PIN SDCARD_SDI_PIN = SPI0_SDI_PIN SDCARD_SS_PIN = IO4 ) // I2C pins const ( SDA0_PIN = IO21 SCL0_PIN = IO22 SDA_PIN = SDA0_PIN SCL_PIN = SCL0_PIN ) // ADC pins const ( ADC1 Pin = IO35 ADC2 Pin = IO36 ) // DAC pins const ( DAC1 Pin = IO25 DAC2 Pin = IO26 ) // UART pins const ( // UART0 (CP2104) UART0_TX_PIN = IO1 UART0_RX_PIN = IO3 UART1_TX_PIN = IO17 UART1_RX_PIN = IO16 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) ================================================ FILE: src/machine/board_m5stack_core2.go ================================================ //go:build m5stack_core2 package machine const ( // GND | ADC G35 // GND | ADC G36 // GND | RST EN // G23 MOSI | DAC G25 // G38 MISO | DAC G26 // G18 SCK | 3.3V // G3 RXD0 | TXD0 G1 // G13 RXD2 | TXD2 G14 // G21 intSDA | intSC G22 // G32 PA_SDA | PA_SCL G33 // G27 GPIO | GPIO G19 // G2 I2S_DOUT | I2S_LRCKC G0 // N/C | PDM_DAT G34 // N/C | 5V // N/C | BAT IO0 = GPIO0 IO1 = GPIO1 // U0TXD IO2 = GPIO2 IO3 = GPIO3 // U0RXD IO4 = GPIO4 IO5 = GPIO5 IO6 = GPIO6 // SD_CLK IO7 = GPIO7 // SD_DATA0 IO8 = GPIO8 // SD_DATA1 IO9 = GPIO9 // SD_DATA2 IO10 = GPIO10 // SD_DATA3 IO11 = GPIO11 // SD_CMD IO12 = GPIO12 IO13 = GPIO13 // U0RXD IO14 = GPIO14 // U1TXD IO15 = GPIO15 IO16 = GPIO16 IO17 = GPIO17 IO18 = GPIO18 // SPI0_SCK IO19 = GPIO19 IO21 = GPIO21 // SDA0 IO22 = GPIO22 // SCL0 IO23 = GPIO23 // SPI0_SDO IO25 = GPIO25 IO26 = GPIO26 IO27 = GPIO27 IO32 = GPIO32 // SDA1 IO33 = GPIO33 // SCL1 IO34 = GPIO34 IO35 = GPIO35 // ADC1 IO36 = GPIO36 // ADC2 IO38 = GPIO38 // SPI0_SDI IO39 = GPIO39 ) // SPI pins const ( SPI0_SCK_PIN = IO18 SPI0_SDO_PIN = IO23 SPI0_SDI_PIN = IO38 SPI0_CS0_PIN = IO5 // LCD (ILI9342C) LCD_SCK_PIN = SPI0_SCK_PIN LCD_SDO_PIN = SPI0_SDO_PIN LCD_SDI_PIN = SPI0_SDI_PIN LCD_SS_PIN = SPI0_CS0_PIN LCD_DC_PIN = IO15 // SD CARD SDCARD_SCK_PIN = SPI0_SCK_PIN SDCARD_SDO_PIN = SPI0_SDO_PIN SDCARD_SDI_PIN = SPI0_SDI_PIN SDCARD_SS_PIN = IO4 ) // I2C pins const ( // Internal I2C (AXP192 / FT6336U / BM8563 / MPU6886) SDA0_PIN = IO21 SCL0_PIN = IO22 // External I2C (PORT A) SDA1_PIN = IO32 SCL1_PIN = IO33 SDA_PIN = SDA1_PIN SCL_PIN = SCL1_PIN ) // ADC pins const ( ADC1 Pin = IO35 ADC2 Pin = IO36 ) // DAC pins const ( DAC1 Pin = IO25 DAC2 Pin = IO26 ) // UART pins const ( // UART0 (CP2104) UART0_TX_PIN = IO1 UART0_RX_PIN = IO3 UART1_TX_PIN = IO14 UART1_RX_PIN = IO13 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) ================================================ FILE: src/machine/board_m5stamp_c3.go ================================================ //go:build m5stamp_c3 package machine const ( IO0 = GPIO0 IO1 = GPIO1 IO2 = GPIO2 IO3 = GPIO3 IO4 = GPIO4 IO5 = GPIO5 IO6 = GPIO6 IO7 = GPIO7 IO8 = GPIO8 IO9 = GPIO9 IO10 = GPIO10 IO11 = GPIO11 IO12 = GPIO12 IO13 = GPIO13 IO14 = GPIO14 IO15 = GPIO15 IO16 = GPIO16 IO17 = GPIO17 IO18 = GPIO18 IO19 = GPIO19 IO20 = GPIO20 IO21 = GPIO21 XTAL_32K_P = IO0 XTAL_32K_N = IO1 MTMS = IO4 MTDI = IO5 MTCK = IO6 MTDO = IO7 VDD_SPI = IO11 SPIHD = IO12 SPISP = IO13 SPICS0 = IO14 SPICLK = IO15 SPID = IO16 SPIQ = IO17 U0RXD = IO20 U0TXD = IO21 UART_TX_PIN = U0TXD UART_RX_PIN = U0RXD ) const ( WS2812 = IO2 ) ================================================ FILE: src/machine/board_m5stick_c.go ================================================ //go:build m5stick_c // This file contains the pin mapping for the M5STACK M5Stick-C device. // doc: https://docs.m5stack.com/en/core/m5stickc package machine const ( // HAT | HY2.0-4P // | // GND | GND // 5V OUT | VOUT // G26 | G32 SDA // G36 | G33 SCL // G0 | // BAT | // 3V3 | // 5V IN | IO0 = GPIO0 // CLK IO1 = GPIO1 // U0TXD IO2 = GPIO2 IO3 = GPIO3 // U0RXD IO4 = GPIO4 IO5 = GPIO5 // TFT_CS LCD_SS SPI0_SS IO6 = GPIO6 IO7 = GPIO7 IO8 = GPIO8 IO9 = GPIO9 // IR IO10 = GPIO10 // LED IO11 = GPIO11 IO12 = GPIO12 IO13 = GPIO13 // TFT_CLK LCD_SCK SPI0_SCK IO14 = GPIO14 IO15 = GPIO15 // TFT_MOSI LCD_MOSI SPI0_MOSI IO16 = GPIO16 IO17 = GPIO17 IO18 = GPIO18 // TFT_RST LCD_RST IO19 = GPIO19 IO21 = GPIO21 // SDA0 IO22 = GPIO22 // SCL0 IO23 = GPIO23 // TFT_DC LCD_DC IO25 = GPIO25 // - DAC1 IO26 = GPIO26 // HAT DAC2 IO27 = GPIO27 IO32 = GPIO32 // SDA1 / PIN 32 / RXD2 IO33 = GPIO33 // SCL1 / PIN 33 / TXD2 IO34 = GPIO34 // MIC_DATA IO35 = GPIO35 // IRQ0 ADC1 IO36 = GPIO36 // HAT ADC2 LCD_MISO SPI0_MISO IO37 = GPIO37 // BUTTON_A, BUTTON_HOME, BUTTON IO38 = GPIO38 IO39 = GPIO39 // BUTTON_B, BUTTON_RST ) const ( // Buttons BUTTON_A = IO37 BUTTON_B = IO39 BUTTON_HOME = BUTTON_A BUTTON_RST = BUTTON_B BUTTON = BUTTON_A // LED IR = IO9 LED = IO10 ) // SPI pins const ( SPI0_SCK_PIN = IO13 SPI0_SDO_PIN = IO15 SPI0_CS0_PIN = IO5 // LCD () LCD_SCK_PIN = SPI0_SCK_PIN LCD_SDO_PIN = SPI0_SDO_PIN LCD_SS_PIN = SPI0_CS0_PIN LCD_DC_PIN = IO23 LCD_RST_PIN = IO18 ) // I2C pins const ( // Internal I2C (AXP192 / BM8563 / MPU6886) SDA0_PIN = IO21 SCL0_PIN = IO22 // External I2C (GROOVE PORT) SDA1_PIN = IO32 SCL1_PIN = IO33 SDA_PIN = SDA1_PIN SCL_PIN = SCL1_PIN ) // ADC pins const ( ADC1 Pin = IO35 ADC2 Pin = IO36 ) // DAC pins const ( DAC1 Pin = IO25 DAC2 Pin = IO26 ) // UART pins const ( // UART0 (CP2104) UART0_TX_PIN = IO1 UART0_RX_PIN = IO3 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) ================================================ FILE: src/machine/board_macropad-rp2040.go ================================================ //go:build macropad_rp2040 package machine const ( NeopixelCount = 12 // Onboard crystal oscillator frequency, in MHz. xoscFreq = 12 // MHz ) const ( SWITCH = GPIO0 BUTTON = GPIO0 KEY1 = GPIO1 KEY2 = GPIO2 KEY3 = GPIO3 KEY4 = GPIO4 KEY5 = GPIO5 KEY6 = GPIO6 KEY7 = GPIO7 KEY8 = GPIO8 KEY9 = GPIO9 KEY10 = GPIO10 KEY11 = GPIO11 KEY12 = GPIO12 LED = GPIO13 SPEAKER_ENABLE = GPIO14 SPEAKER = GPIO16 ROT_A = GPIO18 ROT_B = GPIO17 OLED_CS = GPIO22 OLED_RST = GPIO23 OLED_DC = GPIO24 NEOPIXEL = GPIO19 WS2812 = NEOPIXEL ) // I2C Default pins on Raspberry Pico. const ( I2C0_SDA_PIN = GPIO20 I2C0_SCL_PIN = GPIO21 I2C1_SDA_PIN = NoPin // not pinned out I2C1_SCL_PIN = NoPin // not pinned out ) // SPI default pins const ( // Default Serial Clock Bus 1 for SPI communications SPI1_SCK_PIN = GPIO26 // Default Serial Out Bus 1 for SPI communications SPI1_SDO_PIN = GPIO27 // Tx // Default Serial In Bus 1 for SPI communications SPI1_SDI_PIN = GPIO28 // Rx SPI0_SCK_PIN = NoPin // not pinned out SPI0_SDO_PIN = NoPin // not pinned out SPI0_SDI_PIN = NoPin // not pinned out ) // UART pins const ( UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) var DefaultUART = UART0 // USB identifiers const ( usb_STRING_PRODUCT = "MacroPad RP2040" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x8107 ) ================================================ FILE: src/machine/board_maixbit.go ================================================ //go:build maixbit package machine // Pins on the MAix Bit. const ( D0 = P00 // JTAG_TCLK D1 = P01 // JTAG_TDI D2 = P02 // JTAG_TMS D3 = P03 // JTAG_TDO D4 = P04 // UARTHS_RX D5 = P05 // UARTHS_TX D6 = P06 // RESV0 D7 = P07 // RESV0 D8 = P08 // GPIO1 D9 = P09 // GPIO2 D10 = P10 // GPIO3 D11 = P11 // GPIO4 D12 = P12 // GPIO5 D13 = P13 // GPIO6 D14 = P14 // GPIO7 D15 = P15 // GPIO8 D16 = P16 // GPIOHS0 D17 = P17 // GPIOHS1 D18 = P18 // GPIOHS2 D19 = P19 // GPIOHS3 D20 = P20 // GPIOHS4 D21 = P21 // GPIOHS5 D22 = P22 // GPIOHS6 D23 = P23 // GPIOHS7 D24 = P24 // GPIOHS8 D25 = P25 // GPIOHS9 D26 = P26 // GPIOHS10 / SPI0_SDI D27 = P27 // GPIOHS11 / SPI0_SCLK D28 = P28 // GPIOHS12 / SPI0_SDO D29 = P29 // GPIOHS13 D30 = P30 // GPIOHS14 D31 = P31 // GPIOHS15 D32 = P32 // GPIOHS16 D33 = P33 // GPIOHS17 D34 = P34 // GPIOHS18 D35 = P35 // GPIOHS19 ) const ( LED = LED1 LED1 = LED_RED LED2 = LED_GREEN LED3 = LED_BLUE LED_RED = D13 LED_GREEN = D12 LED_BLUE = D14 ) var DefaultUART = UART0 // Default pins for UARTHS. const ( UART_TX_PIN = D5 UART_RX_PIN = D4 ) // SPI pins. const ( SPI0_SCK_PIN = D27 SPI0_SDO_PIN = D28 SPI0_SDI_PIN = D26 ) // I2C pins. const ( I2C0_SDA_PIN = D34 I2C0_SCL_PIN = D35 ) ================================================ FILE: src/machine/board_maixbit_baremetal.go ================================================ //go:build k210 && maixbit package machine import "device/kendryte" // SPI on the MAix Bit. var ( SPI0 = &SPI{ Bus: kendryte.SPI0, } SPI1 = &SPI{ Bus: kendryte.SPI1, } ) ================================================ FILE: src/machine/board_makerfabs-esp32c3spi35.go ================================================ //go:build makerfabs_esp32c3spi35 // This file contains the pin mappings for the Makerfabs ESP32C3SPI35 board. // // The Makerfabs ESP32C3SPI35 is an LCD Touchscreen development board powered // by the Espressif ESP32-C3 SoC featuring an open-source RISC-V architecture. // // Specifications: // SoC: ESP32-C3-MINI-1-N4, 4MB Flash, RISCV-32bit, 160MHz, 400KB SRAM // Wireless: WiFi & Bluetooth 5.0 (BLE) // LCD: 3.5inch TFT LCD (480x320) // LCD Driver: ILI9488 SPI // Touch Panel: Capacitive // Touch Panel Driver: FT6236 // MicroSD Card Slot // Mabee Interface // Dual USB Type-C (one for USB-to-UART and one for native USB) // // Website: https://www.makerfabs.com/ep32-c3-risc-v-spi-tft-touch.html // Wiki: https://wiki.makerfabs.com/ESP32_C3_SPI_3.5_TFT_with_Touch.html // GitHub: https://github.com/Makerfabs/Makerfabs-ESP32-C3-SPI-TFT-with-Touch // Schematic: https://github.com/Makerfabs/Makerfabs-ESP32-C3-SPI-TFT-with-Touch/raw/main/Hardware/ESP32-C3%20TFT%20Touch%20v1.1(3.5''%20ili9488).PDF // Datasheet: https://www.espressif.com/sites/default/files/documentation/esp32-c3-mini-1_datasheet_en.pdf package machine // Digital pins const ( // Pin // Function // ----- // --------------------- D0 = GPIO0 // Touchscreen CS D1 = GPIO1 // MicroSD CS D2 = GPIO2 // I2C SDA D3 = GPIO3 // I2C SCL D4 = GPIO4 // SPI CS D5 = GPIO5 // SPI SCK D6 = GPIO6 // SPI SDO D7 = GPIO7 // SPI SDI D8 = GPIO8 // Touchscreen Backlight D9 = GPIO9 // Boot Button D10 = GPIO10 // TFT D/C D18 = GPIO18 // USB DM D19 = GPIO19 // USB DP D20 = GPIO20 // UART RX D21 = GPIO21 // UART TX ) // Button pin const ( BUTTON = BUTTON_BOOT BUTTON_BOOT = D9 ) // TFT pins const ( TFT_BL_PIN = D8 TFT_CS_PIN = SPI_CS_PIN TFT_DC_PIN = D10 TFT_SCK_PIN = SPI_SCK_PIN TFT_SDI_PIN = SPI_SDI_PIN TFT_SDO_PIN = SPI_SDO_PIN ) // Touchscreen pins const ( TS_CS_PIN = D0 TS_SDA_PIN = SDA_PIN TS_SCL_PIN = SCL_PIN ) // MicroSD pins const ( SD_CS_PIN = D1 SD_SCK_PIN = SPI_SCK_PIN SD_SDI_PIN = SPI_SDI_PIN SD_SDO_PIN = SPI_SDO_PIN ) // USBCDC pins const ( USBCDC_DM_PIN = D18 USBCDC_DP_PIN = D19 ) // UART pins const ( UART_RX_PIN = D20 UART_TX_PIN = D21 ) // I2C pins const ( SDA_PIN = D2 SCL_PIN = D3 ) // SPI pins const ( SPI_CS_PIN = D4 SPI_SCK_PIN = D5 SPI_SDI_PIN = D7 SPI_SDO_PIN = D6 ) ================================================ FILE: src/machine/board_matrixportal-m4.go ================================================ //go:build matrixportal_m4 package machine // used to reset into bootloader const resetMagicValue = 0xF01669EF // Digital pins const ( // Pin // Function SERCOM PWM Interrupt // ---- // ---------------- ------ --- --------- D0 = PA01 // UART RX 1[1] PWM EXTI1 D1 = PA00 // UART TX 1[0] PWM EXTI0 D2 = PB22 // Button "Up" EXTI6 D3 = PB23 // Button "Down" EXTI7 D4 = PA23 // NeoPixel EXTI7 D5 = PB31 // I2C SDA 5[1] EXTI15 D6 = PB30 // I2C SCL 5[0] EXTI14 D7 = PB00 // HUB75 R1 EXTI0 D8 = PB01 // HUB75 G1 EXTI1 D9 = PB02 // HUB75 B1 EXTI2 D10 = PB03 // HUB75 R2 EXTI3 D11 = PB04 // HUB75 G2 EXTI4 D12 = PB05 // HUB75 B2 EXTI5 D13 = PA14 // LED PWM EXTI14 D14 = PB06 // HUB75 CLK EXTI6 D15 = PB14 // HUB75 LAT EXTI14 D16 = PB12 // HUB75 OE EXTI12 D17 = PB07 // HUB75 ADDR A EXTI7 D18 = PB08 // HUB75 ADDR B EXTI8 D19 = PB09 // HUB75 ADDR C EXTI9 D20 = PB15 // HUB75 ADDR D EXTI15 D21 = PB13 // HUB75 ADDR E EXTI13 D22 = PA02 // ADC (A0) EXTI2 D23 = PA05 // ADC (A1) EXTI5 D24 = PA04 // ADC (A2) PWM EXTI4 D25 = PA06 // ADC (A3) PWM EXTI6 D26 = PA07 // ADC (A4) EXTI7 D27 = PA12 // ESP32 UART RX 4[1] PWM EXTI12 D28 = PA13 // ESP32 UART TX 4[0] PWM EXTI13 D29 = PA20 // ESP32 GPIO0 PWM EXTI4 D30 = PA21 // ESP32 Reset PWM EXTI5 D31 = PA22 // ESP32 Busy PWM EXTI6 D32 = PA18 // ESP32 RTS PWM EXTI2 D33 = PB17 // ESP32 SPI CS PWM EXTI1 D34 = PA16 // ESP32 SPI SCK 3[1] PWM EXTI0 D35 = PA17 // ESP32 SPI SDI 3[0] PWM EXTI1 D36 = PA19 // ESP32 SPI SDO 1[3] PWM EXTI3 D37 = NoPin // USB Host enable D38 = PA24 // USB DM D39 = PA25 // USB DP D40 = PA03 // DAC/VREFP D41 = PB10 // Flash QSPI SCK D42 = PB11 // Flash QSPI CS D43 = PA08 // Flash QSPI I00 D44 = PA09 // Flash QSPI IO1 D45 = PA10 // Flash QSPI IO2 D46 = PA11 // Flash QSPI IO3 D47 = PA27 // LIS3DH IRQ EXTI11 D48 = PA05 // SPI SCK 0[1] EXTI5 D49 = PA04 // SPI SDO 0[0] PWM EXTI4 D50 = PA07 // SPI SDI 0[3] EXTI7 ) // Analog pins const ( A0 = PA02 // ADC Channel 0 A1 = PA05 // ADC Channel 5 A2 = PA04 // ADC Channel 4 A3 = PA06 // ADC Channel 6 A4 = PA07 // ADC Channel 7 ) // LED pins const ( LED = D13 NEOPIXEL = D4 WS2812 = D4 ) // Button pins const ( BUTTON_UP = D2 BUTTON_DOWN = D3 ) // UART pins const ( UART1_RX_PIN = D0 // SERCOM1[1] UART1_TX_PIN = D1 // SERCOM1[0] UART2_RX_PIN = D27 // SERCOM4[1] (ESP32 RX) UART2_TX_PIN = D28 // SERCOM4[0] (ESP32 TX) UART_RX_PIN = UART1_RX_PIN UART_TX_PIN = UART1_TX_PIN ) // UART on the MatrixPortal M4 var ( UART1 = &sercomUSART1 UART2 = &sercomUSART4 DefaultUART = UART1 ) // SPI pins const ( SPI0_SCK_PIN = D34 // SERCOM3[1] (ESP32 SCK) SPI0_SDO_PIN = D36 // SERCOM1[3] (ESP32 SDO) SPI0_SDI_PIN = D35 // SERCOM3[0] (ESP32 SDI) SPI1_SCK_PIN = D48 // SERCOM0[1] SPI1_SDO_PIN = D49 // SERCOM0[0] SPI1_SDI_PIN = D50 // SERCOM0[3] SPI_SCK_PIN = SPI0_SCK_PIN SPI_SDO_PIN = SPI0_SDO_PIN SPI_SDI_PIN = SPI0_SDI_PIN ) // I2C pins const ( I2C0_SDA_PIN = D5 // SERCOM5[1] I2C0_SCL_PIN = D6 // SERCOM5[0] I2C_SDA_PIN = I2C0_SDA_PIN I2C_SCL_PIN = I2C0_SCL_PIN SDA_PIN = I2C_SDA_PIN // awkward naming required by machine_atsamd51.go SCL_PIN = I2C_SCL_PIN // ) // I2C on the MatrixPortal M4 var ( I2C0 = sercomI2CM5 ) // ESP32 pins const ( NINA_ACK = D31 NINA_GPIO0 = D29 NINA_RESETN = D30 NINA_RX = UART2_RX_PIN NINA_TX = UART2_TX_PIN NINA_RTS = D32 NINA_CS = D33 NINA_SDO = SPI0_SDO_PIN NINA_SDI = SPI0_SDI_PIN NINA_SCK = SPI0_SCK_PIN ) // SPI on the MatrixPortal M4 var ( SPI0 = sercomSPIM3 // BUG: SDO on SERCOM1! NINA_SPI = SPI0 SPI1 = sercomSPIM0 ) // HUB75 pins const ( HUB75_R1 = D7 HUB75_G1 = D8 HUB75_B1 = D9 HUB75_R2 = D10 HUB75_G2 = D11 HUB75_B2 = D12 HUB75_CLK = D14 HUB75_LAT = D15 HUB75_OE = D16 HUB75_ADDR_A = D17 HUB75_ADDR_B = D18 HUB75_ADDR_C = D19 HUB75_ADDR_D = D20 HUB75_ADDR_E = D21 ) // USB CDC pins (UART0) const ( USBCDC_DM_PIN = D38 USBCDC_DP_PIN = D39 UART0_RX_PIN = USBCDC_DM_PIN UART0_TX_PIN = USBCDC_DP_PIN ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Matrix Portal M4" usb_STRING_MANUFACTURER = "Adafruit Industries" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x80C9 ) ================================================ FILE: src/machine/board_mch2022.go ================================================ //go:build mch2022 package machine // See: https://badge.team/docs/badges/mch2022/pinout/ const ( UART_TX_PIN Pin = 1 UART_RX_PIN Pin = 3 WS2812 Pin = 5 PowerOn Pin = 19 // Set high to enable power to LEDs and SD card // I2C pins SDA_PIN Pin = 22 SCL_PIN Pin = 21 // SPI and related pins (ICE40 and LCD). LCD_RESET Pin = 25 LCD_MODE Pin = 26 LCD_DC Pin = 33 SPI0_SCK_PIN Pin = 18 SPI0_SDO_PIN Pin = 23 SPI0_SDI_PIN Pin = 35 // connected to ICE40 SPI0_CS_ICE40_PIN Pin = 27 SPI0_CS_LCD_PIN Pin = 32 ) ================================================ FILE: src/machine/board_mdbt50qrx.go ================================================ //go:build mdbt50qrx package machine const HasLowFrequencyCrystal = false // GPIO Pins const ( D0 = P1_13 // LED1 D1 = P1_11 // LED2 (not populated by default) D2 = P0_15 // Button ) const ( LED = D0 ) // MDBT50Q-RX dongle does not have pins broken out for the peripherals below, // however the machine_nrf*.go implementations of I2C/SPI/etc expect the pin // constants to be defined, so we are defining them all as NoPin const ( UART_TX_PIN = NoPin UART_RX_PIN = NoPin SDA_PIN = NoPin SCL_PIN = NoPin SPI0_SCK_PIN = NoPin SPI0_SDO_PIN = NoPin SPI0_SDI_PIN = NoPin ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Raytac MDBT50Q - RX" usb_STRING_MANUFACTURER = "Raytac Corporation" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x810B ) ================================================ FILE: src/machine/board_metro-m4-airlift.go ================================================ //go:build metro_m4_airlift package machine // used to reset into bootloader const resetMagicValue = 0xf01669ef // GPIO Pins const ( D0 = PA23 // UART0 RX/PWM available D1 = PA22 // UART0 TX/PWM available D2 = PB17 // PWM available D3 = PB16 // PWM available D4 = PB13 // PWM available D5 = PB14 // PWM available D6 = PB15 // PWM available D7 = PB12 // PWM available D8 = PA21 // PWM available D9 = PA20 // PWM available D10 = PA18 // can be used for PWM or UART1 TX D11 = PA19 // can be used for PWM or UART1 RX D12 = PA17 // PWM available D13 = PA16 // PWM available D40 = PB22 // built-in neopixel ) // Analog pins const ( A0 = PA02 // ADC/AIN[0] A1 = PA05 // ADC/AIN[2] A2 = PB06 // ADC/AIN[3] A3 = PB00 // ADC/AIN[4] // NOTE: different between "airlift" and non-airlift versions A4 = PB08 // ADC/AIN[5] A5 = PB09 // ADC/AIN[10] ) const ( LED = D13 WS2812 = D40 ) // USBCDC pins const ( USBCDC_DM_PIN = PA24 USBCDC_DP_PIN = PA25 ) const ( UART_TX_PIN = D1 UART_RX_PIN = D0 ) const ( UART2_TX_PIN = PA04 UART2_RX_PIN = PA07 ) var ( UART1 = &sercomUSART3 UART2 = &sercomUSART0 DefaultUART = UART1 UART_NINA = UART2 ) // NINA-W102 settings const ( NINA_BAUDRATE = 115200 NINA_RESET_INVERTED = true NINA_SOFT_FLOWCONTROL = true ) const ( NINA_CS = PA15 NINA_ACK = PB04 NINA_GPIO0 = PB01 NINA_RESETN = PB05 // pins used for the ESP32 connection do not allow hardware // flow control, which is required. have to emulate with software. NINA_TX = PA04 NINA_RX = PA07 NINA_CTS = NINA_ACK NINA_RTS = NINA_GPIO0 ) // I2C pins const ( SDA_PIN = PB02 // SDA: SERCOM5/PAD[0] SCL_PIN = PB03 // SCL: SERCOM5/PAD[1] ) // I2C on the Metro M4. var ( I2C0 = sercomI2CM5 ) // SPI pins const ( SPI0_SCK_PIN = PA13 // SCK: SERCOM2/PAD[1] SPI0_SDO_PIN = PA12 // SDO: SERCOM2/PAD[0] SPI0_SDI_PIN = PA14 // SDI: SERCOM2/PAD[2] NINA_SDO = SPI0_SDO_PIN NINA_SDI = SPI0_SDI_PIN NINA_SCK = SPI0_SCK_PIN ) const ( SPI1_SCK_PIN = D12 // SDI: SERCOM1/PAD[1] SPI1_SDO_PIN = D11 // SDO: SERCOM1/PAD[3] SPI1_SDI_PIN = D13 // SCK: SERCOM1/PAD[0] ) // SPI on the Metro M4. var ( SPI0 = sercomSPIM2 NINA_SPI = SPI0 ) // SPI1 on the Metro M4 on pins 11,12,13 var SPI1 = sercomSPIM1 // USB CDC identifiers const ( usb_STRING_PRODUCT = "Adafruit Metro M4 Airlift Lite" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x8037 ) ================================================ FILE: src/machine/board_metro_rp2350.go ================================================ //go:build metro_rp2350 package machine // GPIO pins const ( GP0 Pin = GPIO0 GP1 Pin = GPIO1 GP2 Pin = GPIO2 GP3 Pin = GPIO3 GP4 Pin = GPIO4 GP5 Pin = GPIO5 GP6 Pin = GPIO6 GP7 Pin = GPIO7 GP8 Pin = GPIO8 GP9 Pin = GPIO9 GP10 Pin = GPIO10 GP11 Pin = GPIO11 GP12 Pin = GPIO12 GP13 Pin = GPIO13 GP14 Pin = GPIO14 GP15 Pin = GPIO15 GP16 Pin = GPIO16 GP17 Pin = GPIO17 GP18 Pin = GPIO18 GP19 Pin = GPIO19 GP20 Pin = GPIO20 GP21 Pin = GPIO21 GP22 Pin = GPIO22 GP23 Pin = GPIO23 GP24 Pin = GPIO24 GP25 Pin = GPIO25 GP26 Pin = GPIO26 GP27 Pin = GPIO27 GP28 Pin = GPIO28 GP29 Pin = GPIO29 GP30 Pin = GPIO30 GP31 Pin = GPIO31 GP32 Pin = GPIO32 GP33 Pin = GPIO33 GP34 Pin = GPIO34 GP35 Pin = GPIO35 GP36 Pin = GPIO36 GP37 Pin = GPIO37 GP38 Pin = GPIO38 GP39 Pin = GPIO39 GP40 Pin = GPIO40 GP41 Pin = GPIO41 GP42 Pin = GPIO42 GP43 Pin = GPIO43 GP44 Pin = GPIO44 GP45 Pin = GPIO45 GP46 Pin = GPIO46 // Boot button BUTTON Pin = GPIO24 // Onboard LED LED Pin = GPIO23 // Onboard NeoPixel NEOPIXEL Pin = GPIO25 WS2812 Pin = GPIO25 // Onboard crystal oscillator frequency, in MHz. xoscFreq = 12 // MHz ) // Arduino-header digital pins const ( RX Pin = GPIO1 TX Pin = GPIO0 D2 Pin = GPIO2 D3 Pin = GPIO3 D4 Pin = GPIO4 D5 Pin = GPIO5 D6 Pin = GPIO6 D7 Pin = GPIO7 D8 Pin = GPIO8 D9 Pin = GPIO9 D10 Pin = GPIO10 D11 Pin = GPIO11 D22 Pin = GPIO22 D23 Pin = GPIO23 ) // Arduino-header analog pins const ( A0 Pin = GPIO41 A1 Pin = GPIO42 A2 Pin = GPIO43 A3 Pin = GPIO44 A4 Pin = GPIO45 A5 Pin = GPIO46 ) // I2C Default pins on Raspberry Pico. const ( I2C0_SDA_PIN = GP20 I2C0_SCL_PIN = GP21 I2C1_SDA_PIN = GP2 I2C1_SCL_PIN = GP3 ) // SPI default pins const ( // Default Serial Clock Bus 0 for SPI communications SPI0_SCK_PIN = GPIO18 // Default Serial Out Bus 0 for SPI communications SPI0_SDO_PIN = GPIO19 // Tx // Default Serial In Bus 0 for SPI communications SPI0_SDI_PIN = GPIO16 // Rx // Default Serial Clock Bus 1 for SPI communications SPI1_SCK_PIN = GPIO30 // Default Serial Out Bus 1 for SPI communications SPI1_SDO_PIN = GPIO31 // Tx // Default Serial In Bus 1 for SPI communications SPI1_SDI_PIN = GPIO28 // Rx // SPI header pins MOSI Pin = SPI1_SDO_PIN MISO Pin = SPI1_SDI_PIN SCK Pin = SPI1_SCK_PIN ) // SD card reader pins const ( SD_SCK = GPIO34 SD_MOSI = GPIO35 SD_MISO = GPIO36 SDIO_DATA1 = GPIO37 SDIO_DATA2 = GPIO38 SD_CS = GPIO39 SD_CARD_DETECT = GPIO40 ) // HSTX pins const ( CKN Pin = GPIO15 CKP Pin = GPIO14 D0N Pin = GPIO19 D0P Pin = GPIO18 D1N Pin = GPIO17 D1P Pin = GPIO16 D2N Pin = GPIO13 D2P Pin = GPIO12 D26 Pin = GPIO26 D27 Pin = GPIO27 SCL Pin = GPIO21 SDA Pin = GPIO20 ) // USB host header pins const ( USB_HOST_DATA_PLUS Pin = GPIO32 USB_HOST_DATA_MINUS Pin = GPIO33 USB_HOST_5V_POWER Pin = GPIO29 ) // UART pins const ( UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART1_TX_PIN = GPIO8 UART1_RX_PIN = GPIO9 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) var DefaultUART = UART0 // USB identifiers const ( usb_STRING_PRODUCT = "Metro RP2350" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x814E ) ================================================ FILE: src/machine/board_microbit-v2.go ================================================ //go:build microbit_v2 package machine // The micro:bit does not have a 32kHz crystal on board. const HasLowFrequencyCrystal = false // Buttons on the micro:bit v2 (A and B) const ( BUTTON Pin = BUTTONA BUTTONA Pin = P5 BUTTONB Pin = P11 ) var DefaultUART = UART0 // UART pins const ( UART_TX_PIN Pin = P34 UART_RX_PIN Pin = P33 ) // ADC pins const ( ADC0 Pin = P0 ADC1 Pin = P1 ADC2 Pin = P2 ) // I2C0 (internal) pins const ( SDA_PIN Pin = SDA0_PIN SCL_PIN Pin = SCL0_PIN SDA0_PIN Pin = P30 SCL0_PIN Pin = P31 ) // I2C1 (external) pins const ( SDA1_PIN Pin = P20 SCL1_PIN Pin = P19 ) // SPI pins const ( SPI0_SCK_PIN Pin = P13 SPI0_SDO_PIN Pin = P15 SPI0_SDI_PIN Pin = P14 ) // GPIO/Analog pins const ( P0 Pin = 2 P1 Pin = 3 P2 Pin = 4 P3 Pin = 31 P4 Pin = 28 P5 Pin = 14 P6 Pin = 37 P7 Pin = 11 P8 Pin = 10 P9 Pin = 9 P10 Pin = 30 P11 Pin = 23 P12 Pin = 12 P13 Pin = 17 P14 Pin = 1 P15 Pin = 13 P16 Pin = 34 P19 Pin = 26 P20 Pin = 32 P21 Pin = 21 P22 Pin = 22 P23 Pin = 15 P24 Pin = 24 P25 Pin = 19 P26 Pin = 36 P27 Pin = 0 P28 Pin = 20 P29 Pin = 5 P30 Pin = 16 P31 Pin = 8 P32 Pin = 25 P33 Pin = 40 P34 Pin = 6 ) // LED matrix pins const ( LED_COL_1 Pin = P0_28 LED_COL_2 Pin = P0_11 LED_COL_3 Pin = P0_31 LED_COL_4 Pin = P1_05 LED_COL_5 Pin = P0_30 LED_ROW_1 Pin = P0_21 LED_ROW_2 Pin = P0_22 LED_ROW_3 Pin = P0_15 LED_ROW_4 Pin = P0_24 LED_ROW_5 Pin = P0_19 ) // Peripherals const ( BUZZER = P27 CAP_TOUCH = P26 MIC = P29 MIC_LED = P28 ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "BBC micro:bit V2" usb_STRING_MANUFACTURER = "BBC" ) var ( usb_VID uint16 = 0x0d28 usb_PID uint16 = 0x0204 ) ================================================ FILE: src/machine/board_microbit.go ================================================ //go:build microbit package machine // The micro:bit does not have a 32kHz crystal on board. const HasLowFrequencyCrystal = false var DefaultUART = UART0 // GPIO/Analog pins const ( P0 = P0_03 P1 = P0_02 P2 = P0_01 P3 = P0_04 P4 = P0_05 P5 = P0_17 P6 = P0_12 P7 = P0_11 P8 = P0_18 P9 = P0_10 P10 = P0_06 P11 = P0_26 P12 = P0_20 P13 = P0_23 P14 = P0_22 P15 = P0_21 P16 = P0_16 ) // Buttons on the micro:bit (A and B) const ( BUTTONA = P0_17 BUTTONB = P0_26 BUTTON = BUTTONA ) // UART pins const ( UART_TX_PIN = P0_24 UART_RX_PIN = P0_25 ) // ADC pins const ( ADC0 = P0_03 // P0 on the board ADC1 = P0_02 // P1 on the board ADC2 = P0_01 // P2 on the board ) // I2C pins const ( SDA_PIN = P0_30 // P20 on the board SCL_PIN = P0_00 // P19 on the board ) // SPI pins const ( SPI0_SCK_PIN = P0_23 // P13 on the board SPI0_SDO_PIN = P0_21 // P15 on the board SPI0_SDI_PIN = P0_22 // P14 on the board ) // LED matrix pins const ( LED_COL_1 = P0_04 LED_COL_2 = P0_05 LED_COL_3 = P0_06 LED_COL_4 = P0_07 LED_COL_5 = P0_08 LED_COL_6 = P0_09 LED_COL_7 = P0_10 LED_COL_8 = P0_11 LED_COL_9 = P0_12 LED_ROW_1 = P0_13 LED_ROW_2 = P0_14 LED_ROW_3 = P0_15 ) ================================================ FILE: src/machine/board_mksnanov3.go ================================================ //go:build mksnanov3 // The MKS Robin Nano V3.X board. // Documented at https://github.com/makerbase-mks/MKS-Robin-Nano-V3.X. package machine import ( "device/stm32" "runtime/interrupt" ) // LED is also wired to the SD card card detect (CD) pin. const LED = PD12 // UART pins const ( UART_TX_PIN = PB10 UART_RX_PIN = PB11 ) // EXP1 and EXP2 expansion ports for connecting // the MKS TS35 V2.0 expansion board. const ( BEEPER = EXP1_1 // LCD pins. LCD_DC = EXP1_8 LCD_CS = EXP1_7 LCD_RS = EXP1_4 LCD_BACKLIGHT = EXP1_3 // Touch pins. Note that some pins are shared with the // LCD SPI1 interface. TOUCH_CLK = EXP2_2 TOUCH_CS = EXP1_5 TOUCH_DIN = EXP2_6 TOUCH_DOUT = EXP2_1 TOUCH_IRQ = EXP1_6 BUTTON = BUTTON_JOG BUTTON_JOG = EXP1_2 BUTTON_JOG_CCW = EXP2_3 BUTTON_JOG_CW = EXP2_5 EXP1_1 = PC5 EXP1_2 = PE13 EXP1_3 = PD13 EXP1_4 = PC6 EXP1_5 = PE14 EXP1_6 = PE15 EXP1_7 = PD11 EXP1_8 = PD10 EXP2_1 = PA6 EXP2_2 = PA5 EXP2_3 = PE8 EXP2_4 = PE10 EXP2_5 = PE11 EXP2_6 = PA7 EXP2_7 = PE12 ) var ( UART3 = &_UART3 _UART3 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART3, TxAltFuncSelector: AF7_USART1_2_3, RxAltFuncSelector: AF7_USART1_2_3, } DefaultUART = UART3 ) // set up RX IRQ handler. Follow similar pattern for other UARTx instances func init() { UART3.Interrupt = interrupt.New(stm32.IRQ_USART3, _UART3.handleInterrupt) } // SPI pins const ( SPI1_SCK_PIN = EXP2_2 SPI1_SDI_PIN = EXP2_1 SPI1_SDO_PIN = EXP2_6 SPI0_SCK_PIN = SPI1_SCK_PIN SPI0_SDI_PIN = SPI1_SDI_PIN SPI0_SDO_PIN = SPI1_SDO_PIN ) // Since the first interface is named SPI1, both SPI0 and SPI1 refer to SPI1. var ( SPI0 = &SPI{ Bus: stm32.SPI1, AltFuncSelector: AF5_SPI1_SPI2, } SPI1 = SPI0 ) const ( I2C0_SCL_PIN = PB6 I2C0_SDA_PIN = PB7 ) var ( I2C0 = &I2C{ Bus: stm32.I2C1, AltFuncSelector: AF4_I2C1_2_3, } ) // Motor control pins. const ( X_ENABLE = PE4 X_STEP = PE3 X_DIR = PE2 X_DIAG = PA15 X_UART = PD5 Y_ENABLE = PE1 Y_STEP = PE0 Y_DIR = PB9 Y_DIAG = PD2 Y_UART = PD7 Z_ENABLE = PB8 Z_STEP = PB5 Z_DIR = PB4 Z_DIAG = PC8 Z_UART = PD4 E0_ENABLE = PB3 E0_STEP = PD6 E0_DIR = PD3 E0_DIAG = PC4 E0_UART = PD9 E1_ENABLE = PA3 E1_STEP = PD15 E1_DIR = PA1 E1_DIAG = PE7 E1_UART = PD8 ) ================================================ FILE: src/machine/board_nano-33-ble.go ================================================ //go:build nano_33_ble // This contains the pin mappings for the Arduino Nano 33 BLE [Sense] boards. // - https://store.arduino.cc/arduino-nano-33-ble // - https://store.arduino.cc/arduino-nano-33-ble-sense // // ---------------------------------------------------------------------------- // Flashing // // Special version of bossac is required. // This executable can be obtained two ways: // 1. In Arduino IDE, install support for the board ("Arduino Mbed OS Nano Boards") // Search for "tools/bossac/1.9.1-arduino2/bossac" in Arduino IDEs directory // 2. Download https://downloads.arduino.cc/packages/package_index.json // Search for "bossac-1.9.1-arduino2" in that file // Download tarball for your OS and unpack it // // Once you have the executable, make it accessible in your PATH as "bossac_arduino2". // // It is possible to replace original bossac with this new one (this only adds support for nrf chip). // In that case make "bossac_arduino2" symlink on it, for the board target to be able to find it. // // ---------------------------------------------------------------------------- // Bluetooth // // SoftDevice (s140v7) must be flashed first to enable use of bluetooth on this board. // See https://github.com/tinygo-org/bluetooth // // SoftDevice overwrites original bootloader and flashing method described above is not available anymore. // Instead, please use debug probe and flash your code with "nano-33-ble-s140v7" target. package machine const HasLowFrequencyCrystal = true // Digital Pins const ( D2 Pin = P1_11 D3 Pin = P1_12 D4 Pin = P1_15 D5 Pin = P1_13 D6 Pin = P1_14 D7 Pin = P0_23 D8 Pin = P0_21 D9 Pin = P0_27 D10 Pin = P1_02 D11 Pin = P1_01 D12 Pin = P1_08 D13 Pin = P0_13 ) // Analog pins const ( A0 Pin = P0_04 A1 Pin = P0_05 A2 Pin = P0_30 A3 Pin = P0_29 A4 Pin = P0_31 A5 Pin = P0_02 A6 Pin = P0_28 A7 Pin = P0_03 ) // Onboard LEDs const ( LED = LED_BUILTIN LED1 = LED_RED LED2 = LED_GREEN LED3 = LED_BLUE LED_BUILTIN = P0_13 LED_RED = P0_24 LED_GREEN = P0_16 LED_BLUE = P0_06 LED_PWR = P1_09 ) // UART0 pins const ( UART_RX_PIN = P1_10 UART_TX_PIN = P1_03 ) // I2C pins const ( // Defaults to internal SDA_PIN = SDA1_PIN SCL_PIN = SCL1_PIN // I2C0 (external) pins SDA0_PIN = P0_31 SCL0_PIN = P0_02 // I2C1 (internal) pins SDA1_PIN = P0_14 SCL1_PIN = P0_15 I2C_PULLUP = P1_00 // Set high for I2C to work ) // SPI pins const ( SPI0_SCK_PIN = P0_13 SPI0_SDO_PIN = P1_01 SPI0_SDI_PIN = P1_08 ) // Peripherals const ( APDS_INT = P0_19 // Proximity (APDS9960) interrupt pin LSM_PWR = P0_22 // IMU (LSM9DS1) power LPS_PWR = P0_22 // Pressure (LPS22HB) power HTS_PWR = P0_22 // Humidity (HTS221) power MIC_PWR = P0_17 // Microphone (MP34DT06JTR) power MIC_CLK = P0_26 MIC_DIN = P0_25 ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Nano 33 BLE" usb_STRING_MANUFACTURER = "Arduino" ) var ( usb_VID uint16 = 0x2341 usb_PID uint16 = 0x805a ) ================================================ FILE: src/machine/board_nano-rp2040.go ================================================ //go:build nano_rp2040 // This contains the pin mappings for the Arduino Nano RP2040 Connect board. // // Sometimes the board is not detected even when the board is connected to your computer. // To solve this, place a jumper wire between the REC and GND pins, then connect the board to your computer. // // For more information, see: https://store.arduino.cc/nano-rp2040-connect // Also // - Datasheets: https://docs.arduino.cc/hardware/nano-rp2040-connect // - Nano RP2040 Connect technical reference: https://docs.arduino.cc/tutorials/nano-rp2040-connect/rp2040-01-technical-reference package machine // Digital Pins const ( D2 Pin = GPIO25 D3 Pin = GPIO15 D4 Pin = GPIO16 D5 Pin = GPIO17 D6 Pin = GPIO18 D7 Pin = GPIO19 D8 Pin = GPIO20 D9 Pin = GPIO21 D10 Pin = GPIO5 D11 Pin = GPIO7 D12 Pin = GPIO4 D13 Pin = GPIO6 D14 Pin = GPIO26 D15 Pin = GPIO27 D16 Pin = GPIO28 D17 Pin = GPIO29 D18 Pin = GPIO12 D19 Pin = GPIO13 ) // Analog pins const ( A0 Pin = ADC0 A1 Pin = ADC1 A2 Pin = ADC2 A3 Pin = ADC3 ) // Onboard LED const ( LED = GPIO6 ) // I2C pins const ( I2C0_SDA_PIN Pin = GPIO12 I2C0_SCL_PIN Pin = GPIO13 I2C1_SDA_PIN Pin = GPIO18 I2C1_SCL_PIN Pin = GPIO19 ) // SPI pins. SPI1 not available on Nano RP2040 Connect. const ( SPI0_SCK_PIN Pin = GPIO6 SPI0_SDO_PIN Pin = GPIO7 SPI0_SDI_PIN Pin = GPIO4 // GPIO22 does not have SPI functionality so we set it to avoid interfering with NINA. SPI1_SCK_PIN Pin = GPIO22 SPI1_SDO_PIN Pin = GPIO22 SPI1_SDI_PIN Pin = GPIO22 ) var ( NINA_SPI = SPI1 ) // NINA-W102 Pins const ( NINA_SCK Pin = GPIO14 NINA_SDO Pin = GPIO11 NINA_SDI Pin = GPIO8 NINA_CS Pin = GPIO9 NINA_ACK Pin = GPIO10 NINA_GPIO0 Pin = GPIO2 NINA_RESETN Pin = GPIO3 NINA_TX Pin = GPIO8 NINA_RX Pin = GPIO9 NINA_CTS Pin = GPIO10 NINA_RTS Pin = GPIO11 ) // NINA-W102 settings const ( NINA_BAUDRATE = 115200 NINA_RESET_INVERTED = true NINA_SOFT_FLOWCONTROL = false ) // Onboard crystal oscillator frequency, in MHz. const ( xoscFreq = 12 // MHz ) // USB CDC identifiers // https://github.com/arduino/ArduinoCore-mbed/blob/master/variants/NANO_RP2040_CONNECT/pins_arduino.h const ( usb_STRING_PRODUCT = "Nano RP2040 Connect" usb_STRING_MANUFACTURER = "Arduino" ) var ( usb_VID uint16 = 0x2341 usb_PID uint16 = 0x005e ) // UART pins const ( UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) // UART_NINA on the Arduino Nano RP2040 connects to the NINA HCI. var UART_NINA = UART1 var DefaultUART = UART0 ================================================ FILE: src/machine/board_nicenano.go ================================================ //go:build nicenano package machine const HasLowFrequencyCrystal = true // GPIO Pins const ( D006 = P0_06 D008 = P0_08 D017 = P0_17 D020 = P0_20 D022 = P0_22 D024 = P0_24 D100 = P1_00 D011 = P0_11 D104 = P1_04 D106 = P1_06 D004 = P0_04 // AIN2; P0.04 (AIN2) is used to read the voltage of the battery via ADC. It can’t be used for any other function. D013 = P0_13 // VCC 3.3V; P0.13 on VCC shuts off the power to VCC when you set it to high; This saves on battery immensely for LEDs of all kinds that eat power even when off D115 = P1_15 D113 = P1_13 D031 = P0_31 // AIN7 D029 = P0_29 // AIN5 D002 = P0_02 // AIN0 D111 = P1_11 D010 = P0_10 // NFC2 D009 = P0_09 // NFC1 D026 = P0_26 D012 = P0_12 D101 = P1_01 D102 = P1_02 D107 = P1_07 ) // Analog Pins const ( AIN2 = P0_04 // Battery AIN7 = P0_31 AIN5 = P0_29 AIN0 = P0_02 ) const ( LED = P0_15 ) // UART0 pins (logical UART1) const ( UART_RX_PIN = P0_06 UART_TX_PIN = P0_08 ) // I2C pins const ( SDA_PIN = P0_17 // I2C0 external SCL_PIN = P0_20 // I2C0 external ) // SPI pins const ( SPI0_SCK_PIN = P0_22 // SCK SPI0_SDO_PIN = P0_24 // SDO SPI0_SDI_PIN = P1_00 // SDI ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "nice!nano" usb_STRING_MANUFACTURER = "Nice Keyboards" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x0029 ) ================================================ FILE: src/machine/board_nodemcu.go ================================================ //go:build nodemcu // Pinout for the NodeMCU dev kit. package machine // GPIO pins on the NodeMCU board. const ( D0 = GPIO16 D1 = GPIO5 D2 = GPIO4 D3 = GPIO0 D4 = GPIO2 D5 = GPIO14 D6 = GPIO12 D7 = GPIO13 D8 = GPIO15 ) // Onboard blue LED (on the AI-Thinker module). const LED = D4 // SPI pins const ( SPI0_SCK_PIN = D5 SPI0_SDO_PIN = D7 SPI0_SDI_PIN = D6 SPI0_CS0_PIN = D8 ) // I2C pins const ( SDA_PIN = D2 SCL_PIN = D1 ) ================================================ FILE: src/machine/board_nrf51.go ================================================ //go:build nrf51 || microbit package machine func CPUFrequency() uint32 { return 16000000 } // Hardware pins const ( P0_00 Pin = 0 P0_01 Pin = 1 P0_02 Pin = 2 P0_03 Pin = 3 P0_04 Pin = 4 P0_05 Pin = 5 P0_06 Pin = 6 P0_07 Pin = 7 P0_08 Pin = 8 P0_09 Pin = 9 P0_10 Pin = 10 P0_11 Pin = 11 P0_12 Pin = 12 P0_13 Pin = 13 P0_14 Pin = 14 P0_15 Pin = 15 P0_16 Pin = 16 P0_17 Pin = 17 P0_18 Pin = 18 P0_19 Pin = 19 P0_20 Pin = 20 P0_21 Pin = 21 P0_22 Pin = 22 P0_23 Pin = 23 P0_24 Pin = 24 P0_25 Pin = 25 P0_26 Pin = 26 P0_27 Pin = 27 P0_28 Pin = 28 P0_29 Pin = 29 P0_30 Pin = 30 P0_31 Pin = 31 ) ================================================ FILE: src/machine/board_nrf52840-mdk-usb-dongle.go ================================================ //go:build nrf52840_mdk_usb_dongle package machine const HasLowFrequencyCrystal = true // LEDs on the nrf52840-mdk-usb-dongle const ( LED Pin = LED_GREEN LED_GREEN Pin = 22 LED_RED Pin = 23 LED_BLUE Pin = 24 ) // RESET/USR button, depending on value of PSELRESET UICR register const ( BUTTON Pin = 18 ) // UART pins const ( UART_TX_PIN Pin = NoPin UART_RX_PIN Pin = NoPin ) // I2C pins (unused) const ( SDA_PIN = NoPin SCL_PIN = NoPin ) // SPI pins (unused) const ( SPI0_SCK_PIN = NoPin SPI0_SDO_PIN = NoPin SPI0_SDI_PIN = NoPin ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Makerdiary nRF52840 MDK USB Dongle" usb_STRING_MANUFACTURER = "Nordic Semiconductor ASA" ) var ( usb_VID uint16 = 0x1915 usb_PID uint16 = 0xCAFE ) ================================================ FILE: src/machine/board_nrf52840-mdk.go ================================================ //go:build nrf52840_mdk package machine const HasLowFrequencyCrystal = true // LEDs on the nrf52840-mdk (nRF52840 dev board) const ( LED_GREEN Pin = 22 LED_RED Pin = 23 LED_BLUE Pin = 24 LED Pin = LED_GREEN ) // UART pins const ( UART_TX_PIN Pin = 20 UART_RX_PIN Pin = 19 ) // I2C pins (unused) const ( SDA_PIN = NoPin SCL_PIN = NoPin ) // SPI pins (unused) const ( SPI0_SCK_PIN = NoPin SPI0_SDO_PIN = NoPin SPI0_SDI_PIN = NoPin ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Makerdiary nRF52840 MDK" usb_STRING_MANUFACTURER = "Nordic Semiconductor ASA" ) var ( usb_VID uint16 = 0x1915 usb_PID uint16 = 0xCAFE ) ================================================ FILE: src/machine/board_nrf52840.go ================================================ //go:build nrf52840 || circuitplay_bluefruit || reelboard || clue || itsybitsy_nrf52840 package machine // Hardware pins const ( P0_00 Pin = 0 P0_01 Pin = 1 P0_02 Pin = 2 P0_03 Pin = 3 P0_04 Pin = 4 P0_05 Pin = 5 P0_06 Pin = 6 P0_07 Pin = 7 P0_08 Pin = 8 P0_09 Pin = 9 P0_10 Pin = 10 P0_11 Pin = 11 P0_12 Pin = 12 P0_13 Pin = 13 P0_14 Pin = 14 P0_15 Pin = 15 P0_16 Pin = 16 P0_17 Pin = 17 P0_18 Pin = 18 P0_19 Pin = 19 P0_20 Pin = 20 P0_21 Pin = 21 P0_22 Pin = 22 P0_23 Pin = 23 P0_24 Pin = 24 P0_25 Pin = 25 P0_26 Pin = 26 P0_27 Pin = 27 P0_28 Pin = 28 P0_29 Pin = 29 P0_30 Pin = 30 P0_31 Pin = 31 P1_00 Pin = 32 P1_01 Pin = 33 P1_02 Pin = 34 P1_03 Pin = 35 P1_04 Pin = 36 P1_05 Pin = 37 P1_06 Pin = 38 P1_07 Pin = 39 P1_08 Pin = 40 P1_09 Pin = 41 P1_10 Pin = 42 P1_11 Pin = 43 P1_12 Pin = 44 P1_13 Pin = 45 P1_14 Pin = 46 P1_15 Pin = 47 ) ================================================ FILE: src/machine/board_nrf52840_generic.go ================================================ //go:build nrf52840 && nrf52840_generic package machine var ( LED = NoPin SDA_PIN = NoPin SCL_PIN = NoPin UART_TX_PIN = NoPin UART_RX_PIN = NoPin SPI0_SCK_PIN = NoPin SPI0_SDO_PIN = NoPin SPI0_SDI_PIN = NoPin // https://pid.codes/org/TinyGo/ usb_VID uint16 = 0x1209 usb_PID uint16 = 0x9090 usb_STRING_MANUFACTURER = "TinyGo" usb_STRING_PRODUCT = "nRF52840 Generic board" ) var ( DefaultUART = UART0 ) ================================================ FILE: src/machine/board_nucleof103rb.go ================================================ //go:build nucleof103rb package machine import ( "device/stm32" "runtime/interrupt" ) const ( LED = LED_BUILTIN LED_BUILTIN = LED_GREEN LED_GREEN = PA5 ) const ( BUTTON = BUTTON_USER BUTTON_USER = PC13 ) // UART pins const ( UART_TX_PIN = PA2 UART_RX_PIN = PA3 UART_ALT_TX_PIN = PD5 UART_ALT_RX_PIN = PD6 ) var ( // USART2 is the hardware serial port connected to the onboard ST-LINK // debugger to be exposed as virtual COM port over USB on Nucleo boards. UART2 = &_UART2 _UART2 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART2, } DefaultUART = UART2 ) func init() { UART2.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART2.handleInterrupt) } // SPI pins const ( SPI0_SCK_PIN = PA5 SPI0_SDI_PIN = PA6 SPI0_SDO_PIN = PA7 ) // I2C pins const ( I2C0_SCL_PIN = PB6 I2C0_SDA_PIN = PB7 ) ================================================ FILE: src/machine/board_nucleof722ze.go ================================================ //go:build nucleof722ze package machine import ( "device/stm32" "runtime/interrupt" ) const ( LED = LED_BUILTIN LED_BUILTIN = LED_GREEN LED_GREEN = PB0 LED_BLUE = PB7 LED_RED = PB14 ) const ( BUTTON = BUTTON_USER BUTTON_USER = PC13 ) // UART pins const ( // PD8 and PD9 are connected to the ST-Link Virtual Com Port (VCP) UART_TX_PIN = PD8 UART_RX_PIN = PD9 UART_ALT_FN = 7 // GPIO_AF7_UART3 ) var ( // USART3 is the hardware serial port connected to the onboard ST-LINK // debugger to be exposed as virtual COM port over USB on Nucleo boards. UART1 = &_UART1 _UART1 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART3, TxAltFuncSelector: UART_ALT_FN, RxAltFuncSelector: UART_ALT_FN, } DefaultUART = UART1 ) func init() { UART1.Interrupt = interrupt.New(stm32.IRQ_USART3, _UART1.handleInterrupt) } // SPI pins const ( SPI0_SCK_PIN = PA5 SPI0_SDI_PIN = PA6 SPI0_SDO_PIN = PA7 ) // I2C pins const ( I2C0_SCL_PIN = PB8 I2C0_SDA_PIN = PB9 ) var ( // I2C1 is documented, alias to I2C0 as well I2C1 = &I2C{ Bus: stm32.I2C1, AltFuncSelector: 4, } I2C0 = I2C1 ) ================================================ FILE: src/machine/board_nucleol031k6.go ================================================ //go:build nucleol031k6 package machine import ( "device/stm32" "runtime/interrupt" ) const ( // Arduino Pins A0 = PA0 // ADC_IN0 A1 = PA1 // ADC_IN1 A2 = PA3 // ADC_IN3 A3 = PA4 // ADC_IN4 A4 = PA5 // ADC_IN5 || I2C1_SDA A5 = PA6 // ADC_IN6 || I2C1_SCL A6 = PA7 // ADC_IN7 A7 = PA2 // ADC_IN2 D0 = PA10 // USART1_TX D1 = PA9 // USART1_RX D2 = PA12 D3 = PB0 // TIM2_CH3 D4 = PB7 D5 = PB6 // TIM16_CH1N D6 = PB1 // TIM14_CH1 D9 = PA8 // TIM1_CH1 D10 = PA11 // SPI_CS || TIM1_CH4 D11 = PB5 // SPI1_MOSI || TIM3_CH2 D12 = PB4 // SPI1_MISO D13 = PB3 // SPI1_SCK ) const ( LED = LED_BUILTIN LED_BUILTIN = LED_GREEN LED_GREEN = PB3 ) const ( // This board does not have a user button, so // use first GPIO pin by default BUTTON = PA0 ) const ( // UART pins // PA2 and PA15 are connected to the ST-Link Virtual Com Port (VCP) UART_TX_PIN = PA2 UART_RX_PIN = PA15 // SPI SPI1_SCK_PIN = PB3 SPI1_SDI_PIN = PB5 SPI1_SDO_PIN = PB4 SPI0_SCK_PIN = SPI1_SCK_PIN SPI0_SDI_PIN = SPI1_SDI_PIN SPI0_SDO_PIN = SPI1_SDO_PIN // I2C pins // PB6 and PB7 are mapped to CN4 pin 7 and CN4 pin 8 respectively with the // default solder bridge settings I2C0_SCL_PIN = PB7 I2C0_SDA_PIN = PB6 I2C0_ALT_FUNC = 1 ) var ( // USART2 is the hardware serial port connected to the onboard ST-LINK // debugger to be exposed as virtual COM port over USB on Nucleo boards. UART1 = &_UART1 _UART1 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART2, TxAltFuncSelector: 4, RxAltFuncSelector: 4, } DefaultUART = UART1 // I2C1 is documented, alias to I2C0 as well I2C1 = &I2C{ Bus: stm32.I2C1, AltFuncSelector: 1, } I2C0 = I2C1 // SPI SPI0 = &SPI{ Bus: stm32.SPI1, AltFuncSelector: 0, } SPI1 = SPI0 ) func init() { UART1.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART1.handleInterrupt) } ================================================ FILE: src/machine/board_nucleol432kc.go ================================================ //go:build nucleol432kc package machine import ( "device/stm32" "runtime/interrupt" ) const ( // Arduino Pins A0 = PA0 A1 = PA1 A2 = PA3 A3 = PA4 A4 = PA5 A5 = PA6 A6 = PA7 A7 = PA2 D0 = PA10 D1 = PA9 D2 = PA12 D3 = PB0 D4 = PB7 D5 = PB6 D6 = PB1 D7 = PC14 D8 = PC15 D9 = PA8 D10 = PA11 D11 = PB5 D12 = PB4 D13 = PB3 ) const ( LED = LED_BUILTIN LED_BUILTIN = LED_GREEN LED_GREEN = PB3 ) const ( // This board does not have a user button, so // use first GPIO pin by default BUTTON = PA0 ) const ( // UART pins // PA2 and PA15 are connected to the ST-Link Virtual Com Port (VCP) UART_TX_PIN = PA2 UART_RX_PIN = PA15 // I2C pins // With default solder bridge settings: // PB6 / Arduino D5 / CN3 Pin 8 is SCL // PB7 / Arduino D4 / CN3 Pin 7 is SDA I2C0_SCL_PIN = PB6 I2C0_SDA_PIN = PB7 // SPI pins SPI1_SCK_PIN = PB3 SPI1_SDI_PIN = PB5 SPI1_SDO_PIN = PB4 SPI0_SCK_PIN = SPI1_SCK_PIN SPI0_SDI_PIN = SPI1_SDI_PIN SPI0_SDO_PIN = SPI1_SDO_PIN ) var ( // USART2 is the hardware serial port connected to the onboard ST-LINK // debugger to be exposed as virtual COM port over USB on Nucleo boards. UART1 = &_UART1 _UART1 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART2, TxAltFuncSelector: 7, RxAltFuncSelector: 3, } DefaultUART = UART1 // I2C1 is documented, alias to I2C0 as well I2C1 = &I2C{ Bus: stm32.I2C1, AltFuncSelector: 4, } I2C0 = I2C1 // SPI1 is documented, alias to SPI0 as well SPI1 = &SPI{ Bus: stm32.SPI1, AltFuncSelector: 5, } SPI0 = SPI1 ) func init() { UART1.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART1.handleInterrupt) } ================================================ FILE: src/machine/board_nucleol476rg.go ================================================ //go:build nucleol476rg // Schematic: https://www.st.com/resource/en/user_manual/um1724-stm32-nucleo64-boards-mb1136-stmicroelectronics.pdf // Datasheet: https://www.st.com/resource/en/datasheet/stm32l476je.pdf package machine import ( "device/stm32" "runtime/interrupt" ) const ( // Arduino Pins A0 = PA0 A1 = PA1 A2 = PA4 A3 = PB0 A4 = PC1 A5 = PC0 D0 = PA3 D1 = PA2 D2 = PA10 D3 = PB3 D4 = PB5 D5 = PB4 D6 = PB10 D7 = PA8 D8 = PA9 D9 = PC7 D10 = PB6 D11 = PA7 D12 = PA6 D13 = PA5 D14 = PB9 D15 = PB8 ) // User LD2: the green LED is a user LED connected to ARDUINO® signal D13 corresponding // to STM32 I/O PA5 (pin 21) or PB13 (pin 34) depending on the STM32 target. const ( LED = LED_BUILTIN LED_BUILTIN = LED_GREEN LED_GREEN = PA5 ) const ( // This board does not have a user button, so // use first GPIO pin by default BUTTON = PA0 ) const ( // UART pins // PA2 and PA3 are connected to the ST-Link Virtual Com Port (VCP) UART_TX_PIN = PA2 UART_RX_PIN = PA3 // I2C pins // With default solder bridge settings: // PB8 / Arduino D5 / CN3 Pin 8 is SCL // PB7 / Arduino D4 / CN3 Pin 7 is SDA I2C0_SCL_PIN = PB8 I2C0_SDA_PIN = PB9 // SPI pins SPI1_SCK_PIN = PA5 SPI1_SDI_PIN = PA6 SPI1_SDO_PIN = PA7 SPI0_SCK_PIN = SPI1_SCK_PIN SPI0_SDI_PIN = SPI1_SDI_PIN SPI0_SDO_PIN = SPI1_SDO_PIN ) var ( // USART2 is the hardware serial port connected to the onboard ST-LINK // debugger to be exposed as virtual COM port over USB on Nucleo boards. UART1 = &_UART1 _UART1 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART2, TxAltFuncSelector: AF7_USART1_2_3, RxAltFuncSelector: AF7_USART1_2_3, } DefaultUART = UART1 // I2C1 is documented, alias to I2C0 as well I2C1 = &I2C{ Bus: stm32.I2C1, AltFuncSelector: AF4_I2C1_2_3, } I2C0 = I2C1 // SPI1 is documented, alias to SPI0 as well SPI1 = &SPI{ Bus: stm32.SPI1, AltFuncSelector: AF5_SPI1_2, } SPI0 = SPI1 ) func init() { UART1.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART1.handleInterrupt) } ================================================ FILE: src/machine/board_nucleol552ze.go ================================================ //go:build nucleol552ze package machine import ( "device/stm32" "runtime/interrupt" ) const ( LED_GREEN = PC7 LED_BLUE = PB7 LED_RED = PA9 LED_BUILTIN = LED_GREEN LED = LED_BUILTIN ) const ( BUTTON = BUTTON_USER BUTTON_USER = PC13 ) // UART pins const ( // PG7 and PG8 are connected to the ST-Link Virtual Com Port (VCP) UART_TX_PIN = PG7 UART_RX_PIN = PG8 UART_ALT_FN = 8 // GPIO_AF8_LPUART1 ) var ( // LPUART1 is the hardware serial port connected to the onboard ST-LINK // debugger to be exposed as virtual COM port over USB on Nucleo boards. UART1 = &_UART1 _UART1 = UART{ Buffer: NewRingBuffer(), Bus: stm32.LPUART1, TxAltFuncSelector: UART_ALT_FN, RxAltFuncSelector: UART_ALT_FN, } DefaultUART = UART1 ) const ( I2C0_SCL_PIN = PB8 I2C0_SDA_PIN = PB9 ) var ( // I2C1 is documented, alias to I2C0 as well I2C1 = &I2C{ Bus: stm32.I2C1, AltFuncSelector: 4, } I2C0 = I2C1 ) func init() { UART1.Interrupt = interrupt.New(stm32.IRQ_LPUART1, _UART1.handleInterrupt) } ================================================ FILE: src/machine/board_nucleowl55jc.go ================================================ //go:build nucleowl55jc package machine import ( "device/stm32" "runtime/interrupt" ) const ( LED_BLUE = PB15 LED_GREEN = PB9 LED_RED = PB11 LED = LED_RED BTN1 = PA0 BTN2 = PA1 BTN3 = PC6 BUTTON = BTN1 // SubGhz (SPI3) SPI0_NSS_PIN = PA4 SPI0_SCK_PIN = PA5 SPI0_SDO_PIN = PA6 SPI0_SDI_PIN = PA7 //MCU USART1 UART1_TX_PIN = PB6 UART1_RX_PIN = PB7 //MCU USART2 UART2_RX_PIN = PA3 UART2_TX_PIN = PA2 // DEFAULT USART UART_RX_PIN = UART2_RX_PIN UART_TX_PIN = UART2_TX_PIN // I2C1 pins I2C1_SCL_PIN = PA9 I2C1_SDA_PIN = PA10 I2C1_ALT_FUNC = 4 // I2C2 pins I2C2_SCL_PIN = PA12 I2C2_SDA_PIN = PA11 I2C2_ALT_FUNC = 4 // I2C0 alias for I2C1 I2C0_SDA_PIN = I2C1_SDA_PIN I2C0_SCL_PIN = I2C1_SCL_PIN ) var ( // STM32 UART2 is connected to the embedded STLINKV3 Virtual Com Port UART0 = &_UART0 _UART0 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART2, TxAltFuncSelector: 7, RxAltFuncSelector: 7, } // UART1 is free UART1 = &_UART1 _UART1 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART1, TxAltFuncSelector: 7, RxAltFuncSelector: 7, } DefaultUART = UART0 // I2C Busses I2C1 = &I2C{ Bus: stm32.I2C1, AltFuncSelector: I2C1_ALT_FUNC, } I2C2 = &I2C{ Bus: stm32.I2C2, AltFuncSelector: I2C2_ALT_FUNC, } I2C0 = I2C1 // SPI SPI3 = &SPI{ Bus: stm32.SPI3, } ) func init() { // Enable UARTs Interrupts UART0.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART0.handleInterrupt) UART1.Interrupt = interrupt.New(stm32.IRQ_USART1, _UART1.handleInterrupt) } ================================================ FILE: src/machine/board_p1am-100.go ================================================ //go:build p1am_100 // This contains the pin mappings for the ProductivityOpen P1AM-100 board. // // For more information, see: https://facts-engineering.github.io/ package machine // used to reset into bootloader const resetMagicValue = 0x07738135 // Note: On the P1AM-100, pins D8, D9, D10, A3, and A4 are used for // communication with the base controller. // GPIO Pins const ( D0 Pin = PA22 // PWM available D1 Pin = PA23 // PWM available D2 Pin = PA10 // PWM available D3 Pin = PA11 // PWM available D4 Pin = PB10 // PWM available D5 Pin = PB11 // PWM available D6 Pin = PA20 // PWM available D7 Pin = PA21 // PWM available D8 Pin = PA16 // PWM available D9 Pin = PA17 D10 Pin = PA19 // PWM available D11 Pin = PA08 D12 Pin = PA09 D13 Pin = PB23 D14 Pin = PB22 // Remaining pins are shared with analog pins D15 Pin = PA02 D16 Pin = PB02 D17 Pin = PB03 D18 Pin = PA04 // PWM available D19 Pin = PA05 // PWM available D20 Pin = PA06 D21 Pin = PA07 ) // Analog pins const ( A0 Pin = PA02 // ADC/AIN[0] A1 Pin = PB02 // ADC/AIN[10] A2 Pin = PB03 // ADC/AIN[11] A3 Pin = PA04 // ADC/AIN[4] A4 Pin = PA05 // ADC/AIN[5] A5 Pin = PA06 // ADC/AIN[6] A6 Pin = PA07 // ADC/AIN[7] ) const ( SWITCH Pin = PA28 LED Pin = PB08 ADC_BATTERY Pin = PB09 // ADC/AIN[3] ) // P1AM Base Controller const ( BASE_SLAVE_SELECT_PIN Pin = A3 BASE_SLAVE_ACK_PIN Pin = A4 BASE_ENABLE_PIN Pin = PB09 ) // USBCDC pins const ( USBCDC_DM_PIN Pin = PA24 USBCDC_DP_PIN Pin = PA25 USBCDC_HOST_ENABLE_PIN Pin = PA18 ) // UART1 pins const ( UART_RX_PIN Pin = PB23 // RX: SERCOM5/PAD[3] UART_TX_PIN Pin = PB22 // TX: SERCOM5/PAD[2] ) // UART1 on the P1AM-100 connects to the normal TX/RX pins. var UART1 = &sercomUSART5 // I2C pins const ( SDA_PIN Pin = PA08 // SDA: SERCOM0/PAD[0] SCL_PIN Pin = PA09 // SCL: SERCOM0/PAD[1] ) // I2C on the P1AM-100. var ( I2C0 = sercomI2CM0 ) // SPI pins const ( SPI0_SCK_PIN Pin = D9 // SCK: SERCOM1/PAD[1] SPI0_SDO_PIN Pin = D8 // SDO: SERCOM1/PAD[0] SPI0_SDI_PIN Pin = D10 // SDI: SERCOM1/PAD[3] ) // SD card pins const ( SDCARD_SDI_PIN Pin = PA15 // SDI: SERCOM2/PAD[3] SDCARD_SDO_PIN Pin = PA12 // SDO: SERCOM2/PAD[0] SDCARD_SCK_PIN Pin = PA13 // SCK: SERCOM2/PAD[1] SDCARD_SS_PIN Pin = PA14 // SS: as GPIO SDCARD_CD_PIN Pin = PA27 ) // SPI on the P1AM-100 is used for Base Controller. var ( SPI0 = sercomSPIM1 BASE_CONTROLLER_SPI = SPI0 ) // SPI1 is connected to the SD card slot on the P1AM-100 var ( SPI1 = sercomSPIM2 SDCARD_SPI = SPI1 ) // I2S pins const ( I2S_SCK_PIN Pin = D2 I2S_SDO_PIN Pin = A6 I2S_SDI_PIN = NoPin I2S_WS_PIN = D3 ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "P1AM-100" usb_STRING_MANUFACTURER = "Facts Engineering" ) var ( usb_VID uint16 = 0x1354 usb_PID uint16 = 0x4000 ) ================================================ FILE: src/machine/board_particle_argon.go ================================================ //go:build particle_argon package machine const HasLowFrequencyCrystal = true // More info: https://docs.particle.io/datasheets/wi-fi/argon-datasheet/ // Board diagram: https://docs.particle.io/assets/images/argon/argon-block-diagram.png // GPIOs const ( A0 Pin = 3 A1 Pin = 4 A2 Pin = 28 A3 Pin = 29 A4 Pin = 30 A5 Pin = 31 D0 Pin = 26 // Also SDA D1 Pin = 27 // Also SCL D2 Pin = 33 D3 Pin = 34 D4 Pin = 40 D5 Pin = 42 D6 Pin = 43 D7 Pin = 44 // Also LED D8 Pin = 35 D9 Pin = 6 // Also TX D10 Pin = 8 // Also RX D11 Pin = 46 // Also SDI D12 Pin = 45 // Also SDO D13 Pin = 47 // Also SCK ) // LEDs const ( LED Pin = 44 LED_GREEN Pin = 14 LED_RED Pin = 13 LED_BLUE Pin = 15 ) // UART var ( DefaultUART = UART0 ) const ( UART_TX_PIN Pin = 6 UART_RX_PIN Pin = 8 ) // I2C pins const ( SDA_PIN Pin = 26 SCL_PIN Pin = 27 ) // SPI pins const ( SPI0_SCK_PIN Pin = 47 SPI0_SDO_PIN Pin = 45 SPI0_SDI_PIN Pin = 46 ) // Internal 4MB SPI Flash const ( SPI1_SCK_PIN Pin = 19 SPI1_SDO_PIN Pin = 20 SPI1_SDI_PIN Pin = 21 SPI1_CS_PIN Pin = 17 SPI1_WP_PIN Pin = 22 SPI1_HOLD_PIN Pin = 23 ) // ESP32 coprocessor const ( ESP32_TXD_PIN Pin = 36 ESP32_RXD_PIN Pin = 37 ESP32_CTS_PIN Pin = 39 ESP32_RTS_PIN Pin = 38 ESP32_BOOT_MODE_PIN Pin = 16 ESP32_WIFI_EN_PIN Pin = 24 ESP32_HOST_WK_PIN Pin = 7 ) // Other peripherals const ( MODE_BUTTON_PIN Pin = 11 CHARGE_STATUS_PIN Pin = 41 LIPO_VOLTAGE_PIN Pin = 5 PCB_ANTENNA_PIN Pin = 2 EXTERNAL_UFL_PIN Pin = 25 NFC1_PIN Pin = 9 NFC2_PIN Pin = 10 ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Argon" usb_STRING_MANUFACTURER = "Particle" ) var ( usb_VID uint16 = 0x2B04 usb_PID uint16 = 0xD00C ) ================================================ FILE: src/machine/board_particle_boron.go ================================================ //go:build particle_boron package machine const HasLowFrequencyCrystal = true // More info: https://docs.particle.io/datasheets/cellular/boron-datasheet/ // Board diagram: https://docs.particle.io/assets/images/boron/boron-block-diagram.png // GPIOs const ( A0 Pin = 3 A1 Pin = 4 A2 Pin = 28 A3 Pin = 29 A4 Pin = 30 A5 Pin = 31 D0 Pin = 26 // Also SDA D1 Pin = 27 // Also SCL D2 Pin = 33 D3 Pin = 34 D4 Pin = 40 D5 Pin = 42 D6 Pin = 43 D7 Pin = 44 // Also LED D8 Pin = 35 D9 Pin = 6 // Also TX D10 Pin = 8 // Also RX D11 Pin = 46 // Also SDI D12 Pin = 45 // Also SDO D13 Pin = 47 // Also SCK ) // LEDs const ( LED Pin = 44 LED_GREEN Pin = 14 LED_RED Pin = 13 LED_BLUE Pin = 15 ) // UART var ( DefaultUART = UART0 ) const ( UART_TX_PIN Pin = 6 UART_RX_PIN Pin = 8 ) // I2C pins const ( SDA_PIN Pin = 26 SCL_PIN Pin = 27 // Internal I2C with MAX17043 (Fuel gauge) and BQ24195 (Power management) chips on it SDA1_PIN Pin = 24 SCL1_PIN Pin = 41 INT1_PIN Pin = 5 ) // SPI pins const ( SPI0_SCK_PIN Pin = 47 SPI0_SDO_PIN Pin = 45 SPI0_SDI_PIN Pin = 46 ) // Internal 4MB SPI Flash const ( SPI1_SCK_PIN Pin = 19 SPI1_SDO_PIN Pin = 20 SPI1_SDI_PIN Pin = 21 SPI1_CS_PIN Pin = 17 SPI1_WP_PIN Pin = 22 SPI1_HOLD_PIN Pin = 23 ) // u-blox SARA coprocessor const ( SARA_TXD_PIN Pin = 37 SARA_RXD_PIN Pin = 36 SARA_CTS_PIN Pin = 38 SARA_RTS_PIN Pin = 39 SARA_RESET_PIN Pin = 12 SARA_POWER_ON_PIN Pin = 16 SARA_BUFF_EN_PIN Pin = 25 SARA_VINT_PIN Pin = 2 ) // Other peripherals const ( MODE_BUTTON_PIN Pin = 11 ANTENNA_SEL_PIN Pin = 7 // Low: chip antenna, High: External uFL NFC1_PIN Pin = 9 NFC2_PIN Pin = 10 ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Boron" usb_STRING_MANUFACTURER = "Particle" ) var ( usb_VID uint16 = 0x2B04 usb_PID uint16 = 0xD00D ) ================================================ FILE: src/machine/board_particle_xenon.go ================================================ //go:build particle_xenon package machine const HasLowFrequencyCrystal = true // More info: https://docs.particle.io/datasheets/discontinued/xenon-datasheet/ // Board diagram: https://docs.particle.io/assets/images/xenon/xenon-block-diagram.png // GPIOs const ( A0 Pin = 3 A1 Pin = 4 A2 Pin = 28 A3 Pin = 29 A4 Pin = 30 A5 Pin = 31 D0 Pin = 26 // Also SDA D1 Pin = 27 // Also SCL D2 Pin = 33 D3 Pin = 34 D4 Pin = 40 D5 Pin = 42 D6 Pin = 43 D7 Pin = 44 // Also LED D8 Pin = 35 D9 Pin = 6 // Also TX D10 Pin = 8 // Also RX D11 Pin = 46 // Also SDI D12 Pin = 45 // Also SDO D13 Pin = 47 // Also SCK ) // LEDs const ( LED Pin = 44 LED_GREEN Pin = 14 LED_RED Pin = 13 LED_BLUE Pin = 15 ) // UART var ( DefaultUART = UART0 ) const ( UART_TX_PIN Pin = 6 UART_RX_PIN Pin = 8 ) // I2C pins const ( SDA_PIN Pin = 26 SCL_PIN Pin = 27 ) // SPI pins const ( SPI0_SCK_PIN Pin = 47 SPI0_SDO_PIN Pin = 45 SPI0_SDI_PIN Pin = 46 ) // Internal 4MB SPI Flash const ( SPI1_SCK_PIN Pin = 19 SPI1_SDO_PIN Pin = 20 SPI1_SDI_PIN Pin = 21 SPI1_CS_PIN Pin = 17 SPI1_WP_PIN Pin = 22 SPI1_HOLD_PIN Pin = 23 ) // Other peripherals const ( MODE_BUTTON_PIN Pin = 11 CHARGE_STATUS_PIN Pin = 41 LIPO_VOLTAGE_PIN Pin = 5 PCB_ANTENNA_PIN Pin = 24 EXTERNAL_UFL_PIN Pin = 25 NFC1_PIN Pin = 9 NFC2_PIN Pin = 10 ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Xenon" usb_STRING_MANUFACTURER = "Particle" ) var ( usb_VID uint16 = 0x2B04 usb_PID uint16 = 0xD00E ) ================================================ FILE: src/machine/board_pca10031.go ================================================ //go:build pca10031 // pca10031 is a nrf51 based dongle, intended for use in wireless applications. // // https://infocenter.nordicsemi.com/pdf/nRF51_Dongle_UG_v1.0.pdf package machine // The pca10031 has a 32kHz crystal on board. const HasLowFrequencyCrystal = true // LED on the pca10031 const ( LED1 = LED_RED LED2 = LED_GREEN LED3 = LED_BLUE LED_RED = P0_21 LED_GREEN = P0_22 LED_BLUE = P0_23 LED = LED_RED ) var DefaultUART = UART0 // UART pins const ( UART_TX_PIN = P0_09 UART_RX_PIN = P0_11 ) // I2C pins (disabled) const ( SDA_PIN = NoPin SCL_PIN = NoPin ) // SPI pins (unused) const ( SPI0_SCK_PIN = NoPin SPI0_SDO_PIN = NoPin SPI0_SDI_PIN = NoPin ) ================================================ FILE: src/machine/board_pca10040.go ================================================ //go:build pca10040 package machine // The PCA10040 has a low-frequency (32kHz) crystal oscillator on board. const HasLowFrequencyCrystal = true // LEDs on the PCA10040 (nRF52832 dev board) const ( LED1 Pin = 17 LED2 Pin = 18 LED3 Pin = 19 LED4 Pin = 20 LED Pin = LED1 ) // Buttons on the PCA10040 (nRF52832 dev board) const ( BUTTON1 Pin = 13 BUTTON2 Pin = 14 BUTTON3 Pin = 15 BUTTON4 Pin = 16 BUTTON Pin = BUTTON1 ) var DefaultUART = UART0 // UART pins for NRF52840-DK const ( UART_TX_PIN Pin = 6 UART_RX_PIN Pin = 8 ) // ADC pins const ( ADC0 Pin = 3 ADC1 Pin = 4 ADC2 Pin = 28 ADC3 Pin = 29 ADC4 Pin = 30 ADC5 Pin = 31 ) // I2C pins const ( SDA_PIN Pin = 26 SCL_PIN Pin = 27 ) // SPI pins const ( SPI0_SCK_PIN Pin = 25 SPI0_SDO_PIN Pin = 23 SPI0_SDI_PIN Pin = 24 ) ================================================ FILE: src/machine/board_pca10056.go ================================================ //go:build pca10056 package machine const HasLowFrequencyCrystal = true // LEDs on the pca10056 const ( LED1 Pin = 13 LED2 Pin = 14 LED3 Pin = 15 LED4 Pin = 16 LED Pin = LED1 ) // Buttons on the pca10056 const ( BUTTON1 Pin = 11 BUTTON2 Pin = 12 BUTTON3 Pin = 24 BUTTON4 Pin = 25 BUTTON Pin = BUTTON1 ) var DefaultUART = UART0 // UART pins const ( UART_TX_PIN Pin = 6 UART_RX_PIN Pin = 8 ) // ADC pins const ( ADC0 Pin = 3 ADC1 Pin = 4 ADC2 Pin = 28 ADC3 Pin = 29 ADC4 Pin = 30 ADC5 Pin = 31 ) // I2C pins const ( SDA_PIN Pin = 26 // P0.26 SCL_PIN Pin = 27 // P0.27 ) // SPI pins const ( SPI0_SCK_PIN Pin = 47 // P1.15 SPI0_SDO_PIN Pin = 45 // P1.13 SPI0_SDI_PIN Pin = 46 // P1.14 ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Nordic nRF52840DK (PCA10056)" usb_STRING_MANUFACTURER = "Nordic Semiconductor" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x8029 ) ================================================ FILE: src/machine/board_pca10059.go ================================================ //go:build pca10059 package machine // The PCA10040 has a low-frequency (32kHz) crystal oscillator on board. const HasLowFrequencyCrystal = true // LEDs on the PCA10059 (nRF52840 dongle) const ( LED1 Pin = 6 LED2 Pin = 8 LED3 Pin = (1 << 5) | 9 LED4 Pin = 12 LED Pin = LED1 ) // Buttons on the PCA10059 (nRF52840 dongle) const ( BUTTON1 Pin = (1 << 5) | 6 BUTTON Pin = BUTTON1 ) // ADC pins const ( ADC1 Pin = 2 ADC2 Pin = 4 ADC3 Pin = 29 ADC4 Pin = 31 ) // UART pins const ( UART_TX_PIN Pin = NoPin UART_RX_PIN Pin = NoPin ) // I2C pins (unused) const ( SDA_PIN = NoPin SCL_PIN = NoPin ) // SPI pins (unused) const ( SPI0_SCK_PIN = NoPin SPI0_SDO_PIN = NoPin SPI0_SDI_PIN = NoPin ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "nRF52840 Dongle" usb_STRING_MANUFACTURER = "Nordic Semiconductor ASA" ) var ( usb_VID uint16 = 0x1915 usb_PID uint16 = 0xCAFE ) ================================================ FILE: src/machine/board_pga2350.go ================================================ //go:build pga2350 package machine // PGA2350 pin definitions. const ( GP0 = GPIO0 GP1 = GPIO1 GP2 = GPIO2 GP3 = GPIO3 GP4 = GPIO4 GP5 = GPIO5 GP6 = GPIO6 GP7 = GPIO7 GP8 = GPIO8 GP9 = GPIO9 GP10 = GPIO10 GP11 = GPIO11 GP12 = GPIO12 GP13 = GPIO13 GP14 = GPIO14 GP15 = GPIO15 GP16 = GPIO16 GP17 = GPIO17 GP18 = GPIO18 GP19 = GPIO19 GP20 = GPIO20 GP21 = GPIO21 GP22 = GPIO22 GP26 = GPIO26 GP27 = GPIO27 GP28 = GPIO28 GP29 = GPIO29 GP30 = GPIO30 GP31 = GPIO31 GP32 = GPIO32 GP33 = GPIO33 GP34 = GPIO34 GP35 = GPIO35 GP36 = GPIO36 GP37 = GPIO37 GP38 = GPIO38 GP39 = GPIO39 GP40 = GPIO40 GP41 = GPIO41 GP42 = GPIO42 GP43 = GPIO43 GP44 = GPIO44 GP45 = GPIO45 GP46 = GPIO46 GP47 = GPIO47 ) var DefaultUART = UART0 // Peripheral defaults. const ( xoscFreq = 12 // MHz I2C0_SDA_PIN = GP4 I2C0_SCL_PIN = GP5 I2C1_SDA_PIN = GP2 I2C1_SCL_PIN = GP3 // Default Serial Clock Bus 0 for SPI communications SPI0_SCK_PIN = GPIO18 // Default Serial Out Bus 0 for SPI communications SPI0_SDO_PIN = GPIO19 // Tx // Default Serial In Bus 0 for SPI communications SPI0_SDI_PIN = GPIO16 // Rx // Default Serial Clock Bus 1 for SPI communications SPI1_SCK_PIN = GPIO10 // Default Serial Out Bus 1 for SPI communications SPI1_SDO_PIN = GPIO11 // Tx // Default Serial In Bus 1 for SPI communications SPI1_SDI_PIN = GPIO12 // Rx UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART1_TX_PIN = GPIO8 UART1_RX_PIN = GPIO9 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) // USB identifiers const ( usb_STRING_PRODUCT = "PGA2350" usb_STRING_MANUFACTURER = "Pimoroni" ) var ( usb_VID uint16 = 0x2E8A usb_PID uint16 = 0x000A ) ================================================ FILE: src/machine/board_pico.go ================================================ //go:build pico package machine // GPIO pins const ( GP0 Pin = GPIO0 GP1 Pin = GPIO1 GP2 Pin = GPIO2 GP3 Pin = GPIO3 GP4 Pin = GPIO4 GP5 Pin = GPIO5 GP6 Pin = GPIO6 GP7 Pin = GPIO7 GP8 Pin = GPIO8 GP9 Pin = GPIO9 GP10 Pin = GPIO10 GP11 Pin = GPIO11 GP12 Pin = GPIO12 GP13 Pin = GPIO13 GP14 Pin = GPIO14 GP15 Pin = GPIO15 GP16 Pin = GPIO16 GP17 Pin = GPIO17 GP18 Pin = GPIO18 GP19 Pin = GPIO19 GP20 Pin = GPIO20 GP21 Pin = GPIO21 GP22 Pin = GPIO22 GP26 Pin = GPIO26 GP27 Pin = GPIO27 GP28 Pin = GPIO28 // Onboard LED LED Pin = GPIO25 // Onboard crystal oscillator frequency, in MHz. xoscFreq = 12 // MHz ) // I2C Default pins on Raspberry Pico. const ( I2C0_SDA_PIN = GP4 I2C0_SCL_PIN = GP5 I2C1_SDA_PIN = GP2 I2C1_SCL_PIN = GP3 ) // SPI default pins const ( // Default Serial Clock Bus 0 for SPI communications SPI0_SCK_PIN = GPIO18 // Default Serial Out Bus 0 for SPI communications SPI0_SDO_PIN = GPIO19 // Tx // Default Serial In Bus 0 for SPI communications SPI0_SDI_PIN = GPIO16 // Rx // Default Serial Clock Bus 1 for SPI communications SPI1_SCK_PIN = GPIO10 // Default Serial Out Bus 1 for SPI communications SPI1_SDO_PIN = GPIO11 // Tx // Default Serial In Bus 1 for SPI communications SPI1_SDI_PIN = GPIO12 // Rx ) // UART pins const ( UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART1_TX_PIN = GPIO8 UART1_RX_PIN = GPIO9 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) var DefaultUART = UART0 // USB identifiers const ( usb_STRING_PRODUCT = "Pico" usb_STRING_MANUFACTURER = "Raspberry Pi" ) var ( usb_VID uint16 = 0x2E8A usb_PID uint16 = 0x000A ) ================================================ FILE: src/machine/board_pico2.go ================================================ //go:build pico2 package machine // GPIO pins const ( GP0 Pin = GPIO0 GP1 Pin = GPIO1 GP2 Pin = GPIO2 GP3 Pin = GPIO3 GP4 Pin = GPIO4 GP5 Pin = GPIO5 GP6 Pin = GPIO6 GP7 Pin = GPIO7 GP8 Pin = GPIO8 GP9 Pin = GPIO9 GP10 Pin = GPIO10 GP11 Pin = GPIO11 GP12 Pin = GPIO12 GP13 Pin = GPIO13 GP14 Pin = GPIO14 GP15 Pin = GPIO15 GP16 Pin = GPIO16 GP17 Pin = GPIO17 GP18 Pin = GPIO18 GP19 Pin = GPIO19 GP20 Pin = GPIO20 GP21 Pin = GPIO21 GP22 Pin = GPIO22 GP26 Pin = GPIO26 GP27 Pin = GPIO27 GP28 Pin = GPIO28 // Onboard LED LED Pin = GPIO25 // Onboard crystal oscillator frequency, in MHz. xoscFreq = 12 // MHz ) // I2C Default pins on Raspberry Pico. const ( I2C0_SDA_PIN = GP4 I2C0_SCL_PIN = GP5 I2C1_SDA_PIN = GP2 I2C1_SCL_PIN = GP3 ) // SPI default pins const ( // Default Serial Clock Bus 0 for SPI communications SPI0_SCK_PIN = GPIO18 // Default Serial Out Bus 0 for SPI communications SPI0_SDO_PIN = GPIO19 // Tx // Default Serial In Bus 0 for SPI communications SPI0_SDI_PIN = GPIO16 // Rx // Default Serial Clock Bus 1 for SPI communications SPI1_SCK_PIN = GPIO10 // Default Serial Out Bus 1 for SPI communications SPI1_SDO_PIN = GPIO11 // Tx // Default Serial In Bus 1 for SPI communications SPI1_SDI_PIN = GPIO12 // Rx ) // UART pins const ( UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART1_TX_PIN = GPIO8 UART1_RX_PIN = GPIO9 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) var DefaultUART = UART0 // USB identifiers const ( usb_STRING_PRODUCT = "Pico2" usb_STRING_MANUFACTURER = "Raspberry Pi" ) var ( usb_VID uint16 = 0x2E8A usb_PID uint16 = 0x000A ) ================================================ FILE: src/machine/board_pico2_ice.go ================================================ //go:build pico2_ice // Most of the info is from // https://pico2-ice.tinyvision.ai/md_pinout.html although // (2025-09-07) RP4 appears twice in that pinout - the schematic is // more clear. Consistent with other RPi boards, we use GPn instead of // RPn to reference the RPi connected pins. package machine // GPIO pins const ( GP0 = GPIO0 GP1 = GPIO1 GP2 = GPIO2 GP3 = GPIO3 GP4 = GPIO4 GP5 = GPIO5 GP6 = GPIO6 GP7 = GPIO7 GP8 = GPIO8 GP9 = GPIO9 GP10 = GPIO10 GP11 = GPIO11 GP12 = GPIO12 GP13 = GPIO13 GP14 = GPIO14 GP15 = GPIO15 GP16 = GPIO16 GP17 = GPIO17 GP18 = GPIO18 GP19 = GPIO19 GP20 = GPIO20 GP21 = GPIO21 GP22 = GPIO22 GP23 = GPIO23 GP24 = GPIO24 GP25 = GPIO25 GP26 = GPIO26 GP27 = GPIO27 GP28 = GPIO28 GP29 = GPIO29 GP30 = GPIO30 GP31 = GPIO31 GP32 = GPIO32 GP33 = GPIO33 GP34 = GPIO34 GP35 = GPIO35 GP36 = GPIO36 GP37 = GPIO37 GP38 = GPIO38 GP39 = GPIO39 GP40 = GPIO40 GP41 = GPIO41 GP42 = GPIO42 GP43 = GPIO43 GP44 = GPIO44 GP45 = GPIO45 GP46 = GPIO46 GP47 = GPIO47 // RPi pins shared with ICE. The ICE number is what appears on // the board silkscreen. ICE9 = GP28 ICE11 = GP29 ICE14 = GP7 ICE15 = GP6 ICE16 = GP5 ICE17 = GP4 ICE18 = GP27 ICE19 = GP23 ICE20 = GP22 ICE21 = GP26 ICE23 = GP25 ICE25 = GP30 ICE26 = GP24 ICE27 = GP20 // FPGA Clock pin. ICE35_G0 = GP21 // Silkscreen & Pinout names ICE_SSN = ICE16 ICE_SO = ICE14 ICE_SI = ICE17 ICE_CK = ICE15 SD = GP2 SC = GP3 FPGA_RSTN = GP31 A3 = GP32 A1 = GP33 A4 = GP34 A2 = GP35 B3 = GP36 B1 = GP37 B4 = GP38 B2 = GP39 N0 = GP40 // On the board these are labeled "~0" N1 = GP41 N2 = GP42 N3 = GP43 N4 = GP44 N5 = GP45 N6 = GP46 // Functions from Schematic. ICE_DONE = GP40 USB_BOOT = GP42 // Button SW1 = GP42 BOOTSEL = GP42 // Tricolor LEDs LED_RED = GP1 LED_GREEN = GP0 LED_BLUE = GP9 // Onboard LED LED = LED_GREEN // Onboard crystal oscillator frequency, in MHz. xoscFreq = 12 // MHz ) // This board does not define default i2c pins. const ( I2C0_SDA_PIN = NoPin I2C0_SCL_PIN = NoPin I2C1_SDA_PIN = NoPin I2C1_SCL_PIN = NoPin ) // SPI default pins const ( // Default Serial Clock Bus 0 for SPI communications SPI0_SCK_PIN = GPIO18 // Default Serial Out Bus 0 for SPI communications SPI0_SDO_PIN = GPIO19 // Tx // Default Serial In Bus 0 for SPI communications SPI0_SDI_PIN = GPIO16 // Rx // Default Serial Clock Bus 1 for SPI communications SPI1_SCK_PIN = GPIO10 // Default Serial Out Bus 1 for SPI communications SPI1_SDO_PIN = GPIO11 // Tx // Default Serial In Bus 1 for SPI communications SPI1_SDI_PIN = GPIO12 // Rx ) // UART pins const ( UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART1_TX_PIN = GPIO8 UART1_RX_PIN = GPIO9 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) var DefaultUART = UART0 // USB identifiers const ( usb_STRING_PRODUCT = "Pico2" usb_STRING_MANUFACTURER = "Raspberry Pi" ) var ( usb_VID uint16 = 0x2E8A usb_PID uint16 = 0x000A ) ================================================ FILE: src/machine/board_pico_plus2.go ================================================ //go:build pico_plus2 package machine // GPIO pins const ( GP0 Pin = GPIO0 GP1 Pin = GPIO1 GP2 Pin = GPIO2 GP3 Pin = GPIO3 GP4 Pin = GPIO4 GP5 Pin = GPIO5 GP6 Pin = GPIO6 GP7 Pin = GPIO7 GP8 Pin = GPIO8 GP9 Pin = GPIO9 GP10 Pin = GPIO10 GP11 Pin = GPIO11 GP12 Pin = GPIO12 GP13 Pin = GPIO13 GP14 Pin = GPIO14 GP15 Pin = GPIO15 GP16 Pin = GPIO16 GP17 Pin = GPIO17 GP18 Pin = GPIO18 GP19 Pin = GPIO19 GP20 Pin = GPIO20 GP21 Pin = GPIO21 GP22 Pin = GPIO22 GP26 Pin = GPIO26 GP27 Pin = GPIO27 GP28 Pin = GPIO28 GP32 Pin = GPIO32 GP33 Pin = GPIO33 GP34 Pin = GPIO34 GP35 Pin = GPIO35 GP36 Pin = GPIO36 // Onboard LED LED Pin = GPIO25 // Onboard crystal oscillator frequency, in MHz. xoscFreq = 12 // MHz ) // I2C Default pins on Raspberry Pico. const ( I2C0_SDA_PIN = GP4 I2C0_SCL_PIN = GP5 I2C1_SDA_PIN = GP2 I2C1_SCL_PIN = GP3 ) // SPI default pins const ( // Default Serial Clock Bus 0 for SPI communications SPI0_SCK_PIN = GPIO18 // Default Serial Out Bus 0 for SPI communications SPI0_SDO_PIN = GPIO19 // Tx // Default Serial In Bus 0 for SPI communications SPI0_SDI_PIN = GPIO16 // Rx // Default Serial Clock Bus 1 for SPI communications SPI1_SCK_PIN = GPIO10 // Default Serial Out Bus 1 for SPI communications SPI1_SDO_PIN = GPIO11 // Tx // Default Serial In Bus 1 for SPI communications SPI1_SDI_PIN = GPIO12 // Rx ) // UART pins const ( UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART1_TX_PIN = GPIO8 UART1_RX_PIN = GPIO9 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) var DefaultUART = UART0 // USB identifiers const ( usb_STRING_PRODUCT = "Pico Plus2" usb_STRING_MANUFACTURER = "Pimoroni" ) var ( usb_VID uint16 = 0x2E8A usb_PID uint16 = 0x000F ) ================================================ FILE: src/machine/board_pinetime.go ================================================ //go:build pinetime package machine // Board pins for the PineTime. // Details: https://wiki.pine64.org/index.php/PineTime // The PineTime has a low-frequency (32kHz) crystal oscillator on board. const HasLowFrequencyCrystal = true // LEDs simply expose the three brightness level LEDs on the PineTime. They can // be useful for simple "hello world" style programs. const ( LED1 = LCD_BACKLIGHT_HIGH LED2 = LCD_BACKLIGHT_MID LED3 = LCD_BACKLIGHT_LOW LED = LED1 ) // The PineTime doesn't have a UART output. // Additionally, leaving the UART on results in a pretty big current drain. const ( UART_TX_PIN Pin = NoPin UART_RX_PIN Pin = NoPin ) // SPI pins for the PineTime. const ( SPI0_SCK_PIN Pin = 2 SPI0_SDO_PIN Pin = 3 SPI0_SDI_PIN Pin = 4 ) // I2C pins for the PineTime. const ( SDA_PIN Pin = 6 SCL_PIN Pin = 7 ) // Button pins. For some reason, there are two pins for the button. const ( BUTTON_IN Pin = 13 BUTTON_OUT Pin = 15 ) // Pin for the vibrator. const VIBRATOR_PIN Pin = 16 // LCD pins, using the naming convention of the official docs: // http://files.pine64.org/doc/PineTime/PineTime%20Port%20Assignment%20rev1.0.pdf const ( LCD_SCK = SPI0_SCK_PIN LCD_SDI = SPI0_SDO_PIN LCD_RS Pin = 18 LCD_CS Pin = 25 LCD_RESET Pin = 26 LCD_BACKLIGHT_LOW Pin = 14 LCD_BACKLIGHT_MID Pin = 22 LCD_BACKLIGHT_HIGH Pin = 23 ) ================================================ FILE: src/machine/board_pybadge.go ================================================ //go:build pybadge package machine // used to reset into bootloader const resetMagicValue = 0xf01669ef // GPIO Pins const ( D0 = PB17 // UART0 RX/PWM available D1 = PB16 // UART0 TX/PWM available D2 = PB03 D3 = PB02 D4 = PA14 // PWM available D5 = PA16 // PWM available D6 = PA18 // PWM available D7 = PB14 D8 = PA15 // built-in neopixel D9 = PA19 // PWM available D10 = PA20 // can be used for PWM or UART1 TX D11 = PA21 // can be used for PWM or UART1 RX D12 = PA22 // PWM available D13 = PA23 // PWM available ) // Analog pins const ( A0 = PA02 // ADC/AIN[0] A1 = PA05 // ADC/AIN[2] A2 = PB08 // ADC/AIN[3] A3 = PB09 // ADC/AIN[4] A4 = PA04 // ADC/AIN[5] A5 = PA06 // ADC/AIN[6] A6 = PB01 // ADC/AIN[12]/VMEAS A7 = PB04 // ADC/AIN[6]/LIGHT A8 = D2 // ADC/AIN[14] A9 = D3 // ADC/AIN[15] ) const ( LED = D13 NEOPIXELS = D8 WS2812 = D8 LIGHTSENSOR = A7 BUTTON_LATCH = PB00 BUTTON_OUT = PB30 BUTTON_CLK = PB31 TFT_DC = PB05 TFT_CS = PB07 TFT_RST = PA00 TFT_LITE = PA01 SPEAKER_ENABLE = PA27 ) const ( BUTTON_LEFT_MASK = 1 BUTTON_UP_MASK = 2 BUTTON_DOWN_MASK = 4 BUTTON_RIGHT_MASK = 8 BUTTON_SELECT_MASK = 16 BUTTON_START_MASK = 32 BUTTON_A_MASK = 64 BUTTON_B_MASK = 128 ) // USBCDC pins const ( USBCDC_DM_PIN = PA24 USBCDC_DP_PIN = PA25 ) // UART1 pins const ( UART_TX_PIN = D1 UART_RX_PIN = D0 ) const ( UART2_TX_PIN = A4 UART2_RX_PIN = A5 ) var ( UART1 = &sercomUSART5 UART2 = &sercomUSART0 DefaultUART = UART1 ) // I2C pins const ( SDA_PIN = PA12 // SDA: SERCOM2/PAD[0] SCL_PIN = PA13 // SCL: SERCOM2/PAD[1] ) // I2C on the ItsyBitsy M4. var I2C0 = sercomI2CM2 // SPI pins const ( SPI0_SCK_PIN = PA17 // SCK: SERCOM1/PAD[1] SPI0_SDO_PIN = PB23 // SDO: SERCOM1/PAD[3] SPI0_SDI_PIN = PB22 // SDI: SERCOM1/PAD[2] ) // TFT SPI pins const ( SPI1_SCK_PIN = PB13 // SCK: SERCOM4/PAD[1] SPI1_SDO_PIN = PB15 // SDO: SERCOM4/PAD[3] SPI1_SDI_PIN = NoPin ) // SPI on the PyBadge. var SPI0 = sercomSPIM1 // TFT SPI on the PyBadge. var SPI1 = sercomSPIM4 // USB CDC identifiers const ( usb_STRING_PRODUCT = "Adafruit pyBadge M4" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x8033 ) // NINA-W102 settings when using AirLift WiFi FeatherWing const ( NINA_BAUDRATE = 115200 NINA_RESET_INVERTED = true NINA_SOFT_FLOWCONTROL = true ) const ( NINA_CS = D13 NINA_ACK = D11 NINA_GPIO0 = D10 NINA_RESETN = D12 // pins used for the ESP32 connection do not allow hardware // flow control, which is required. have to emulate with software. NINA_TX = UART_TX_PIN NINA_RX = UART_RX_PIN NINA_CTS = NINA_ACK NINA_RTS = NINA_GPIO0 NINA_SDO = SPI0_SDO_PIN NINA_SDI = SPI0_SDI_PIN NINA_SCK = SPI0_SCK_PIN ) var ( NINA_SPI = SPI0 UART_NINA = UART1 ) ================================================ FILE: src/machine/board_pygamer.go ================================================ //go:build sam && atsamd51 && pygamer package machine // used to reset into bootloader const resetMagicValue = 0xf01669ef // GPIO Pins const ( D0 = PB17 // UART0 RX/PWM available D1 = PB16 // UART0 TX/PWM available D2 = PB03 D3 = PB02 D4 = PA14 // PWM available D5 = PA16 // PWM available D6 = PA18 // PWM available D7 = PB14 // CS for microSD card slot D8 = PA15 // built-in neopixel D9 = PA19 // PWM available D10 = PA20 // can be used for PWM or UART1 TX D11 = PA21 // can be used for PWM or UART1 RX D12 = PA22 // PWM available D13 = PA23 // PWM available ) // Analog pins const ( A0 = PA02 // ADC/AIN[0] A1 = PA05 // ADC/AIN[2] A2 = PB08 // ADC/AIN[3] A3 = PB09 // ADC/AIN[4] A4 = PA04 // ADC/AIN[5] A5 = PA06 // ADC/AIN[6] A6 = PB01 // ADC/AIN[12]/VMEAS A7 = PB04 // ADC/AIN[6]/LIGHT A8 = D2 // ADC/AIN[14] A9 = D3 // ADC/AIN[15] ) const ( LED = D13 NEOPIXELS = D8 WS2812 = D8 SD_CS = D7 LIGHTSENSOR = A7 BUTTON_LATCH = PB00 BUTTON_OUT = PB30 BUTTON_CLK = PB31 JOYY = PB06 JOYX = PB07 TFT_DC = PB05 TFT_CS = PB12 TFT_RST = PA00 TFT_LITE = PA01 SPEAKER_ENABLE = PA27 ) const ( BUTTON_SELECT_MASK = 16 BUTTON_START_MASK = 32 BUTTON_A_MASK = 64 BUTTON_B_MASK = 128 ) // USBCDC pins const ( USBCDC_DM_PIN = PA24 USBCDC_DP_PIN = PA25 ) // UART1 pins const ( UART_TX_PIN = D1 UART_RX_PIN = D0 ) // UART1 var is on SERCOM3, defined in atsamd51.go // UART2 pins const ( UART2_TX_PIN = A4 UART2_RX_PIN = A5 ) // UART2 var is on SERCOM0, defined in atsamd51.go // I2C pins const ( SDA_PIN = PA12 // SDA: SERCOM2/PAD[0] SCL_PIN = PA13 // SCL: SERCOM2/PAD[1] ) // I2C on the PyGamer. var ( I2C0 = sercomI2CM2 ) // SPI pins const ( SPI0_SCK_PIN = PA17 // SCK: SERCOM1/PAD[1] SPI0_SDO_PIN = PB23 // SDO: SERCOM1/PAD[3] SPI0_SDI_PIN = PB22 // SDI: SERCOM1/PAD[2] ) // SPI on the PyGamer. var SPI0 = sercomSPIM1 // TFT SPI pins const ( SPI1_SCK_PIN = PB13 // SCK: SERCOM4/PAD[1] SPI1_SDO_PIN = PB15 // SDO: SERCOM4/PAD[3] SPI1_SDI_PIN = NoPin ) // TFT SPI on the PyGamer. var SPI1 = sercomSPIM4 // USB CDC identifiers const ( usb_STRING_PRODUCT = "Adafruit pyGamer M4" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x8033 ) ================================================ FILE: src/machine/board_pyportal.go ================================================ //go:build pyportal package machine // used to reset into bootloader const resetMagicValue = 0xf01669ef // GPIO Pins const ( D0 = PB13 // NINA_RX D1 = PB12 // NINA_TX D2 = PB22 // built-in neopixel D3 = PA04 // PWM available D4 = PA05 // PWM available D5 = PB16 // NINA_ACK D6 = PB15 // NINA_GPIO0 D7 = PB17 // NINA_RESETN D8 = PB14 // NINA_CS D9 = PB04 // TFT_RD D10 = PB05 // TFT_DC D11 = PB06 // TFT_CS D12 = PB07 // TFT_TE D13 = PB23 // built-in LED D24 = PA00 // TFT_RESET D25 = PB31 // TFT_BACKLIGHT D26 = PB09 // TFT_WR D27 = PB02 // SDA D28 = PB03 // SCL D29 = PA12 // SDO D30 = PA13 // SCK D31 = PA14 // SDI D32 = PB30 // SD_CS D33 = PA01 // SD_CARD_DETECT D34 = PA16 // LCD_DATA0 D35 = PA17 // LCD_DATA1 D36 = PA18 // LCD_DATA2 D37 = PA19 // LCD_DATA3 D38 = PA20 // LCD_DATA4 D39 = PA21 // LCD_DATA5 D40 = PA22 // LCD_DATA6 D41 = PA23 // LCD_DATA7 D42 = PB10 // QSPI D43 = PB11 // QSPI D44 = PA08 // QSPI D45 = PA09 // QSPI D46 = PA10 // QSPI D47 = PA11 // QSPI D50 = PA02 // speaker amplifier shutdown D51 = PA15 // NINA_RTS NINA_CS = D8 NINA_ACK = D5 NINA_GPIO0 = D6 NINA_RESETN = D7 // pins used for the ESP32 connection do not allow hardware // flow control, which is required. have to emulate with software. NINA_TX = D1 NINA_RX = D0 NINA_CTS = NINA_ACK NINA_RTS = NINA_GPIO0 LCD_DATA0 = D34 TFT_RD = D9 TFT_DC = D10 TFT_CS = D11 TFT_TE = D12 TFT_RESET = D24 TFT_BACKLIGHT = D25 TFT_WR = D26 NEOPIXEL = D2 WS2812 = D2 SPK_SD = D50 ) // Analog pins const ( A0 = PA02 // ADC0/AIN[0] A1 = D3 // ADC0/AIN[4] A2 = PA07 // ADC0/AIN[7] A3 = D4 // ADC0/AIN[5] A4 = PB00 // ADC0/AIN[12] A5 = PB01 // ADC0/AIN[13] A6 = PA06 // ADC0/AIN[6] A7 = PB08 // ADC1/AIN[0] AUDIO_OUT = A0 LIGHT = A2 TOUCH_YD = A4 TOUCH_XL = A5 TOUCH_YU = A6 TOUCH_XR = A7 ) const ( LED = D13 ) // USBCDC pins const ( USBCDC_DM_PIN = PA24 USBCDC_DP_PIN = PA25 ) // UART1 aka NINA_TX/NINA_RX const ( UART_TX_PIN = D1 UART_RX_PIN = D0 ) var ( UART1 = &sercomUSART4 DefaultUART = UART1 UART_NINA = UART1 ) // NINA-W102 settings const ( NINA_BAUDRATE = 115200 NINA_RESET_INVERTED = true NINA_SOFT_FLOWCONTROL = true ) // I2C pins const ( SDA_PIN = PB02 // SDA: SERCOM2/PAD[0] SCL_PIN = PB03 // SCL: SERCOM2/PAD[1] ) // I2C on the PyPortal. var ( I2C0 = sercomI2CM5 ) // SPI pins const ( SPI0_SCK_PIN = PA13 // SCK: SERCOM1/PAD[1] SPI0_SDO_PIN = PA12 // SDO: SERCOM1/PAD[3] SPI0_SDI_PIN = PA14 // SDI: SERCOM1/PAD[2] NINA_SDO = SPI0_SDO_PIN NINA_SDI = SPI0_SDI_PIN NINA_SCK = SPI0_SCK_PIN ) // SPI on the PyPortal. var ( SPI0 = sercomSPIM2 NINA_SPI = SPI0 ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Adafruit PyPortal M4" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x8035 ) ================================================ FILE: src/machine/board_qtpy.go ================================================ //go:build sam && atsamd21 && qtpy package machine // used to reset into bootloader const resetMagicValue = 0xf01669ef // GPIO Pins const ( D0 = PA02 // PWM available D1 = PA03 D2 = PA04 // PWM available D3 = PA05 // PWM available D4 = PA16 // PWM available D5 = PA17 // PWM available D6 = PA06 D7 = PA07 D8 = PA11 D9 = PA09 D10 = PA10 D11 = PA18 D12 = PA15 D13 = PA27 D14 = PA23 D15 = PA19 D16 = PA22 D17 = PA08 ) // Analog pins const ( A0 = D0 A1 = D1 A2 = D2 A3 = D3 A4 = D4 ) const ( NEOPIXELS = D11 WS2812 = D11 NEOPIXELS_POWER = D12 ) // USBCDC pins const ( USBCDC_DM_PIN = PA24 USBCDC_DP_PIN = PA25 ) // UART1 pins const ( UART_TX_PIN = D6 UART_RX_PIN = D7 ) // UART1 on the QT Py M0. var UART1 = &sercomUSART0 // SPI pins const ( SPI0_SCK_PIN = D8 SPI0_SDO_PIN = D10 SPI0_SDI_PIN = D9 ) // SPI on the QT Py M0. var SPI0 = sercomSPIM0 // I2C pins const ( SDA_PIN = D4 // SDA SCL_PIN = D5 // SCL ) // I2C on the QT Py M0. var ( I2C0 = sercomI2CM1 ) // I2S pins const ( I2S_SCK_PIN = PA10 I2S_SDO_PIN = PA08 I2S_SDI_PIN = NoPin // TODO: figure out what this is on QT Py M0. I2S_WS_PIN = NoPin // TODO: figure out what this is on QT Py M0. ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Adafruit QTPy M0" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x80CB ) var ( DefaultUART = UART1 ) ================================================ FILE: src/machine/board_qtpy_esp32c3.go ================================================ //go:build qtpy_esp32c3 // This file contains the pin mappings for the Adafruit QtPy ESP32C3 boards. // // https://learn.adafruit.com/adafruit-qt-py-esp32-c3-wifi-dev-board/pinouts package machine // Digital Pins const ( D0 = GPIO4 D1 = GPIO3 D2 = GPIO1 D3 = GPIO0 ) // Analog pins (ADC1) const ( A0 = GPIO4 A1 = GPIO3 A2 = GPIO1 A3 = GPIO0 ) // UART pins const ( RX_PIN = GPIO20 TX_PIN = GPIO21 UART_RX_PIN = RX_PIN UART_TX_PIN = TX_PIN ) // I2C pins const ( SDA_PIN = GPIO5 SCL_PIN = GPIO6 I2C0_SDA_PIN = SDA_PIN I2C0_SCL_PIN = SCL_PIN ) // SPI pins const ( SCK_PIN = GPIO10 MI_PIN = GPIO8 MO_PIN = GPIO7 SPI_SCK_PIN = SCK_PIN SPI_SDI_PIN = MI_PIN SPI_SDO_PIN = MO_PIN ) const ( NEOPIXEL = GPIO2 WS2812 = GPIO2 // also used for boot button. // set it to be an input-with-pullup BUTTON = GPIO9 ) ================================================ FILE: src/machine/board_qtpy_rp2040.go ================================================ //go:build qtpy_rp2040 package machine // Onboard crystal oscillator frequency, in MHz. const xoscFreq = 12 // MHz // GPIO Pins const ( SDA = GPIO24 SCL = GPIO25 TX = GPIO20 MO = GPIO3 MOSI = GPIO3 MI = GPIO4 MISO = GPIO4 SCK = GPIO6 RX = GPIO5 QT_SCL1 = GPIO23 QT_SDA1 = GPIO22 ) // Analog pins const ( A0 = GPIO29 A1 = GPIO28 A2 = GPIO27 A3 = GPIO26 ) const ( NEOPIXEL = GPIO12 WS2812 = GPIO12 NEOPIXEL_POWER = GPIO11 ) // I2C Pins. const ( I2C0_SDA_PIN = GPIO24 I2C0_SCL_PIN = GPIO25 I2C1_SDA_PIN = GPIO26 I2C1_SCL_PIN = GPIO27 I2C1_QT_SDA_PIN = GPIO22 I2C1_QT_SCL_PIN = GPIO23 SDA_PIN = GPIO24 SCL_PIN = GPIO25 ) // SPI default pins const ( // Default Serial Clock Bus 0 for SPI communications SPI0_SCK_PIN = GPIO6 // Default Serial Out Bus 0 for SPI communications SPI0_SDO_PIN = GPIO3 // Tx // Default Serial In Bus 0 for SPI communications SPI0_SDI_PIN = GPIO4 // Rx SPI0_CS = GPIO5 // Default Serial Clock Bus 1 for SPI communications SPI1_SCK_PIN = GPIO26 // Default Serial Out Bus 1 for SPI communications SPI1_SDO_PIN = GPIO27 // Tx // Default Serial In Bus 1 for SPI communications SPI1_SDI_PIN = GPIO24 // Rx SPI1_CS = GPIO25 ) // UART pins const ( UART0_TX_PIN = GPIO28 UART0_RX_PIN = GPIO29 UART1_TX_PIN = GPIO20 UART1_RX_PIN = GPIO5 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) var DefaultUART = UART0 // USB identifiers const ( usb_STRING_PRODUCT = "QT Py RP2040" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x80F7 ) ================================================ FILE: src/machine/board_rak4631.go ================================================ //go:build rak4631 package machine const HasLowFrequencyCrystal = true // Digital Pins const ( D0 Pin = P0_28 D1 Pin = P0_02 ) // Analog pins const ( A0 Pin = P0_17 A1 Pin = P1_02 A2 Pin = P0_21 ) // Onboard LEDs const ( LED = LED2 LED1 = P1_03 LED2 = P1_04 ) // UART pins const ( // Default to UART1 UART_RX_PIN = UART0_RX_PIN UART_TX_PIN = UART0_TX_PIN // UART1 UART0_RX_PIN = P0_19 UART0_TX_PIN = P0_20 // UART2 UART1_RX_PIN = P0_15 UART1_TX_PIN = P0_16 ) // I2C pins const ( SDA_PIN = SDA1_PIN SCL_PIN = SCL1_PIN SDA1_PIN = P0_13 SCL1_PIN = P0_14 SDA2_PIN = P0_24 SCL2_PIN = P0_25 ) // SPI pins const ( SPI0_SCK_PIN = P0_03 SPI0_SDO_PIN = P0_29 SPI0_SDI_PIN = P0_30 ) // Peripherals const ( LORA_NSS = P1_10 LORA_SCK = P1_11 LORA_MOSI = P1_12 LORA_MISO = P1_13 LORA_BUSY = P1_14 LORA_DIO1 = P1_15 LORA_NRESET = P1_06 LORA_POWER = P1_05 ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "WisCore RAK4631 Board" usb_STRING_MANUFACTURER = "RAKwireless" ) var ( usb_VID uint16 = 0x239a usb_PID uint16 = 0x8029 ) var ( DefaultUART = UART0 ) ================================================ FILE: src/machine/board_reelboard.go ================================================ //go:build reelboard package machine const HasLowFrequencyCrystal = true // Pins on the reel board const ( LED_RED Pin = 11 LED_GREEN Pin = 12 LED_BLUE Pin = 41 LED_YELLOW Pin = 13 LED1 Pin = LED_YELLOW LED2 Pin = LED_RED LED3 Pin = LED_GREEN LED4 Pin = LED_BLUE LED Pin = LED1 EPD_BUSY_PIN Pin = 14 EPD_RESET_PIN Pin = 15 EPD_DC_PIN Pin = 16 EPD_CS_PIN Pin = 17 EPD_SCK_PIN Pin = 19 EPD_SDO_PIN Pin = 20 POWER_SUPPLY_PIN Pin = 32 ) // User "a" button on the reel board const ( BUTTON Pin = 7 ) var DefaultUART = UART0 // UART pins const ( UART_TX_PIN Pin = 6 UART_RX_PIN Pin = 8 ) // I2C pins const ( SDA_PIN Pin = 26 SCL_PIN Pin = 27 ) // SPI pins const ( SPI0_SCK_PIN Pin = 47 SPI0_SDO_PIN Pin = 45 SPI0_SDI_PIN Pin = 46 ) // PowerSupplyActive enables the supply voltages for nRF52840 and peripherals (true) or only for nRF52840 (false) // This controls the TPS610981 boost converter. You must turn the power supply active in order to use the EPD and // other onboard peripherals. func PowerSupplyActive(active bool) { POWER_SUPPLY_PIN.Configure(PinConfig{Mode: PinOutput}) if active { POWER_SUPPLY_PIN.High() } else { POWER_SUPPLY_PIN.Low() } } // USB CDC identifiers const ( usb_STRING_PRODUCT = "PHYTEC reelboard" usb_STRING_MANUFACTURER = "PHYTEC" ) var ( usb_VID uint16 = 0x2FE3 usb_PID uint16 = 0x100 ) ================================================ FILE: src/machine/board_stm32f469disco.go ================================================ //go:build stm32f469disco package machine import ( "device/stm32" "runtime/interrupt" ) const ( LED = LED_BUILTIN LED1 = LED_GREEN LED2 = LED_ORANGE LED3 = LED_RED LED4 = LED_BLUE LED_BUILTIN = LED_GREEN LED_GREEN = PG6 LED_ORANGE = PD4 LED_RED = PD5 LED_BLUE = PK3 ) const ( BUTTON = PA0 ) // UART pins const ( UART_TX_PIN = PB10 UART_RX_PIN = PB11 ) var ( UART3 = &_UART3 _UART3 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART3, TxAltFuncSelector: AF7_USART1_2_3, RxAltFuncSelector: AF7_USART1_2_3, } DefaultUART = UART3 ) // set up RX IRQ handler. Follow similar pattern for other UARTx instances func init() { UART3.Interrupt = interrupt.New(stm32.IRQ_USART3, _UART3.handleInterrupt) } // SPI pins const ( SPI1_SCK_PIN = PA5 SPI1_SDI_PIN = PA6 SPI1_SDO_PIN = PA7 SPI0_SCK_PIN = SPI1_SCK_PIN SPI0_SDI_PIN = SPI1_SDI_PIN SPI0_SDO_PIN = SPI1_SDO_PIN ) // Since the first interface is named SPI1, both SPI0 and SPI1 refer to SPI1. // TODO: implement SPI2 and SPI3. var ( SPI0 = &SPI{ Bus: stm32.SPI1, AltFuncSelector: AF5_SPI1_SPI2, } SPI1 = SPI0 ) const ( I2C0_SCL_PIN = PB6 I2C0_SDA_PIN = PB9 ) var ( I2C0 = &I2C{ Bus: stm32.I2C1, AltFuncSelector: AF4_I2C1_2_3, } ) ================================================ FILE: src/machine/board_stm32f4disco.go ================================================ //go:build stm32f4disco package machine import ( "device/stm32" "runtime/interrupt" ) const ( LED1 = LED_GREEN LED2 = LED_ORANGE LED3 = LED_RED LED4 = LED_BLUE LED_GREEN = PD12 LED_ORANGE = PD13 LED_RED = PD14 LED_BLUE = PD15 LED = LED_BUILTIN LED_BUILTIN = LED_GREEN ) const ( BUTTON = PA0 ) // Analog Pins const ( ADC0 = PA0 ADC1 = PA1 ADC2 = PA2 ADC3 = PA3 ADC4 = PA4 ADC5 = PA5 ADC6 = PA6 ADC7 = PA7 ADC8 = PB0 ADC9 = PB1 ADC10 = PC0 ADC11 = PC1 ADC12 = PC2 ADC13 = PC3 ADC14 = PC4 ADC15 = PC5 ) // UART pins const ( UART_TX_PIN = PA2 UART_RX_PIN = PA3 ) var ( UART1 = &_UART1 _UART1 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART2, TxAltFuncSelector: AF7_USART1_2_3, RxAltFuncSelector: AF7_USART1_2_3, } DefaultUART = UART1 ) // set up RX IRQ handler. Follow similar pattern for other UARTx instances func init() { UART1.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART1.handleInterrupt) } // SPI pins const ( SPI1_SCK_PIN = PA5 SPI1_SDI_PIN = PA6 SPI1_SDO_PIN = PA7 SPI0_SCK_PIN = SPI1_SCK_PIN SPI0_SDI_PIN = SPI1_SDI_PIN SPI0_SDO_PIN = SPI1_SDO_PIN ) // MEMs accelerometer const ( MEMS_ACCEL_CS = PE3 MEMS_ACCEL_INT1 = PE0 MEMS_ACCEL_INT2 = PE1 ) // Since the first interface is named SPI1, both SPI0 and SPI1 refer to SPI1. // TODO: implement SPI2 and SPI3. var ( SPI0 = &SPI{ Bus: stm32.SPI1, AltFuncSelector: AF5_SPI1_SPI2, } SPI1 = SPI0 ) const ( I2C0_SCL_PIN = PB6 I2C0_SDA_PIN = PB9 ) var ( I2C0 = &I2C{ Bus: stm32.I2C1, AltFuncSelector: AF4_I2C1_2_3, } ) ================================================ FILE: src/machine/board_stm32l0x1.go ================================================ //go:build stm32l0x1 && !nucleol031k6 // This file is for the bare STM32L0x1 (not for any specific board that is based // on the STM32L0x1). package machine const ( I2C0_SCL_PIN = NoPin I2C0_SDA_PIN = NoPin UART_TX_PIN = NoPin UART_RX_PIN = NoPin SPI0_SDI_PIN = NoPin SPI0_SDO_PIN = NoPin SPI0_SCK_PIN = NoPin ) ================================================ FILE: src/machine/board_swan.go ================================================ //go:build swan package machine import ( "device/stm32" "runtime/interrupt" ) const ( // LED on the SWAN LED = PE2 // UART pins // PA9 and PA10 are connected to the SWAN Tx/Rx UART_TX_PIN = PA9 UART_RX_PIN = PA10 // I2C pins // PB6 is SCL // PB7 is SDA I2C0_SCL_PIN = PB6 I2C0_SDA_PIN = PB7 // SPI pins SPI1_SCK_PIN = PD1 SPI1_SDI_PIN = PB14 SPI1_SDO_PIN = PB15 SPI0_SCK_PIN = SPI1_SCK_PIN SPI0_SDI_PIN = SPI1_SDI_PIN SPI0_SDO_PIN = SPI1_SDO_PIN ) var ( // USART1 is connected to the TX/RX pins UART1 = &_UART1 _UART1 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART1, TxAltFuncSelector: 7, RxAltFuncSelector: 7, } DefaultUART = UART1 // I2C1 is documented, alias to I2C0 as well I2C1 = &I2C{ Bus: stm32.I2C1, AltFuncSelector: 4, } I2C0 = I2C1 // SPI1 is documented, alias to SPI0 as well SPI1 = &SPI{ Bus: stm32.SPI2, AltFuncSelector: 5, } SPI0 = SPI1 ) func init() { UART1.Interrupt = interrupt.New(stm32.IRQ_USART1, _UART1.handleInterrupt) } ================================================ FILE: src/machine/board_teensy36.go ================================================ //go:build nxp && mk66f18 && teensy36 package machine // CPUFrequency returns the frequency of the ARM core clock (180MHz) func CPUFrequency() uint32 { return 180000000 } // ClockFrequency returns the frequency of the external oscillator (16MHz) func ClockFrequency() uint32 { return 16000000 } // digital IO const ( D00 = PB16 D01 = PB17 D02 = PD00 D03 = PA12 D04 = PA13 D05 = PD07 D06 = PD04 D07 = PD02 D08 = PD03 D09 = PC03 D10 = PC04 D11 = PC06 D12 = PC07 D13 = PC05 D14 = PD01 D15 = PC00 D16 = PB00 D17 = PB01 D18 = PB03 D19 = PB02 D20 = PD05 D21 = PD06 D22 = PC01 D23 = PC02 D24 = PE26 D25 = PA05 D26 = PA14 D27 = PA15 D28 = PA16 D29 = PB18 D30 = PB19 D31 = PB10 D32 = PB11 D33 = PE24 D34 = PE25 D35 = PC08 D36 = PC09 D37 = PC10 D38 = PC11 D39 = PA17 D40 = PA28 D41 = PA29 D42 = PA26 D43 = PB20 D44 = PB22 D45 = PB23 D46 = PB21 D47 = PD08 D48 = PD09 D49 = PB04 D50 = PB05 D51 = PD14 D52 = PD13 D53 = PD12 D54 = PD15 D55 = PD11 D56 = PE10 D57 = PE11 D58 = PE00 D59 = PE01 D60 = PE02 D61 = PE03 D62 = PE04 D63 = PE05 ) // LED on the Teensy const LED = PC05 var ( TeensyUART1 = UART0 TeensyUART2 = UART1 TeensyUART3 = UART2 TeensyUART4 = UART3 TeensyUART5 = UART4 ) var DefaultUART = UART0 const ( defaultUART0RX = D00 defaultUART0TX = D01 defaultUART1RX = D09 defaultUART1TX = D10 defaultUART2RX = D07 defaultUART2TX = D08 defaultUART3RX = D31 defaultUART3TX = D32 defaultUART4RX = D34 defaultUART4TX = D33 ) ================================================ FILE: src/machine/board_teensy40.go ================================================ //go:build teensy40 package machine import ( "device/nxp" "runtime/interrupt" ) // Digital pins const ( // = Pin // [Pad]: Alt Func 0 Alt Func 1 Alt Func 2 Alt Func 3 Alt Func 4 Alt Func 5 Alt Func 6 Alt Func 7 Alt Func 8 Alt Func 9 // = ---- ----------- --------------- --------------- --------------- -------------- -------------------- ---------- -------------------- --------------------- --------------------- ---------------- D0 = PA3 // [AD_B0_03]: FLEXCAN2_RX XBAR1_INOUT17 LPUART6_RX USB_OTG1_OC FLEXPWM1_PWMX01 GPIO1_IO03 REF_CLK_24M LPSPI3_PCS0 ~ ~ D1 = PA2 // [AD_B0_02]: FLEXCAN2_TX XBAR1_INOUT16 LPUART6_TX USB_OTG1_PWR FLEXPWM1_PWMX00 GPIO1_IO02 LPI2C1_HREQ LPSPI3_SDI ~ ~ D2 = PD4 // [EMC_04]: SEMC_DATA04 FLEXPWM4_PWMA02 SAI2_TX_DATA XBAR1_INOUT06 FLEXIO1_FLEXIO04 GPIO4_IO04 ~ ~ ~ ~ D3 = PD5 // [EMC_05]: SEMC_DATA05 FLEXPWM4_PWMB02 SAI2_TX_SYNC XBAR1_INOUT07 FLEXIO1_FLEXIO05 GPIO4_IO05 ~ ~ ~ ~ D4 = PD6 // [EMC_06]: SEMC_DATA06 FLEXPWM2_PWMA00 SAI2_TX_BCLK XBAR1_INOUT08 FLEXIO1_FLEXIO06 GPIO4_IO06 ~ ~ ~ ~ D5 = PD8 // [EMC_08]: SEMC_DM00 FLEXPWM2_PWMA01 SAI2_RX_DATA XBAR1_INOUT17 FLEXIO1_FLEXIO08 GPIO4_IO08 ~ ~ ~ ~ D6 = PB10 // [B0_10]: LCD_DATA06 QTIMER4_TIMER1 FLEXPWM2_PWMA02 SAI1_TX_DATA03 FLEXIO2_FLEXIO10 GPIO2_IO10 SRC_BOOT_CFG06 ENET2_CRS ~ ~ D7 = PB17 // [B1_01]: LCD_DATA13 XBAR1_INOUT15 LPUART4_RX SAI1_TX_DATA00 FLEXIO2_FLEXIO17 GPIO2_IO17 FLEXPWM1_PWMB03 ENET2_RDATA00 FLEXIO3_FLEXIO17 ~ D8 = PB16 // [B1_00]: LCD_DATA12 XBAR1_INOUT14 LPUART4_TX SAI1_RX_DATA00 FLEXIO2_FLEXIO16 GPIO2_IO16 FLEXPWM1_PWMA03 ENET2_RX_ER FLEXIO3_FLEXIO16 ~ D9 = PB11 // [B0_11]: LCD_DATA07 QTIMER4_TIMER2 FLEXPWM2_PWMB02 SAI1_TX_DATA02 FLEXIO2_FLEXIO11 GPIO2_IO11 SRC_BOOT_CFG07 ENET2_COL ~ ~ D10 = PB0 // [B0_00]: LCD_CLK QTIMER1_TIMER0 MQS_RIGHT LPSPI4_PCS0 FLEXIO2_FLEXIO00 GPIO2_IO00 SEMC_CSX01 ENET2_MDC ~ ~ D11 = PB2 // [B0_02]: LCD_HSYNC QTIMER1_TIMER2 FLEXCAN1_TX LPSPI4_SDO FLEXIO2_FLEXIO02 GPIO2_IO02 SEMC_CSX03 ENET2_1588_EVENT0_OUT ~ ~ D12 = PB1 // [B0_01]: LCD_ENABLE QTIMER1_TIMER1 MQS_LEFT LPSPI4_SDI FLEXIO2_FLEXIO01 GPIO2_IO01 SEMC_CSX02 ENET2_MDIO ~ ~ D13 = PB3 // [B0_03]: LCD_VSYNC QTIMER2_TIMER0 FLEXCAN1_RX LPSPI4_SCK FLEXIO2_FLEXIO03 GPIO2_IO03 WDOG2_RESET_B_DEB ENET2_1588_EVENT0_IN ~ ~ D14 = PA18 // [AD_B1_02]: USB_OTG1_ID QTIMER3_TIMER2 LPUART2_TX SPDIF_OUT ENET_1588_EVENT2_OUT GPIO1_IO18 USDHC1_CD_B KPP_ROW06 GPT2_CLK FLEXIO3_FLEXIO02 D15 = PA19 // [AD_B1_03]: USB_OTG1_OC QTIMER3_TIMER3 LPUART2_RX SPDIF_IN ENET_1588_EVENT2_IN GPIO1_IO19 USDHC2_CD_B KPP_COL06 GPT2_CAPTURE1 FLEXIO3_FLEXIO03 D16 = PA23 // [AD_B1_07]: FLEXSPIB_DATA00 LPI2C3_SCL LPUART3_RX SPDIF_EXT_CLK CSI_HSYNC GPIO1_IO23 USDHC2_DATA3 KPP_COL04 GPT2_COMPARE3 FLEXIO3_FLEXIO07 D17 = PA22 // [AD_B1_06]: FLEXSPIB_DATA01 LPI2C3_SDA LPUART3_TX SPDIF_LOCK CSI_VSYNC GPIO1_IO22 USDHC2_DATA2 KPP_ROW04 GPT2_COMPARE2 FLEXIO3_FLEXIO06 D18 = PA17 // [AD_B1_01]: USB_OTG1_PWR QTIMER3_TIMER1 LPUART2_RTS_B LPI2C1_SDA CCM_PMIC_READY GPIO1_IO17 USDHC1_VSELECT KPP_COL07 ENET2_1588_EVENT0_IN FLEXIO3_FLEXIO01 D19 = PA16 // [AD_B1_00]: USB_OTG2_ID QTIMER3_TIMER0 LPUART2_CTS_B LPI2C1_SCL WDOG1_B GPIO1_IO16 USDHC1_WP KPP_ROW07 ENET2_1588_EVENT0_OUT FLEXIO3_FLEXIO00 D20 = PA26 // [AD_B1_10]: FLEXSPIA_DATA03 WDOG1_B LPUART8_TX SAI1_RX_SYNC CSI_DATA07 GPIO1_IO26 USDHC2_WP KPP_ROW02 ENET2_1588_EVENT1_OUT FLEXIO3_FLEXIO10 D21 = PA27 // [AD_B1_11]: FLEXSPIA_DATA02 EWM_OUT_B LPUART8_RX SAI1_RX_BCLK CSI_DATA06 GPIO1_IO27 USDHC2_RESET_B KPP_COL02 ENET2_1588_EVENT1_IN FLEXIO3_FLEXIO11 D22 = PA24 // [AD_B1_08]: FLEXSPIA_SS1_B FLEXPWM4_PWMA00 FLEXCAN1_TX CCM_PMIC_READY CSI_DATA09 GPIO1_IO24 USDHC2_CMD KPP_ROW03 FLEXIO3_FLEXIO08 ~ D23 = PA25 // [AD_B1_09]: FLEXSPIA_DQS FLEXPWM4_PWMA01 FLEXCAN1_RX SAI1_MCLK CSI_DATA08 GPIO1_IO25 USDHC2_CLK KPP_COL03 FLEXIO3_FLEXIO09 ~ D24 = PA12 // [AD_B0_12]: LPI2C4_SCL CCM_PMIC_READY LPUART1_TX WDOG2_WDOG_B FLEXPWM1_PWMX02 GPIO1_IO12 ENET_1588_EVENT1_OUT NMI_GLUE_NMI ~ ~ D25 = PA13 // [AD_B0_13]: LPI2C4_SDA GPT1_CLK LPUART1_RX EWM_OUT_B FLEXPWM1_PWMX03 GPIO1_IO13 ENET_1588_EVENT1_IN REF_CLK_24M ~ ~ D26 = PA30 // [AD_B1_14]: FLEXSPIA_SCLK ACMP_OUT02 LPSPI3_SDO SAI1_TX_BCLK CSI_DATA03 GPIO1_IO30 USDHC2_DATA6 KPP_ROW00 ENET2_1588_EVENT3_OUT FLEXIO3_FLEXIO14 D27 = PA31 // [AD_B1_15]: FLEXSPIA_SS0_B ACMP_OUT03 LPSPI3_SCK SAI1_TX_SYNC CSI_DATA02 GPIO1_IO31 USDHC2_DATA7 KPP_COL00 ENET2_1588_EVENT3_IN FLEXIO3_FLEXIO15 D28 = PC18 // [EMC_32]: SEMC_DATA10 FLEXPWM3_PWMB01 LPUART7_RX CCM_PMIC_RDY CSI_DATA21 GPIO3_IO18 ENET2_TX_EN ~ ~ ~ D29 = PD31 // [EMC_31]: SEMC_DATA09 FLEXPWM3_PWMA01 LPUART7_TX LPSPI1_PCS1 CSI_DATA22 GPIO4_IO31 ENET2_TDATA01 ~ ~ ~ D30 = PC23 // [EMC_37]: SEMC_DATA15 XBAR1_IN23 GPT1_COMPARE3 SAI3_MCLK CSI_DATA16 GPIO3_IO23 USDHC2_WP ENET2_RX_EN FLEXCAN3_RX ~ D31 = PC22 // [EMC_36]: SEMC_DATA14 XBAR1_IN22 GPT1_COMPARE2 SAI3_TX_DATA CSI_DATA17 GPIO3_IO22 USDHC1_WP ENET2_RDATA01 FLEXCAN3_TX ~ D32 = PB12 // [B0_12]: LCD_DATA08 XBAR1_INOUT10 ARM_TRACE_CLK SAI1_TX_DATA01 FLEXIO2_FLEXIO12 GPIO2_IO12 SRC_BOOT_CFG08 ENET2_TDATA00 ~ ~ D33 = PD7 // [EMC_07]: SEMC_DATA07 FLEXPWM2_PWMB00 SAI2_MCLK XBAR1_INOUT09 FLEXIO1_FLEXIO07 GPIO4_IO07 ~ ~ ~ ~ D34 = PC15 // [SD_B0_03]: USDHC1_DATA1 FLEXPWM1_PWMB01 LPUART8_RTS_B XBAR1_INOUT07 LPSPI1_SDI GPIO3_IO15 ENET2_RDATA00 SEMC_CLK6 ~ ~ D35 = PC14 // [SD_B0_02]: USDHC1_DATA0 FLEXPWM1_PWMA01 LPUART8_CTS_B XBAR1_INOUT06 LPSPI1_SDO GPIO3_IO14 ENET2_RX_ER SEMC_CLK5 ~ ~ D36 = PC13 // [SD_B0_01]: USDHC1_CLK FLEXPWM1_PWMB00 LPI2C3_SDA XBAR1_INOUT05 LPSPI1_PCS0 GPIO3_IO13 FLEXSPIB_SS1_B ENET2_TX_CLK ENET2_REF_CLK2 ~ D37 = PC12 // [SD_B0_00]: USDHC1_CMD FLEXPWM1_PWMA00 LPI2C3_SCL XBAR1_INOUT04 LPSPI1_SCK GPIO3_IO12 FLEXSPIA_SS1_B ENET2_TX_EN SEMC_DQS4 ~ D38 = PC17 // [SD_B0_05]: USDHC1_DATA3 FLEXPWM1_PWMB02 LPUART8_RX XBAR1_INOUT09 FLEXSPIB_DQS GPIO3_IO17 CCM_CLKO2 ENET2_RX_EN ~ ~ D39 = PC16 // [SD_B0_04]: USDHC1_DATA2 FLEXPWM1_PWMA02 LPUART8_TX XBAR1_INOUT08 FLEXSPIB_SS0_B GPIO3_IO16 CCM_CLKO1 ENET2_RDATA01 ~ ~ ) // Analog pins const ( // = Pin // Dig | [Pad] {ADC1/ADC2} A0 = PA18 // D14 | [AD_B1_02] { 7 / 7 } A1 = PA19 // D15 | [AD_B1_03] { 8 / 8 } A2 = PA23 // D16 | [AD_B1_07] { 12 / 12 } A3 = PA22 // D17 | [AD_B1_06] { 11 / 11 } A4 = PA17 // D18 | [AD_B1_01] { 6 / 6 } A5 = PA16 // D19 | [AD_B1_00] { 5 / 5 } A6 = PA26 // D20 | [AD_B1_10] { 15 / 15 } A7 = PA27 // D21 | [AD_B1_11] { 0 / 0 } A8 = PA24 // D22 | [AD_B1_08] { 13 / 13 } A9 = PA25 // D23 | [AD_B1_09] { 14 / 14 } A10 = PA12 // D24 | [AD_B0_12] { 1 / - } A11 = PA13 // D25 | [AD_B0_13] { 2 / - } A12 = PA30 // D26 | [AD_B1_14] { - / 3 } A13 = PA31 // D27 | [AD_B1_15] { - / 4 } ) // Default peripheral pins const ( LED = D13 UART_RX_PIN = UART1_RX_PIN // D0 UART_TX_PIN = UART1_TX_PIN // D1 SPI_SDI_PIN = SPI1_SDI_PIN // D12 SPI_SDO_PIN = SPI1_SDO_PIN // D11 SPI_SCK_PIN = SPI1_SCK_PIN // D13 SPI_CS_PIN = SPI1_CS_PIN // D10 I2C_SDA_PIN = I2C1_SDA_PIN // D18/A4 I2C_SCL_PIN = I2C1_SCL_PIN // D19/A5 ) // Default peripherals var ( DefaultUART = UART1 ) func init() { // register any interrupt handlers for this board's peripherals _UART1.Interrupt = interrupt.New(nxp.IRQ_LPUART6, _UART1.handleInterrupt) _UART2.Interrupt = interrupt.New(nxp.IRQ_LPUART4, _UART2.handleInterrupt) _UART3.Interrupt = interrupt.New(nxp.IRQ_LPUART2, _UART3.handleInterrupt) _UART4.Interrupt = interrupt.New(nxp.IRQ_LPUART3, _UART4.handleInterrupt) _UART5.Interrupt = interrupt.New(nxp.IRQ_LPUART8, _UART5.handleInterrupt) _UART6.Interrupt = interrupt.New(nxp.IRQ_LPUART1, _UART6.handleInterrupt) _UART7.Interrupt = interrupt.New(nxp.IRQ_LPUART7, _UART7.handleInterrupt) } // #=====================================================# // | UART | // #===========#===========#=============#===============# // | Interface | Hardware | Clock(Freq) | RX/TX : Alt | // #===========#===========#=============#=========-=====# // | UART1 | LPUART6 | OSC(24 MHz) | D0/D1 : 2/2 | // | UART2 | LPUART4 | OSC(24 MHz) | D7/D8 : 2/2 | // | UART3 | LPUART2 | OSC(24 MHz) | D15/D14 : 2/2 | // | UART4 | LPUART3 | OSC(24 MHz) | D16/D17 : 2/2 | // | UART5 | LPUART8 | OSC(24 MHz) | D21/D20 : 2/2 | // | UART6 | LPUART1 | OSC(24 MHz) | D25/D24 : 2/2 | // | UART7 | LPUART7 | OSC(24 MHz) | D28/D29 : 2/2 | // #===========#===========#=============#=========-=====# const ( UART1_RX_PIN = D0 UART1_TX_PIN = D1 UART2_RX_PIN = D7 UART2_TX_PIN = D8 UART3_RX_PIN = D15 UART3_TX_PIN = D14 UART4_RX_PIN = D16 UART4_TX_PIN = D17 UART5_RX_PIN = D21 UART5_TX_PIN = D20 UART6_RX_PIN = D25 UART6_TX_PIN = D24 UART7_RX_PIN = D28 UART7_TX_PIN = D29 ) var ( UART1 = &_UART1 _UART1 = UART{ Bus: nxp.LPUART6, Buffer: NewRingBuffer(), txBuffer: NewRingBuffer(), muxRX: muxSelect{ // D0 (PA3 [AD_B0_03]) mux: nxp.IOMUXC_LPUART6_RX_SELECT_INPUT_DAISY_GPIO_AD_B0_03_ALT2, sel: &nxp.IOMUXC.LPUART6_RX_SELECT_INPUT, }, muxTX: muxSelect{ // D1 (PA2 [AD_B0_02]) mux: nxp.IOMUXC_LPUART6_TX_SELECT_INPUT_DAISY_GPIO_AD_B0_02_ALT2, sel: &nxp.IOMUXC.LPUART6_TX_SELECT_INPUT, }, } UART2 = &_UART2 _UART2 = UART{ Bus: nxp.LPUART4, Buffer: NewRingBuffer(), txBuffer: NewRingBuffer(), muxRX: muxSelect{ // D7 (PB17 [B1_01]) mux: nxp.IOMUXC_LPUART4_RX_SELECT_INPUT_DAISY_GPIO_B1_01_ALT2, sel: &nxp.IOMUXC.LPUART4_RX_SELECT_INPUT, }, muxTX: muxSelect{ // D8 (PB16 [B1_00]) mux: nxp.IOMUXC_LPUART4_TX_SELECT_INPUT_DAISY_GPIO_B1_00_ALT2, sel: &nxp.IOMUXC.LPUART4_TX_SELECT_INPUT, }, } UART3 = &_UART3 _UART3 = UART{ Bus: nxp.LPUART2, Buffer: NewRingBuffer(), txBuffer: NewRingBuffer(), muxRX: muxSelect{ // D15 (PA19 [AD_B1_03]) mux: nxp.IOMUXC_LPUART2_RX_SELECT_INPUT_DAISY_GPIO_AD_B1_03_ALT2, sel: &nxp.IOMUXC.LPUART2_RX_SELECT_INPUT, }, muxTX: muxSelect{ // D14 (PA18 [AD_B1_02]) mux: nxp.IOMUXC_LPUART2_TX_SELECT_INPUT_DAISY_GPIO_AD_B1_02_ALT2, sel: &nxp.IOMUXC.LPUART2_TX_SELECT_INPUT, }, } UART4 = &_UART4 _UART4 = UART{ Bus: nxp.LPUART3, Buffer: NewRingBuffer(), txBuffer: NewRingBuffer(), muxRX: muxSelect{ // D16 (PA23 [AD_B1_07]) mux: nxp.IOMUXC_LPUART3_RX_SELECT_INPUT_DAISY_GPIO_AD_B1_07_ALT2, sel: &nxp.IOMUXC.LPUART3_RX_SELECT_INPUT, }, muxTX: muxSelect{ // D17 (PA22 [AD_B1_06]) mux: nxp.IOMUXC_LPUART3_TX_SELECT_INPUT_DAISY_GPIO_AD_B1_06_ALT2, sel: &nxp.IOMUXC.LPUART3_TX_SELECT_INPUT, }, } UART5 = &_UART5 _UART5 = UART{ Bus: nxp.LPUART8, Buffer: NewRingBuffer(), txBuffer: NewRingBuffer(), muxRX: muxSelect{ // D21 (PA27 [AD_B1_11]) mux: nxp.IOMUXC_LPUART8_RX_SELECT_INPUT_DAISY_GPIO_AD_B1_11_ALT2, sel: &nxp.IOMUXC.LPUART8_RX_SELECT_INPUT, }, muxTX: muxSelect{ // D20 (PA26 [AD_B1_10]) mux: nxp.IOMUXC_LPUART8_TX_SELECT_INPUT_DAISY_GPIO_AD_B1_10_ALT2, sel: &nxp.IOMUXC.LPUART8_TX_SELECT_INPUT, }, } UART6 = &_UART6 _UART6 = UART{ Bus: nxp.LPUART1, Buffer: NewRingBuffer(), txBuffer: NewRingBuffer(), // LPUART1 not connected via IOMUXC // RX: D24 (PA12 [AD_B0_12]) // TX: D25 (PA13 [AD_B0_13]) } UART7 = &_UART7 _UART7 = UART{ Bus: nxp.LPUART7, Buffer: NewRingBuffer(), txBuffer: NewRingBuffer(), muxRX: muxSelect{ // D28 (PC18 [EMC_32]) mux: nxp.IOMUXC_LPUART7_RX_SELECT_INPUT_DAISY_GPIO_EMC_32_ALT2, sel: &nxp.IOMUXC.LPUART7_RX_SELECT_INPUT, }, muxTX: muxSelect{ // D29 (PD31 [EMC_31]) mux: nxp.IOMUXC_LPUART7_TX_SELECT_INPUT_DAISY_GPIO_EMC_31_ALT2, sel: &nxp.IOMUXC.LPUART7_TX_SELECT_INPUT, }, } ) // #==================================================================# // | SPI | // #===========#==========#===============#===========================# // | Interface | Hardware | Clock(Freq) | SDI/SDO/SCK/CS : Alt | // #===========#==========#===============#=================-=========# // | SPI1 | LPSPI4 | PLL2(132 MHz) | D12/D11/D13/D10 : 3/3/3/3 | // | SPI2 | LPSPI3 | PLL2(132 MHz) | D1/D26/D27/D0 : 7/2/2/7 | // | SPI3 | LPSPI1 | PLL2(132 MHz) | D34/D35/D37/D36 : 4/4/4/4 | // #===========#==========#===============#=================-=========# const ( SPI1_SDI_PIN = D12 SPI1_SDO_PIN = D11 SPI1_SCK_PIN = D13 SPI1_CS_PIN = D10 SPI2_SDI_PIN = D1 SPI2_SDO_PIN = D26 SPI2_SCK_PIN = D27 SPI2_CS_PIN = D0 SPI3_SDI_PIN = D34 SPI3_SDO_PIN = D35 SPI3_SCK_PIN = D37 SPI3_CS_PIN = D36 ) var ( SPI0 = SPI1 // SPI0 is an alias of SPI1 (LPSPI4) SPI1 = &SPI{ Bus: nxp.LPSPI4, muxSDI: muxSelect{ // D12 (PB1 [B0_01]) mux: nxp.IOMUXC_LPSPI4_SDI_SELECT_INPUT_DAISY_GPIO_B0_01_ALT3, sel: &nxp.IOMUXC.LPSPI4_SDI_SELECT_INPUT, }, muxSDO: muxSelect{ // D11 (PB2 [B0_02]) mux: nxp.IOMUXC_LPSPI4_SDO_SELECT_INPUT_DAISY_GPIO_B0_02_ALT3, sel: &nxp.IOMUXC.LPSPI4_SDO_SELECT_INPUT, }, muxSCK: muxSelect{ // D13 (PB3 [B0_03]) mux: nxp.IOMUXC_LPSPI4_SCK_SELECT_INPUT_DAISY_GPIO_B0_03_ALT3, sel: &nxp.IOMUXC.LPSPI4_SCK_SELECT_INPUT, }, muxCS: muxSelect{ // D10 (PB0 [B0_00]) mux: nxp.IOMUXC_LPSPI4_PCS0_SELECT_INPUT_DAISY_GPIO_B0_00_ALT3, sel: &nxp.IOMUXC.LPSPI4_PCS0_SELECT_INPUT, }, } SPI2 = &SPI{ Bus: nxp.LPSPI3, muxSDI: muxSelect{ // D1 (PA2 [AD_B0_02]) mux: nxp.IOMUXC_LPSPI3_SDI_SELECT_INPUT_DAISY_GPIO_AD_B0_02_ALT7, sel: &nxp.IOMUXC.LPSPI3_SDI_SELECT_INPUT, }, muxSDO: muxSelect{ // D26 (PA30 [AD_B1_14]) mux: nxp.IOMUXC_LPSPI3_SDO_SELECT_INPUT_DAISY_GPIO_AD_B1_14_ALT2, sel: &nxp.IOMUXC.LPSPI3_SDO_SELECT_INPUT, }, muxSCK: muxSelect{ // D27 (PA31 [AD_B1_15]) mux: nxp.IOMUXC_LPSPI3_SCK_SELECT_INPUT_DAISY_GPIO_AD_B1_15, sel: &nxp.IOMUXC.LPSPI3_SCK_SELECT_INPUT, }, muxCS: muxSelect{ // D0 (PA3 [AD_B0_03]) mux: nxp.IOMUXC_LPSPI3_PCS0_SELECT_INPUT_DAISY_GPIO_AD_B0_03_ALT7, sel: &nxp.IOMUXC.LPSPI3_PCS0_SELECT_INPUT, }, } SPI3 = &SPI{ Bus: nxp.LPSPI1, muxSDI: muxSelect{ // D34 (PC15 [SD_B0_03]) mux: nxp.IOMUXC_LPSPI1_SDI_SELECT_INPUT_DAISY_GPIO_SD_B0_03_ALT4, sel: &nxp.IOMUXC.LPSPI1_SDI_SELECT_INPUT, }, muxSDO: muxSelect{ // D35 (PC14 [SD_B0_02]) mux: nxp.IOMUXC_LPSPI1_SDO_SELECT_INPUT_DAISY_GPIO_SD_B0_02_ALT4, sel: &nxp.IOMUXC.LPSPI1_SDO_SELECT_INPUT, }, muxSCK: muxSelect{ // D37 (PC12 [SD_B0_00]) mux: nxp.IOMUXC_LPSPI1_SCK_SELECT_INPUT_DAISY_GPIO_SD_B0_00_ALT4, sel: &nxp.IOMUXC.LPSPI1_SCK_SELECT_INPUT, }, muxCS: muxSelect{ // D36 (PC13 [SD_B0_01]) mux: nxp.IOMUXC_LPSPI1_PCS0_SELECT_INPUT_DAISY_GPIO_SD_B0_01_ALT4, sel: &nxp.IOMUXC.LPSPI1_PCS0_SELECT_INPUT, }, } ) // #====================================================# // | I2C | // #===========#==========#=============#===============# // | Interface | Hardware | Clock(Freq) | SDA/SCL : Alt | // #===========#==========#=============#=========-=====# // | I2C1 | LPI2C1 | OSC(24 MHz) | D18/D19 : 3/3 | // | I2C2 | LPI2C3 | OSC(24 MHz) | D17/D16 : 1/1 | // | I2C3 | LPI2C4 | OSC(24 MHz) | D25/D24 : 0/0 | // #===========#==========#=============#=========-=====# const ( I2C1_SDA_PIN = D18 I2C1_SCL_PIN = D19 I2C2_SDA_PIN = D17 I2C2_SCL_PIN = D16 I2C3_SDA_PIN = D25 I2C3_SCL_PIN = D24 ) var ( I2C0 = I2C1 // I2C0 is an alias for I2C1 (LPI2C1) I2C1 = &_I2C1 _I2C1 = I2C{ Bus: nxp.LPI2C1, sda: I2C1_SDA_PIN, // D18 (PA17 [AD_B1_01]) scl: I2C1_SCL_PIN, // D19 (PA16 [AD_B1_00]) muxSDA: muxSelect{ mux: nxp.IOMUXC_LPI2C1_SDA_SELECT_INPUT_DAISY_GPIO_AD_B1_01_ALT3, sel: &nxp.IOMUXC.LPI2C1_SDA_SELECT_INPUT, }, muxSCL: muxSelect{ mux: nxp.IOMUXC_LPI2C1_SCL_SELECT_INPUT_DAISY_GPIO_AD_B1_00_ALT3, sel: &nxp.IOMUXC.LPI2C1_SCL_SELECT_INPUT, }, } I2C2 = &_I2C2 _I2C2 = I2C{ Bus: nxp.LPI2C3, sda: I2C2_SDA_PIN, // D17 (PA22 [AD_B1_06]) scl: I2C2_SCL_PIN, // D16 (PA23 [AD_B1_07]) muxSDA: muxSelect{ mux: nxp.IOMUXC_LPI2C3_SDA_SELECT_INPUT_DAISY_GPIO_AD_B1_06_ALT1, sel: &nxp.IOMUXC.LPI2C3_SDA_SELECT_INPUT, }, muxSCL: muxSelect{ mux: nxp.IOMUXC_LPI2C3_SCL_SELECT_INPUT_DAISY_GPIO_AD_B1_07_ALT1, sel: &nxp.IOMUXC.LPI2C3_SCL_SELECT_INPUT, }, } I2C3 = &_I2C3 _I2C3 = I2C{ Bus: nxp.LPI2C4, sda: I2C3_SDA_PIN, // D25 (PA13 [AD_B0_13]) scl: I2C3_SCL_PIN, // D24 (PA12 [AD_B0_12]) muxSDA: muxSelect{ mux: nxp.IOMUXC_LPI2C4_SDA_SELECT_INPUT_DAISY_GPIO_AD_B0_13_ALT0, sel: &nxp.IOMUXC.LPI2C4_SDA_SELECT_INPUT, }, muxSCL: muxSelect{ mux: nxp.IOMUXC_LPI2C4_SCL_SELECT_INPUT_DAISY_GPIO_AD_B0_12_ALT0, sel: &nxp.IOMUXC.LPI2C4_SCL_SELECT_INPUT, }, } ) ================================================ FILE: src/machine/board_teensy41.go ================================================ //go:build teensy41 package machine import ( "device/nxp" "runtime/interrupt" ) // Digital pins const ( // = Pin // [Pad]: Alt Func 0 Alt Func 1 Alt Func 2 Alt Func 3 Alt Func 4 Alt Func 5 Alt Func 6 Alt Func 7 Alt Func 8 Alt Func 9 // = ---- ----------- --------------- --------------- --------------- -------------- -------------------- ---------- -------------------- --------------------- --------------------- ---------------- D0 = PA3 // [AD_B0_03]: FLEXCAN2_RX XBAR1_INOUT17 LPUART6_RX USB_OTG1_OC FLEXPWM1_PWMX01 GPIO1_IO03 REF_CLK_24M LPSPI3_PCS0 ~ ~ D1 = PA2 // [AD_B0_02]: FLEXCAN2_TX XBAR1_INOUT16 LPUART6_TX USB_OTG1_PWR FLEXPWM1_PWMX00 GPIO1_IO02 LPI2C1_HREQ LPSPI3_SDI ~ ~ D2 = PD4 // [EMC_04]: SEMC_DATA04 FLEXPWM4_PWMA02 SAI2_TX_DATA XBAR1_INOUT06 FLEXIO1_FLEXIO04 GPIO4_IO04 ~ ~ ~ ~ D3 = PD5 // [EMC_05]: SEMC_DATA05 FLEXPWM4_PWMB02 SAI2_TX_SYNC XBAR1_INOUT07 FLEXIO1_FLEXIO05 GPIO4_IO05 ~ ~ ~ ~ D4 = PD6 // [EMC_06]: SEMC_DATA06 FLEXPWM2_PWMA00 SAI2_TX_BCLK XBAR1_INOUT08 FLEXIO1_FLEXIO06 GPIO4_IO06 ~ ~ ~ ~ D5 = PD8 // [EMC_08]: SEMC_DM00 FLEXPWM2_PWMA01 SAI2_RX_DATA XBAR1_INOUT17 FLEXIO1_FLEXIO08 GPIO4_IO08 ~ ~ ~ ~ D6 = PB10 // [B0_10]: LCD_DATA06 QTIMER4_TIMER1 FLEXPWM2_PWMA02 SAI1_TX_DATA03 FLEXIO2_FLEXIO10 GPIO2_IO10 SRC_BOOT_CFG06 ENET2_CRS ~ ~ D7 = PB17 // [B1_01]: LCD_DATA13 XBAR1_INOUT15 LPUART4_RX SAI1_TX_DATA00 FLEXIO2_FLEXIO17 GPIO2_IO17 FLEXPWM1_PWMB03 ENET2_RDATA00 FLEXIO3_FLEXIO17 ~ D8 = PB16 // [B1_00]: LCD_DATA12 XBAR1_INOUT14 LPUART4_TX SAI1_RX_DATA00 FLEXIO2_FLEXIO16 GPIO2_IO16 FLEXPWM1_PWMA03 ENET2_RX_ER FLEXIO3_FLEXIO16 ~ D9 = PB11 // [B0_11]: LCD_DATA07 QTIMER4_TIMER2 FLEXPWM2_PWMB02 SAI1_TX_DATA02 FLEXIO2_FLEXIO11 GPIO2_IO11 SRC_BOOT_CFG07 ENET2_COL ~ ~ D10 = PB0 // [B0_00]: LCD_CLK QTIMER1_TIMER0 MQS_RIGHT LPSPI4_PCS0 FLEXIO2_FLEXIO00 GPIO2_IO00 SEMC_CSX01 ENET2_MDC ~ ~ D11 = PB2 // [B0_02]: LCD_HSYNC QTIMER1_TIMER2 FLEXCAN1_TX LPSPI4_SDO FLEXIO2_FLEXIO02 GPIO2_IO02 SEMC_CSX03 ENET2_1588_EVENT0_OUT ~ ~ D12 = PB1 // [B0_01]: LCD_ENABLE QTIMER1_TIMER1 MQS_LEFT LPSPI4_SDI FLEXIO2_FLEXIO01 GPIO2_IO01 SEMC_CSX02 ENET2_MDIO ~ ~ D13 = PB3 // [B0_03]: LCD_VSYNC QTIMER2_TIMER0 FLEXCAN1_RX LPSPI4_SCK FLEXIO2_FLEXIO03 GPIO2_IO03 WDOG2_RESET_B_DEB ENET2_1588_EVENT0_IN ~ ~ D14 = PA18 // [AD_B1_02]: USB_OTG1_ID QTIMER3_TIMER2 LPUART2_TX SPDIF_OUT ENET_1588_EVENT2_OUT GPIO1_IO18 USDHC1_CD_B KPP_ROW06 GPT2_CLK FLEXIO3_FLEXIO02 D15 = PA19 // [AD_B1_03]: USB_OTG1_OC QTIMER3_TIMER3 LPUART2_RX SPDIF_IN ENET_1588_EVENT2_IN GPIO1_IO19 USDHC2_CD_B KPP_COL06 GPT2_CAPTURE1 FLEXIO3_FLEXIO03 D16 = PA23 // [AD_B1_07]: FLEXSPIB_DATA00 LPI2C3_SCL LPUART3_RX SPDIF_EXT_CLK CSI_HSYNC GPIO1_IO23 USDHC2_DATA3 KPP_COL04 GPT2_COMPARE3 FLEXIO3_FLEXIO07 D17 = PA22 // [AD_B1_06]: FLEXSPIB_DATA01 LPI2C3_SDA LPUART3_TX SPDIF_LOCK CSI_VSYNC GPIO1_IO22 USDHC2_DATA2 KPP_ROW04 GPT2_COMPARE2 FLEXIO3_FLEXIO06 D18 = PA17 // [AD_B1_01]: USB_OTG1_PWR QTIMER3_TIMER1 LPUART2_RTS_B LPI2C1_SDA CCM_PMIC_READY GPIO1_IO17 USDHC1_VSELECT KPP_COL07 ENET2_1588_EVENT0_IN FLEXIO3_FLEXIO01 D19 = PA16 // [AD_B1_00]: USB_OTG2_ID QTIMER3_TIMER0 LPUART2_CTS_B LPI2C1_SCL WDOG1_B GPIO1_IO16 USDHC1_WP KPP_ROW07 ENET2_1588_EVENT0_OUT FLEXIO3_FLEXIO00 D20 = PA26 // [AD_B1_10]: FLEXSPIA_DATA03 WDOG1_B LPUART8_TX SAI1_RX_SYNC CSI_DATA07 GPIO1_IO26 USDHC2_WP KPP_ROW02 ENET2_1588_EVENT1_OUT FLEXIO3_FLEXIO10 D21 = PA27 // [AD_B1_11]: FLEXSPIA_DATA02 EWM_OUT_B LPUART8_RX SAI1_RX_BCLK CSI_DATA06 GPIO1_IO27 USDHC2_RESET_B KPP_COL02 ENET2_1588_EVENT1_IN FLEXIO3_FLEXIO11 D22 = PA24 // [AD_B1_08]: FLEXSPIA_SS1_B FLEXPWM4_PWMA00 FLEXCAN1_TX CCM_PMIC_READY CSI_DATA09 GPIO1_IO24 USDHC2_CMD KPP_ROW03 FLEXIO3_FLEXIO08 ~ D23 = PA25 // [AD_B1_09]: FLEXSPIA_DQS FLEXPWM4_PWMA01 FLEXCAN1_RX SAI1_MCLK CSI_DATA08 GPIO1_IO25 USDHC2_CLK KPP_COL03 FLEXIO3_FLEXIO09 ~ D24 = PA12 // [AD_B0_12]: LPI2C4_SCL CCM_PMIC_READY LPUART1_TX WDOG2_WDOG_B FLEXPWM1_PWMX02 GPIO1_IO12 ENET_1588_EVENT1_OUT NMI_GLUE_NMI ~ ~ D25 = PA13 // [AD_B0_13]: LPI2C4_SDA GPT1_CLK LPUART1_RX EWM_OUT_B FLEXPWM1_PWMX03 GPIO1_IO13 ENET_1588_EVENT1_IN REF_CLK_24M ~ ~ D26 = PA30 // [AD_B1_14]: FLEXSPIA_SCLK ACMP_OUT02 LPSPI3_SDO SAI1_TX_BCLK CSI_DATA03 GPIO1_IO30 USDHC2_DATA6 KPP_ROW00 ENET2_1588_EVENT3_OUT FLEXIO3_FLEXIO14 D27 = PA31 // [AD_B1_15]: FLEXSPIA_SS0_B ACMP_OUT03 LPSPI3_SCK SAI1_TX_SYNC CSI_DATA02 GPIO1_IO31 USDHC2_DATA7 KPP_COL00 ENET2_1588_EVENT3_IN FLEXIO3_FLEXIO15 D28 = PC18 // [EMC_32]: SEMC_DATA10 FLEXPWM3_PWMB01 LPUART7_RX CCM_PMIC_RDY CSI_DATA21 GPIO3_IO18 ENET2_TX_EN ~ ~ ~ D29 = PD31 // [EMC_31]: SEMC_DATA09 FLEXPWM3_PWMA01 LPUART7_TX LPSPI1_PCS1 CSI_DATA22 GPIO4_IO31 ENET2_TDATA01 ~ ~ ~ D30 = PC23 // [EMC_37]: SEMC_DATA15 XBAR1_IN23 GPT1_COMPARE3 SAI3_MCLK CSI_DATA16 GPIO3_IO23 USDHC2_WP ENET2_RX_EN FLEXCAN3_RX ~ D31 = PC22 // [EMC_36]: SEMC_DATA14 XBAR1_IN22 GPT1_COMPARE2 SAI3_TX_DATA CSI_DATA17 GPIO3_IO22 USDHC1_WP ENET2_RDATA01 FLEXCAN3_TX ~ D32 = PB12 // [B0_12]: LCD_DATA08 XBAR1_INOUT10 ARM_TRACE_CLK SAI1_TX_DATA01 FLEXIO2_FLEXIO12 GPIO2_IO12 SRC_BOOT_CFG08 ENET2_TDATA00 ~ ~ D33 = PD7 // [EMC_07]: SEMC_DATA07 FLEXPWM2_PWMB00 SAI2_MCLK XBAR1_INOUT09 FLEXIO1_FLEXIO07 GPIO4_IO07 ~ ~ ~ ~ D34 = PB29 // [B1_13]: WDOG1_B LPUART5_RX CSI_VSYNC ENET_1588_EVENT0_OUT FLEXIO2_FLEXIO29 GPIO2_IO29 USDHC1_WP SEMC_DQS4 FLEXIO3_FLEXIO29 ~ D35 = PB28 // [B1_12]: LPUART5_TX CSI_PIXCLK ENET_1588_EVENT0_IN FLEXIO2_FLEXIO28 GPIO2_IO28 USDHC1_CD_B FLEXIO3_FLEXIO28 ~ ~ ~ D36 = PB18 // [B1_02]: LCD_DATA14 XBAR1_INOUT16 LPSPI4_PCS2 SAI1_TX_BCLK FLEXIO2_FLEXIO18 GPIO2_IO18 FLEXPWM2_PWMA03 ENET2_RDATA01 FLEXIO3_FLEXIO18 ~ D37 = PB19 // [B1_03]: LCD_DATA15 XBAR1_INOUT17 LPSPI4_PCS1 SAI1_TX_SYNC FLEXIO2_FLEXIO19 GPIO2_IO19 FLEXPWM2_PWMB03 ENET2_RX_EN FLEXIO3_FLEXIO19 ~ D38 = PA28 // [AD_B1_12]: FLEXSPIA_DATA01 ACMP_OUT00 LPSPI3_PCS0 SAI1_RX_DATA00 CSI_DATA05 GPIO1_IO28 USDHC2_DATA4 KPP_ROW01 ENET2_1588_EVENT2_OUT FLEXIO3_FLEXIO12 D39 = PA29 // [AD_B1_13]: FLEXSPIA_DATA00 ACMP_OUT01 LPSPI3_SDI SAI1_TX_DATA00 CSI_DATA04 GPIO1_IO29 USDHC2_DATA5 KPP_COL01 ENET2_1588_EVENT2_IN FLEXIO3_FLEXIO13 D40 = PA20 // [AD_B1_04]: FLEXSPIB_DATA03 ENET_MDC LPUART3_CTS_B SPDIF_SR_CLK CSI_PIXCLK GPIO1_IO20 USDHC2_DATA0 KPP_ROW05 GPT2_CAPTURE2 FLEXIO3_FLEXIO04 D41 = PA21 // [AD_B1_05]: FLEXSPIB_DATA02 ENET_MDIO LPUART3_RTS_B SPDIF_OUT CSI_MCLK GPIO1_IO21 USDHC2_DATA1 KPP_COL05 GPT2_COMPARE1 FLEXIO3_FLEXIO05 D42 = PC15 // [SD_B0_03]: USDHC1_DATA1 FLEXPWM1_PWMB01 LPUART8_RTS_B XBAR1_INOUT07 LPSPI1_SDI GPIO3_IO15 ENET2_RDATA00 SEMC_CLK6 ~ ~ D43 = PC14 // [SD_B0_02]: USDHC1_DATA0 FLEXPWM1_PWMA01 LPUART8_CTS_B XBAR1_INOUT06 LPSPI1_SDO GPIO3_IO14 ENET2_RX_ER SEMC_CLK5 ~ ~ D44 = PC13 // [SD_B0_01]: USDHC1_CLK FLEXPWM1_PWMB00 LPI2C3_SDA XBAR1_INOUT05 LPSPI1_PCS0 GPIO3_IO13 FLEXSPIB_SS1_B ENET2_TX_CLK ENET2_REF_CLK2 ~ D45 = PC12 // [SD_B0_00]: USDHC1_CMD FLEXPWM1_PWMA00 LPI2C3_SCL XBAR1_INOUT04 LPSPI1_SCK GPIO3_IO12 FLEXSPIA_SS1_B ENET2_TX_EN SEMC_DQS4 ~ D46 = PC17 // [SD_B0_05]: USDHC1_DATA3 FLEXPWM1_PWMB02 LPUART8_RX XBAR1_INOUT09 FLEXSPIB_DQS GPIO3_IO17 CCM_CLKO2 ENET2_RX_EN ~ ~ D47 = PC16 // [SD_B0_04]: USDHC1_DATA2 FLEXPWM1_PWMA02 LPUART8_TX XBAR1_INOUT08 FLEXSPIB_SS0_B GPIO3_IO16 CCM_CLKO1 ENET2_RDATA01 ~ ~ D48 = PD24 // [EMC_24]: SEMC_CAS FLEXPWM1_PWMB00 LPUART5_RX ENET_TX_EN GPT1_CAPTURE1 GPIO4_IO24 FLEXSPI2_A_SS0_B ~ ~ ~ D49 = PD27 // [EMC_27]: SEMC_CKE FLEXPWM1_PWMA02 LPUART5_RTS_B LPSPI1_SCK FLEXIO1_FLEXIO13 GPIO4_IO27 FLEXSPI2_A_DATA01 ~ ~ ~ D50 = PD28 // [EMC_28]: SEMC_WE FLEXPWM1_PWMB02 LPUART5_CTS_B LPSPI1_SDO FLEXIO1_FLEXIO14 GPIO4_IO28 FLEXSPI2_A_DATA02 ~ ~ ~ D51 = PD22 // [EMC_22]: SEMC_BA1 FLEXPWM3_PWMB03 LPI2C3_SCL ENET_TDATA00 QTIMER2_TIMER3 GPIO4_IO22 FLEXSPI2_A_SS1_B ~ ~ ~ D52 = PD26 // [EMC_26]: SEMC_CLK FLEXPWM1_PWMB01 LPUART6_RX ENET_RX_ER FLEXIO1_FLEXIO12 GPIO4_IO26 FLEXSPI2_A_DATA00 ~ ~ ~ D53 = PD25 // [EMC_25]: SEMC_RAS FLEXPWM1_PWMA01 LPUART6_TX ENET_TX_CLK ENET_REF_CLK GPIO4_IO25 FLEXSPI2_A_SCLK ~ ~ ~ D54 = PD29 // [EMC_29]: SEMC_CS0 FLEXPWM3_PWMA00 LPUART6_RTS_B LPSPI1_SDI FLEXIO1_FLEXIO15 GPIO4_IO29 FLEXSPI2_A_DATA03 ~ ~ ~ ) // Analog pins const ( // = Pin // Dig | [Pad] {ADC1/ADC2} A0 = PA18 // D14 | [AD_B1_02] { 7 / 7 } A1 = PA19 // D15 | [AD_B1_03] { 8 / 8 } A2 = PA23 // D16 | [AD_B1_07] { 12 / 12 } A3 = PA22 // D17 | [AD_B1_06] { 11 / 11 } A4 = PA17 // D18 | [AD_B1_01] { 6 / 6 } A5 = PA16 // D19 | [AD_B1_00] { 5 / 5 } A6 = PA26 // D20 | [AD_B1_10] { 15 / 15 } A7 = PA27 // D21 | [AD_B1_11] { 0 / 0 } A8 = PA24 // D22 | [AD_B1_08] { 13 / 13 } A9 = PA25 // D23 | [AD_B1_09] { 14 / 14 } A10 = PA12 // D24 | [AD_B0_12] { 1 / - } A11 = PA13 // D25 | [AD_B0_13] { 2 / - } A12 = PA30 // D26 | [AD_B1_14] { - / 3 } A13 = PA31 // D27 | [AD_B1_15] { - / 4 } A14 = PA28 // D38 | [AD_B1_12] { ? / ? } // FIXME A15 = PA29 // D39 | [AD_B1_13] { ? / ? } // FIXME A16 = PA20 // D40 | [AD_B1_04] { ? / ? } // FIXME A17 = PA21 // D41 | [AD_B1_05] { ? / ? } // FIXME ) // Default peripheral pins const ( LED = D13 UART_RX_PIN = UART1_RX_PIN // D0 UART_TX_PIN = UART1_TX_PIN // D1 SPI_SDI_PIN = SPI1_SDI_PIN // D12 SPI_SDO_PIN = SPI1_SDO_PIN // D11 SPI_SCK_PIN = SPI1_SCK_PIN // D13 SPI_CS_PIN = SPI1_CS_PIN // D10 I2C_SDA_PIN = I2C1_SDA_PIN // D18/A4 I2C_SCL_PIN = I2C1_SCL_PIN // D19/A5 ) // Default peripherals var ( DefaultUART = UART1 ) func init() { // register any interrupt handlers for this board's peripherals _UART1.Interrupt = interrupt.New(nxp.IRQ_LPUART6, _UART1.handleInterrupt) _UART2.Interrupt = interrupt.New(nxp.IRQ_LPUART4, _UART2.handleInterrupt) _UART3.Interrupt = interrupt.New(nxp.IRQ_LPUART2, _UART3.handleInterrupt) _UART4.Interrupt = interrupt.New(nxp.IRQ_LPUART3, _UART4.handleInterrupt) _UART5.Interrupt = interrupt.New(nxp.IRQ_LPUART8, _UART5.handleInterrupt) _UART6.Interrupt = interrupt.New(nxp.IRQ_LPUART1, _UART6.handleInterrupt) _UART7.Interrupt = interrupt.New(nxp.IRQ_LPUART7, _UART7.handleInterrupt) _UART8.Interrupt = interrupt.New(nxp.IRQ_LPUART5, _UART8.handleInterrupt) } // #=====================================================# // | UART | // #===========#===========#=============#===============# // | Interface | Hardware | Clock(Freq) | RX/TX : Alt | // #===========#===========#=============#=========-=====# // | UART1 | LPUART6 | OSC(24 MHz) | D0/D1 : 2/2 | // | UART2 | LPUART4 | OSC(24 MHz) | D7/D8 : 2/2 | // | UART3 | LPUART2 | OSC(24 MHz) | D15/D14 : 2/2 | // | UART4 | LPUART3 | OSC(24 MHz) | D16/D17 : 2/2 | // | UART5 | LPUART8 | OSC(24 MHz) | D21/D20 : 2/2 | // | UART6 | LPUART1 | OSC(24 MHz) | D25/D24 : 2/2 | // | UART7 | LPUART7 | OSC(24 MHz) | D28/D29 : 2/2 | // | UART8 | LPUART5 | OSC(24 MHz) | D34/D35 : 1/1 | // #===========#===========#=============#=========-=====# const ( UART1_RX_PIN = D0 UART1_TX_PIN = D1 UART2_RX_PIN = D7 UART2_TX_PIN = D8 UART3_RX_PIN = D15 UART3_TX_PIN = D14 UART4_RX_PIN = D16 UART4_TX_PIN = D17 UART5_RX_PIN = D21 UART5_TX_PIN = D20 UART6_RX_PIN = D25 UART6_TX_PIN = D24 UART7_RX_PIN = D28 UART7_TX_PIN = D29 UART8_RX_PIN = D34 UART8_TX_PIN = D35 ) var ( UART1 = &_UART1 _UART1 = UART{ Bus: nxp.LPUART6, Buffer: NewRingBuffer(), txBuffer: NewRingBuffer(), muxRX: muxSelect{ // D0 (PA3 [AD_B0_03]) mux: nxp.IOMUXC_LPUART6_RX_SELECT_INPUT_DAISY_GPIO_AD_B0_03_ALT2, sel: &nxp.IOMUXC.LPUART6_RX_SELECT_INPUT, }, muxTX: muxSelect{ // D1 (PA2 [AD_B0_02]) mux: nxp.IOMUXC_LPUART6_TX_SELECT_INPUT_DAISY_GPIO_AD_B0_02_ALT2, sel: &nxp.IOMUXC.LPUART6_TX_SELECT_INPUT, }, } UART2 = &_UART2 _UART2 = UART{ Bus: nxp.LPUART4, Buffer: NewRingBuffer(), txBuffer: NewRingBuffer(), muxRX: muxSelect{ // D7 (PB17 [B1_01]) mux: nxp.IOMUXC_LPUART4_RX_SELECT_INPUT_DAISY_GPIO_B1_01_ALT2, sel: &nxp.IOMUXC.LPUART4_RX_SELECT_INPUT, }, muxTX: muxSelect{ // D8 (PB16 [B1_00]) mux: nxp.IOMUXC_LPUART4_TX_SELECT_INPUT_DAISY_GPIO_B1_00_ALT2, sel: &nxp.IOMUXC.LPUART4_TX_SELECT_INPUT, }, } UART3 = &_UART3 _UART3 = UART{ Bus: nxp.LPUART2, Buffer: NewRingBuffer(), txBuffer: NewRingBuffer(), muxRX: muxSelect{ // D15 (PA19 [AD_B1_03]) mux: nxp.IOMUXC_LPUART2_RX_SELECT_INPUT_DAISY_GPIO_AD_B1_03_ALT2, sel: &nxp.IOMUXC.LPUART2_RX_SELECT_INPUT, }, muxTX: muxSelect{ // D14 (PA18 [AD_B1_02]) mux: nxp.IOMUXC_LPUART2_TX_SELECT_INPUT_DAISY_GPIO_AD_B1_02_ALT2, sel: &nxp.IOMUXC.LPUART2_TX_SELECT_INPUT, }, } UART4 = &_UART4 _UART4 = UART{ Bus: nxp.LPUART3, Buffer: NewRingBuffer(), txBuffer: NewRingBuffer(), muxRX: muxSelect{ // D16 (PA23 [AD_B1_07]) mux: nxp.IOMUXC_LPUART3_RX_SELECT_INPUT_DAISY_GPIO_AD_B1_07_ALT2, sel: &nxp.IOMUXC.LPUART3_RX_SELECT_INPUT, }, muxTX: muxSelect{ // D17 (PA22 [AD_B1_06]) mux: nxp.IOMUXC_LPUART3_TX_SELECT_INPUT_DAISY_GPIO_AD_B1_06_ALT2, sel: &nxp.IOMUXC.LPUART3_TX_SELECT_INPUT, }, } UART5 = &_UART5 _UART5 = UART{ Bus: nxp.LPUART8, Buffer: NewRingBuffer(), txBuffer: NewRingBuffer(), muxRX: muxSelect{ // D21 (PA27 [AD_B1_11]) mux: nxp.IOMUXC_LPUART8_RX_SELECT_INPUT_DAISY_GPIO_AD_B1_11_ALT2, sel: &nxp.IOMUXC.LPUART8_RX_SELECT_INPUT, }, muxTX: muxSelect{ // D20 (PA26 [AD_B1_10]) mux: nxp.IOMUXC_LPUART8_TX_SELECT_INPUT_DAISY_GPIO_AD_B1_10_ALT2, sel: &nxp.IOMUXC.LPUART8_TX_SELECT_INPUT, }, } UART6 = &_UART6 _UART6 = UART{ Bus: nxp.LPUART1, Buffer: NewRingBuffer(), txBuffer: NewRingBuffer(), // LPUART1 not connected via IOMUXC // RX: D24 (PA12 [AD_B0_12]) // TX: D25 (PA13 [AD_B0_13]) } UART7 = &_UART7 _UART7 = UART{ Bus: nxp.LPUART7, Buffer: NewRingBuffer(), txBuffer: NewRingBuffer(), muxRX: muxSelect{ // D28 (PC18 [EMC_32]) mux: nxp.IOMUXC_LPUART7_RX_SELECT_INPUT_DAISY_GPIO_EMC_32_ALT2, sel: &nxp.IOMUXC.LPUART7_RX_SELECT_INPUT, }, muxTX: muxSelect{ // D29 (PD31 [EMC_31]) mux: nxp.IOMUXC_LPUART7_TX_SELECT_INPUT_DAISY_GPIO_EMC_31_ALT2, sel: &nxp.IOMUXC.LPUART7_TX_SELECT_INPUT, }, } UART8 = &_UART8 _UART8 = UART{ Bus: nxp.LPUART5, Buffer: NewRingBuffer(), txBuffer: NewRingBuffer(), muxRX: muxSelect{ // D34 (PB29 [B1_13]) mux: nxp.IOMUXC_LPUART5_RX_SELECT_INPUT_DAISY_GPIO_B1_13_ALT1, sel: &nxp.IOMUXC.LPUART5_RX_SELECT_INPUT, }, muxTX: muxSelect{ // D35 (PB28 [B1_12]) mux: nxp.IOMUXC_LPUART5_TX_SELECT_INPUT_DAISY_GPIO_B1_12_ALT1, sel: &nxp.IOMUXC.LPUART5_TX_SELECT_INPUT, }, } ) // #===========#==========#===============#===========================# // | Interface | Hardware | Clock(Freq) | SDI/SDO/SCK/CS : Alt | // #===========#==========#===============#=================-=========# // | SPI1 | LPSPI4 | PLL2(132 MHz) | D12/D11/D13/D10 : 3/3/3/3 | // | SPI2 | LPSPI3 | PLL2(132 MHz) | D1/D26/D27/D0 : 7/2/2/7 | // | SPI3 | LPSPI1 | PLL2(132 MHz) | D42/D43/D45/D44 : 4/4/4/4 | // #===========#==========#===============#=================-=========# const ( SPI1_SDI_PIN = D12 SPI1_SDO_PIN = D11 SPI1_SCK_PIN = D13 SPI1_CS_PIN = D10 SPI2_SDI_PIN = D1 SPI2_SDO_PIN = D26 SPI2_SCK_PIN = D27 SPI2_CS_PIN = D0 SPI3_SDI_PIN = D42 SPI3_SDO_PIN = D43 SPI3_SCK_PIN = D45 SPI3_CS_PIN = D44 ) var ( SPI0 = SPI1 // SPI0 is an alias of SPI1 (LPSPI4) SPI1 = &SPI{ Bus: nxp.LPSPI4, muxSDI: muxSelect{ // D12 (PB1 [B0_01]) mux: nxp.IOMUXC_LPSPI4_SDI_SELECT_INPUT_DAISY_GPIO_B0_01_ALT3, sel: &nxp.IOMUXC.LPSPI4_SDI_SELECT_INPUT, }, muxSDO: muxSelect{ // D11 (PB2 [B0_02]) mux: nxp.IOMUXC_LPSPI4_SDO_SELECT_INPUT_DAISY_GPIO_B0_02_ALT3, sel: &nxp.IOMUXC.LPSPI4_SDO_SELECT_INPUT, }, muxSCK: muxSelect{ // D13 (PB3 [B0_03]) mux: nxp.IOMUXC_LPSPI4_SCK_SELECT_INPUT_DAISY_GPIO_B0_03_ALT3, sel: &nxp.IOMUXC.LPSPI4_SCK_SELECT_INPUT, }, muxCS: muxSelect{ // D10 (PB0 [B0_00]) mux: nxp.IOMUXC_LPSPI4_PCS0_SELECT_INPUT_DAISY_GPIO_B0_00_ALT3, sel: &nxp.IOMUXC.LPSPI4_PCS0_SELECT_INPUT, }, } SPI2 = &SPI{ Bus: nxp.LPSPI3, muxSDI: muxSelect{ // D1 (PA2 [AD_B0_02]) mux: nxp.IOMUXC_LPSPI3_SDI_SELECT_INPUT_DAISY_GPIO_AD_B0_02_ALT7, sel: &nxp.IOMUXC.LPSPI3_SDI_SELECT_INPUT, }, muxSDO: muxSelect{ // D26 (PA30 [AD_B1_14]) mux: nxp.IOMUXC_LPSPI3_SDO_SELECT_INPUT_DAISY_GPIO_AD_B1_14_ALT2, sel: &nxp.IOMUXC.LPSPI3_SDO_SELECT_INPUT, }, muxSCK: muxSelect{ // D27 (PA31 [AD_B1_15]) mux: nxp.IOMUXC_LPSPI3_SCK_SELECT_INPUT_DAISY_GPIO_AD_B1_15, sel: &nxp.IOMUXC.LPSPI3_SCK_SELECT_INPUT, }, muxCS: muxSelect{ // D0 (PA3 [AD_B0_03]) mux: nxp.IOMUXC_LPSPI3_PCS0_SELECT_INPUT_DAISY_GPIO_AD_B0_03_ALT7, sel: &nxp.IOMUXC.LPSPI3_PCS0_SELECT_INPUT, }, } ) // #====================================================# // | I2C | // #===========#==========#=============#===============# // | Interface | Hardware | Clock(Freq) | SDA/SCL : Alt | // #===========#==========#=============#=========-=====# // | I2C1 | LPI2C1 | OSC(24 MHz) | D18/D19 : 3/3 | // | I2C2 | LPI2C3 | OSC(24 MHz) | D17/D16 : 1/1 | // | I2C3 | LPI2C4 | OSC(24 MHz) | D25/D24 : 0/0 | // #===========#==========#=============#=========-=====# const ( I2C1_SDA_PIN = D18 I2C1_SCL_PIN = D19 I2C2_SDA_PIN = D17 I2C2_SCL_PIN = D16 I2C3_SDA_PIN = D25 I2C3_SCL_PIN = D24 ) var ( I2C0 = I2C1 // I2C0 is an alias for I2C1 (LPI2C1) I2C1 = &_I2C1 _I2C1 = I2C{ Bus: nxp.LPI2C1, sda: I2C1_SDA_PIN, // D18 (PA17 [AD_B1_01]) scl: I2C1_SCL_PIN, // D19 (PA16 [AD_B1_00]) muxSDA: muxSelect{ mux: nxp.IOMUXC_LPI2C1_SDA_SELECT_INPUT_DAISY_GPIO_AD_B1_01_ALT3, sel: &nxp.IOMUXC.LPI2C1_SDA_SELECT_INPUT, }, muxSCL: muxSelect{ mux: nxp.IOMUXC_LPI2C1_SCL_SELECT_INPUT_DAISY_GPIO_AD_B1_00_ALT3, sel: &nxp.IOMUXC.LPI2C1_SCL_SELECT_INPUT, }, } I2C2 = &_I2C2 _I2C2 = I2C{ Bus: nxp.LPI2C3, sda: I2C2_SDA_PIN, // D17 (PA22 [AD_B1_06]) scl: I2C2_SCL_PIN, // D16 (PA23 [AD_B1_07]) muxSDA: muxSelect{ mux: nxp.IOMUXC_LPI2C3_SDA_SELECT_INPUT_DAISY_GPIO_AD_B1_06_ALT1, sel: &nxp.IOMUXC.LPI2C3_SDA_SELECT_INPUT, }, muxSCL: muxSelect{ mux: nxp.IOMUXC_LPI2C3_SCL_SELECT_INPUT_DAISY_GPIO_AD_B1_07_ALT1, sel: &nxp.IOMUXC.LPI2C3_SCL_SELECT_INPUT, }, } I2C3 = &_I2C3 _I2C3 = I2C{ Bus: nxp.LPI2C4, sda: I2C3_SDA_PIN, // D25 (PA13 [AD_B0_13]) scl: I2C3_SCL_PIN, // D24 (PA12 [AD_B0_12]) muxSDA: muxSelect{ mux: nxp.IOMUXC_LPI2C4_SDA_SELECT_INPUT_DAISY_GPIO_AD_B0_13_ALT0, sel: &nxp.IOMUXC.LPI2C4_SDA_SELECT_INPUT, }, muxSCL: muxSelect{ mux: nxp.IOMUXC_LPI2C4_SCL_SELECT_INPUT_DAISY_GPIO_AD_B0_12_ALT0, sel: &nxp.IOMUXC.LPI2C4_SCL_SELECT_INPUT, }, } ) ================================================ FILE: src/machine/board_thingplus_rp2040.go ================================================ //go:build thingplus_rp2040 package machine // Onboard crystal oscillator frequency, in MHz. const xoscFreq = 12 // MHz // GPIO Pins const ( GP0 Pin = GPIO0 // TX GP1 Pin = GPIO1 // RX GP2 Pin = GPIO2 // SCK GP3 Pin = GPIO3 // COPI GP4 Pin = GPIO4 // CIPO GP6 Pin = GPIO6 // SDA GP7 Pin = GPIO7 // SCL (connected to GPIO23 as well) GP8 Pin = GPIO8 // WS2812 RGB LED GP9 Pin = GPIO9 // muSDcard DATA3 / CS GP10 Pin = GPIO10 // muSDcard DATA2 GP11 Pin = GPIO11 // muSDcard DATA1 GP12 Pin = GPIO12 // muSDcard DATA0 / CIPO GP14 Pin = GPIO14 // muSDcard CLK /SCLK GP15 Pin = GPIO15 // muSDcard CMD / COPI GP16 Pin = GPIO16 // 16 GP17 Pin = GPIO17 // 17 GP18 Pin = GPIO18 // 18 GP19 Pin = GPIO19 // 19 GP20 Pin = GPIO20 // 20 GP21 Pin = GPIO21 // 21 GP22 Pin = GPIO22 // 22 GP23 Pin = GPIO23 // Connected to GPIO7 GP25 Pin = GPIO25 // Status blue LED GP26 Pin = GPIO26 // ADC0 GP27 Pin = GPIO27 // ADC1 GP28 Pin = GPIO28 // ADC2 GP29 Pin = GPIO29 // ADC3 ) // Analog pins const ( A0 = GPIO26 A1 = GPIO27 A2 = GPIO28 A3 = GPIO29 ) // Onboard LEDs const ( LED = GPIO25 WS2812 = GPIO8 ) // I2C Pins. const ( I2C0_SCL_PIN = GPIO6 // N/A I2C0_SDA_PIN = GPIO7 // N/A I2C1_SDA_PIN = GPIO6 I2C1_SCL_PIN = GPIO7 SDA_PIN = I2C1_SDA_PIN SCL_PIN = I2C1_SCL_PIN ) // SPI default pins const ( // Default Serial Clock Bus 0 for SPI communications SPI0_SCK_PIN = GPIO2 // Default Serial Out Bus 0 for SPI communications SPI0_SDO_PIN = GPIO3 // Tx // Default Serial In Bus 0 for SPI communications SPI0_SDI_PIN = GPIO4 // Rx // Default Serial Clock Bus 1 for SPI communications to muSDcard SPI1_SCK_PIN = GPIO14 // Default Serial Out Bus 1 for SPI communications to muSDcard SPI1_SDO_PIN = GPIO15 // Tx // Default Serial In Bus 1 for SPI communications to muSDcard SPI1_SDI_PIN = GPIO12 // Rx ) // UART pins const ( UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) var DefaultUART = UART0 // USB identifiers const ( usb_STRING_PRODUCT = "Thing Plus RP2040" usb_STRING_MANUFACTURER = "SparkFun" ) var ( usb_VID uint16 = 0x1B4F usb_PID uint16 = 0x0026 ) ================================================ FILE: src/machine/board_thumby.go ================================================ //go:build thumby // This contains the pin mappings for the Thumby. // // https://thumby.us/ package machine const ( THUMBY_SCK_PIN = I2C1_SDA_PIN THUMBY_SDA_PIN = I2C1_SCL_PIN THUMBY_CS_PIN = GPIO16 THUMBY_DC_PIN = GPIO17 THUMBY_RESET_PIN = GPIO20 THUMBY_LINK_TX_PIN = UART0_TX_PIN THUMBY_LINK_RX_PIN = UART0_RX_PIN THUMBY_LINK_PU_PIN = GPIO2 THUMBY_BTN_LDPAD_PIN = GPIO3 THUMBY_BTN_RDPAD_PIN = GPIO5 THUMBY_BTN_UDPAD_PIN = GPIO4 THUMBY_BTN_DDPAD_PIN = GPIO6 THUMBY_BTN_B_PIN = GPIO24 THUMBY_BTN_A_PIN = GPIO27 THUMBY_AUDIO_PIN = GPIO28 THUMBY_SCREEN_RESET_PIN = GPIO20 ) // I2C pins const ( I2C0_SDA_PIN Pin = NoPin I2C0_SCL_PIN Pin = NoPin I2C1_SDA_PIN Pin = GPIO18 I2C1_SCL_PIN Pin = GPIO19 ) // SPI pins const ( SPI0_SCK_PIN = GPIO18 SPI0_SDO_PIN = GPIO19 SPI0_SDI_PIN = GPIO16 SPI1_SCK_PIN = NoPin SPI1_SDO_PIN = NoPin SPI1_SDI_PIN = NoPin ) // Onboard crystal oscillator frequency, in MHz. const ( xoscFreq = 12 // MHz ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Thumby" usb_STRING_MANUFACTURER = "TinyCircuits" ) var ( usb_VID uint16 = 0x2E8A usb_PID uint16 = 0x0005 ) // UART pins const ( UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) var DefaultUART = UART0 ================================================ FILE: src/machine/board_tiny2350.go ================================================ //go:build tiny2350 package machine // GPIO pins const ( GP0 Pin = GPIO0 GP1 Pin = GPIO1 GP2 Pin = GPIO2 GP3 Pin = GPIO3 GP4 Pin = GPIO4 GP5 Pin = GPIO5 GP6 Pin = GPIO6 GP7 Pin = GPIO7 GP12 Pin = GPIO12 GP13 Pin = GPIO13 GP18 Pin = GPIO18 GP19 Pin = GPIO19 GP20 Pin = GPIO20 GP26 Pin = GPIO26 GP27 Pin = GPIO27 GP28 Pin = GPIO28 GP29 Pin = GPIO29 // Onboard LED LED_RED Pin = GPIO18 LED_GREEN Pin = GPIO19 LED_BLUE Pin = GPIO20 LED = LED_RED // Onboard crystal oscillator frequency, in MHz. xoscFreq = 12 // MHz ) // I2C Default pins on Tiny2350. const ( I2C0_SDA_PIN = GP12 I2C0_SCL_PIN = GP13 I2C1_SDA_PIN = GP2 I2C1_SCL_PIN = GP3 ) // SPI default pins const ( // Default Serial Clock Bus 0 for SPI communications SPI0_SCK_PIN = GPIO6 // Default Serial Out Bus 0 for SPI communications SPI0_SDO_PIN = GPIO7 // Tx // Default Serial In Bus 0 for SPI communications SPI0_SDI_PIN = GPIO4 // Rx // Default Serial Clock Bus 1 for SPI communications SPI1_SCK_PIN = GPIO26 // Default Serial Out Bus 1 for SPI communications SPI1_SDO_PIN = GPIO27 // Tx // Default Serial In Bus 1 for SPI communications SPI1_SDI_PIN = GPIO28 // Rx ) // UART pins const ( UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART1_TX_PIN = GPIO4 UART1_RX_PIN = GPIO5 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) var DefaultUART = UART0 // USB identifiers const ( usb_STRING_PRODUCT = "Tiny2350" usb_STRING_MANUFACTURER = "Pimoroni" ) var ( usb_VID uint16 = 0x2E8A usb_PID uint16 = 0x000F ) ================================================ FILE: src/machine/board_trinket.go ================================================ //go:build sam && atsamd21 && trinket_m0 package machine // used to reset into bootloader const resetMagicValue = 0xf01669ef // GPIO Pins const ( D0 = PA08 // PWM available D1 = PA02 D2 = PA09 // PWM available D3 = PA07 // PWM available / UART0 RX D4 = PA06 // PWM available / UART0 TX D13 = PA10 // LED ) // Analog pins const ( A0 = D1 A1 = D2 A2 = D0 A3 = D3 A4 = D4 ) const ( LED = D13 ) // USBCDC pins const ( USBCDC_DM_PIN = PA24 USBCDC_DP_PIN = PA25 ) // UART1 pins const ( UART_TX_PIN = D4 UART_RX_PIN = D3 ) // UART1 on the Trinket M0. var UART1 = &sercomUSART0 // SPI pins const ( SPI0_SCK_PIN = D3 SPI0_SDO_PIN = D4 SPI0_SDI_PIN = D2 ) // SPI on the Trinket M0. var SPI0 = sercomSPIM0 // I2C pins const ( SDA_PIN = D0 // SDA SCL_PIN = D2 // SCL ) // I2C on the Trinket M0. var ( I2C0 = sercomI2CM2 ) // I2S pins const ( I2S_SCK_PIN = PA10 I2S_SDO_PIN = PA08 I2S_SDI_PIN = NoPin // TODO: figure out what this is on Trinket M0. I2S_WS_PIN = NoPin // TODO: figure out what this is on Trinket M0. ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Adafruit Trinket M0" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x801E ) var ( DefaultUART = UART1 ) ================================================ FILE: src/machine/board_trinkey_qt2040.go ================================================ //go:build trinkey_qt2040 // This file contains the pin mappings for the Adafruit Trinkey QT2040 board. // // The Trinkey QT2040 is a small development board based on the RP2040 which // plugs into a USB A port. The board has a minimal pinout: an integrated // NeoPixel LED and a STEMMA QT I2C port. // // - Product: https://www.adafruit.com/product/5056 // - Overview: https://learn.adafruit.com/adafruit-trinkey-qt2040 // - Pinouts: https://learn.adafruit.com/adafruit-trinkey-qt2040/pinouts // - Datasheets: https://learn.adafruit.com/adafruit-trinkey-qt2040/downloads package machine // Onboard crystal oscillator frequency, in MHz const xoscFreq = 12 // MHz // Onboard LEDs const ( NEOPIXEL = GPIO27 WS2812 = NEOPIXEL ) // I2C pins const ( I2C0_SDA_PIN = GPIO16 I2C0_SCL_PIN = GPIO17 I2C1_SDA_PIN = NoPin I2C1_SCL_PIN = NoPin ) // SPI pins const ( SPI0_SCK_PIN = NoPin SPI0_SDO_PIN = NoPin SPI0_SDI_PIN = NoPin SPI1_SCK_PIN = NoPin SPI1_SDO_PIN = NoPin SPI1_SDI_PIN = NoPin ) // UART pins const ( UART0_TX_PIN = NoPin UART0_RX_PIN = NoPin UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) // USB identifiers const ( usb_STRING_PRODUCT = "Trinkey QT2040" usb_STRING_MANUFACTURER = "Adafruit" ) var ( usb_VID uint16 = 0x239a usb_PID uint16 = 0x8109 ) ================================================ FILE: src/machine/board_tufty2040.go ================================================ //go:build tufty2040 // This contains the pin mappings for the Badger 2040 Connect board. // // For more information, see: https://shop.pimoroni.com/products/tufty-2040 // Also // - Tufty 2040 schematic: https://cdn.shopify.com/s/files/1/0174/1800/files/tufty_schematic.pdf?v=1655385675 package machine const ( LED Pin = GPIO25 BUTTON_A Pin = GPIO7 BUTTON_B Pin = GPIO8 BUTTON_C Pin = GPIO9 BUTTON_UP Pin = GPIO22 BUTTON_DOWN Pin = GPIO6 BUTTON_USER Pin = GPIO23 LCD_BACKLIGHT Pin = GPIO2 LCD_CS Pin = GPIO10 LCD_DC Pin = GPIO11 LCD_WR Pin = GPIO12 LCD_RD Pin = GPIO13 LCD_DB0 Pin = GPIO14 LCD_DB1 Pin = GPIO15 LCD_DB2 Pin = GPIO16 LCD_DB3 Pin = GPIO17 LCD_DB4 Pin = GPIO18 LCD_DB5 Pin = GPIO19 LCD_DB6 Pin = GPIO20 LCD_DB7 Pin = GPIO21 VBUS_DETECT Pin = GPIO24 BATTERY Pin = GPIO29 USER_LED Pin = GPIO25 LIGHT_SENSE Pin = GPIO26 SENSOR_POWER Pin = GPIO27 ) // I2C pins const ( I2C0_SDA_PIN Pin = GPIO4 I2C0_SCL_PIN Pin = GPIO5 I2C1_SDA_PIN Pin = NoPin I2C1_SCL_PIN Pin = NoPin ) // SPI pins. const ( SPI0_SCK_PIN Pin = NoPin SPI0_SDO_PIN Pin = NoPin SPI0_SDI_PIN Pin = NoPin SPI1_SCK_PIN Pin = NoPin SPI1_SDO_PIN Pin = NoPin SPI1_SDI_PIN Pin = NoPin ) // Onboard crystal oscillator frequency, in MHz. const ( xoscFreq = 12 // MHz ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Tufty 2040" usb_STRING_MANUFACTURER = "Pimoroni" ) var ( usb_VID uint16 = 0x2e8a usb_PID uint16 = 0x1002 ) // UART pins const ( UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) var DefaultUART = UART0 ================================================ FILE: src/machine/board_waveshare-rp2040-zero.go ================================================ //go:build waveshare_rp2040_zero // This file contains the pin mappings for the Waveshare RP2040-Zero boards. // // Waveshare RP2040-Zero is a microcontroller using the Raspberry Pi RP2040 chip. // // - https://www.waveshare.com/wiki/RP2040-Zero package machine // Digital Pins const ( D0 Pin = GPIO0 D1 Pin = GPIO1 D2 Pin = GPIO2 D3 Pin = GPIO3 D4 Pin = GPIO4 D5 Pin = GPIO5 D6 Pin = GPIO6 D7 Pin = GPIO7 D8 Pin = GPIO8 D9 Pin = GPIO9 D10 Pin = GPIO10 D11 Pin = GPIO11 D12 Pin = GPIO12 D13 Pin = GPIO13 D14 Pin = GPIO14 D15 Pin = GPIO15 D16 Pin = GPIO16 D17 Pin = GPIO17 D18 Pin = GPIO18 D19 Pin = GPIO19 D20 Pin = GPIO20 D21 Pin = GPIO21 D22 Pin = GPIO22 D23 Pin = GPIO23 D24 Pin = GPIO24 D25 Pin = GPIO25 D26 Pin = GPIO26 D27 Pin = GPIO27 D28 Pin = GPIO28 D29 Pin = GPIO29 ) // Analog pins const ( A0 Pin = D26 A1 Pin = D27 A2 Pin = D28 A3 Pin = D29 ) // Onboard LEDs const ( NEOPIXEL = GPIO16 WS2812 = GPIO16 ) // I2C pins const ( I2C0_SDA_PIN Pin = D0 I2C0_SCL_PIN Pin = D1 I2C1_SDA_PIN Pin = D2 I2C1_SCL_PIN Pin = D3 ) // SPI pins const ( SPI0_SCK_PIN Pin = D6 SPI0_SDO_PIN Pin = D3 SPI0_SDI_PIN Pin = D4 SPI1_SCK_PIN Pin = D10 SPI1_SDO_PIN Pin = D11 SPI1_SDI_PIN Pin = D12 ) // Onboard crystal oscillator frequency, in MHz. const ( xoscFreq = 12 // MHz ) // UART pins const ( UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN UART1_TX_PIN = GPIO8 UART1_RX_PIN = GPIO9 ) var DefaultUART = UART0 // USB CDC identifiers const ( usb_STRING_PRODUCT = "RP2040-Zero" usb_STRING_MANUFACTURER = "Waveshare" ) var ( usb_VID uint16 = 0x2e8a usb_PID uint16 = 0x0003 ) ================================================ FILE: src/machine/board_waveshare_rp2040_tiny.go ================================================ //go:build waveshare_rp2040_tiny // This file contains the pin mappings for the Waveshare RP2040-Tiny boards. // // Waveshare RP2040-Tiny is a microcontroller using the Raspberry Pi RP2040 chip. // // - https://www.waveshare.com/wiki/RP2040-Tiny package machine // Digital Pins const ( GP0 Pin = GPIO0 GP1 Pin = GPIO1 GP2 Pin = GPIO2 GP3 Pin = GPIO3 GP4 Pin = GPIO4 GP5 Pin = GPIO5 GP6 Pin = GPIO6 GP7 Pin = GPIO7 GP8 Pin = GPIO8 GP9 Pin = GPIO9 GP10 Pin = GPIO10 GP11 Pin = GPIO11 GP12 Pin = GPIO12 GP13 Pin = GPIO13 GP14 Pin = GPIO14 GP15 Pin = GPIO15 GP16 Pin = GPIO16 GP17 Pin = NoPin GP18 Pin = NoPin GP19 Pin = NoPin GP20 Pin = NoPin GP21 Pin = NoPin GP22 Pin = NoPin GP23 Pin = NoPin GP24 Pin = GPIO24 GP25 Pin = GPIO25 GP26 Pin = GPIO26 GP27 Pin = GPIO27 GP28 Pin = GPIO28 GP29 Pin = GPIO29 ) // Analog pins const ( A0 Pin = GP26 A1 Pin = GP27 A2 Pin = GP28 A3 Pin = GP29 ) // Onboard LEDs const ( LED = GP16 WS2812 = GP16 ) // I2C pins const ( I2C0_SDA_PIN Pin = GP0 I2C0_SCL_PIN Pin = GP1 I2C1_SDA_PIN Pin = GP2 I2C1_SCL_PIN Pin = GP3 // default I2C0 I2C_SDA_PIN Pin = I2C0_SDA_PIN I2C_SCL_PIN Pin = I2C0_SCL_PIN ) // SPI pins const ( SPI0_RX_PIN Pin = GP0 SPI0_CSN_PIN Pin = GP1 SPI0_SCK_PIN Pin = GP2 SPI0_TX_PIN Pin = GP3 SPI0_SDO_PIN Pin = SPI0_TX_PIN SPI0_SDI_PIN Pin = SPI0_RX_PIN SPI1_RX_PIN Pin = GP8 SPI1_CSN_PIN Pin = GP9 SPI1_SCK_PIN Pin = GP10 SPI1_TX_PIN Pin = GP11 SPI1_SDO_PIN Pin = SPI1_TX_PIN SPI1_SDI_PIN Pin = SPI1_RX_PIN // default SPI0 SPI_RX_PIN Pin = SPI0_RX_PIN SPI_CSN_PIN Pin = SPI0_CSN_PIN SPI_SCK_PIN Pin = SPI0_SCK_PIN SPI_TX_PIN Pin = SPI0_TX_PIN SPI_SDO_PIN Pin = SPI0_TX_PIN SPI_SDI_PIN Pin = SPI0_RX_PIN ) // Onboard crystal oscillator frequency, in MHz. const ( xoscFreq = 12 // MHz ) // UART pins const ( UART0_TX_PIN = GP0 UART0_RX_PIN = GP1 UART1_TX_PIN = GP8 UART1_RX_PIN = GP9 // default UART0 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "RP2040-Tiny" usb_STRING_MANUFACTURER = "Waveshare" ) var ( usb_VID uint16 = 0x2e8a usb_PID uint16 = 0x0003 ) ================================================ FILE: src/machine/board_wioterminal.go ================================================ //go:build wioterminal package machine // used to reset into bootloader const resetMagicValue = 0xf01669ef const ( ADC0 = A0 ADC1 = A1 ADC2 = A2 ADC3 = A3 ADC4 = A4 ADC5 = A5 ADC6 = A6 ADC7 = A7 ADC8 = A8 LED = PIN_LED BUTTON = BUTTON_1 ) const ( // https://github.com/Seeed-Studio/ArduinoCore-samd/blob/master/variants/wio_terminal/variant.h // LEDs PIN_LED_13 = PA15 PIN_LED_RXL = PA15 PIN_LED_TXL = PA15 PIN_LED = PIN_LED_13 PIN_LED2 = PIN_LED_RXL PIN_LED3 = PIN_LED_TXL LED_BUILTIN = PIN_LED_13 PIN_NEOPIXEL = PA15 //Digital PINs D0 = PB08 D1 = PB09 D2 = PA07 D3 = PB04 D4 = PB05 D5 = PB06 D6 = PA04 D7 = PB07 D8 = PA06 //Analog PINs A0 = PB08 // ADC/AIN[0] A1 = PB09 // ADC/AIN[2] A2 = PA07 // ADC/AIN[3] A3 = PB04 // ADC/AIN[4] A4 = PB05 // ADC/AIN[5] A5 = PB06 // ADC/AIN[10] A6 = PA04 // ADC/AIN[10] A7 = PB07 // ADC/AIN[10] A8 = PA06 // ADC/AIN[10] // 3.3V || 5V // BCM2 || 5V // BCM3 || GND // BCM4 || BCM14 // GND || BCM15 // BCM17 || BCM18 // BCM27 || GND // BCM22 || BCM23 // GND || BCM24 // BCM10 || GND // BCM9 || BCM25 // BCM11 || BCM8 // GND || BCM7 // BCM0 || BCM1 // BCM5 || GND // BCM6 || BCM12 // BCM13 || GND // BCM19 || BCM16 // BCM26 || BCM20 // GND || BCM21 //PIN DEFINE FOR RPI BCM0 = PA13 // I2C Wire1 BCM1 = PA12 // I2C Wire1 BCM2 = PA17 // I2C Wire2 BCM3 = PA16 // I2C Wire2 BCM4 = PB14 // GCLK BCM5 = PB12 // GCLK BCM6 = PB13 // GCLK BCM7 = PA05 // DAC1 BCM8 = PB01 // SPI SS BCM9 = PB00 // SPI SDI BCM10 = PB02 // SPI SDO BCM11 = PB03 // SPI SCK BCM12 = PB06 BCM13 = PA04 BCM14 = PB27 // UART Serial1 BCM15 = PB26 // UART Serial1 BCM16 = PB07 BCM17 = PA02 // DAC0 BCM18 = PB28 // FPC Digital & AD pins BCM19 = PA20 // WIO_IR BCM20 = PA21 // I2S SDO BCM21 = PA22 // I2S SDI BCM22 = PB09 BCM23 = PA07 BCM24 = PB04 BCM25 = PB05 BCM26 = PA06 BCM27 = PB08 // FPC NEW DEFINE FPC1 = PB28 // FPC Digital & AD pins FPC2 = PB17 FPC3 = PB29 FPC4 = PA14 FPC5 = PC01 FPC6 = PC02 FPC7 = PC03 FPC8 = PC04 FPC9 = PC31 FPC10 = PD00 // RPI Analog RPIs RPI_A0 = PB08 RPI_A1 = PB09 RPI_A2 = PA07 RPI_A3 = PB04 RPI_A4 = PB05 RPI_A5 = PB06 RPI_A6 = PA04 RPI_A7 = PB07 RPI_A8 = PA06 PIN_DAC0 = PA02 PIN_DAC1 = PA05 // FPO Analog RPIs //FPC_A7 = FPC_D7 //FPC_A8 = FPC_D8 //FPC_A9 = FPC_D9 //FPC_A11 = FPC_D11 //FPC_A12 = FPC_D12 //FPC_A13 = FPC_D13 // USB PIN_USB_DM = PA24 PIN_USB_DP = PA25 PIN_USB_HOST_ENABLE = PA27 // BUTTON BUTTON_1 = PC26 BUTTON_2 = PC27 BUTTON_3 = PC28 WIO_KEY_A = PC26 WIO_KEY_B = PC27 WIO_KEY_C = PC28 // SWITCH SWITCH_X = PD20 SWITCH_Y = PD12 SWITCH_Z = PD09 SWITCH_B = PD08 SWITCH_U = PD10 WIO_5S_UP = PD20 WIO_5S_LEFT = PD12 WIO_5S_RIGHT = PD09 WIO_5S_DOWN = PD08 WIO_5S_PRESS = PD10 // IRQ0 : RTL8720D IRQ0 = PC20 // BUZZER_CTR BUZZER_CTR = PD11 WIO_BUZZER = PD11 // MIC_INPUT MIC_INPUT = PC30 WIO_MIC = PC30 // GCLK GCLK0 = PB14 GCLK1 = PB12 GCLK2 = PB13 // Serial interfaces // Serial1 PIN_SERIAL1_RX = PB27 PIN_SERIAL1_TX = PB26 // Serial2 : RTL8720D PIN_SERIAL2_RX = PC23 PIN_SERIAL2_TX = PC22 // Wire Interfaces // I2C Wire2 // I2C1 PIN_WIRE_SDA = PA17 PIN_WIRE_SCL = PA16 SDA = PIN_WIRE_SDA SCL = PIN_WIRE_SCL // I2C Wire1 // I2C0 : LIS3DHTR and ATECC608 PIN_WIRE1_SDA = PA13 PIN_WIRE1_SCL = PA12 SDA1 = PIN_WIRE1_SDA SCL1 = PIN_WIRE1_SCL PIN_GYROSCOPE_WIRE_SDA = PIN_WIRE1_SDA PIN_GYROSCOPE_WIRE_SCL = PIN_WIRE1_SCL GYROSCOPE_INT1 = PC21 WIO_LIS3DH_SDA = PIN_WIRE1_SDA WIO_LIS3DH_SCL = PIN_WIRE1_SCL WIO_LIS3DH_INT = PC21 // SPI PIN_SPI_SDI = PB00 PIN_SPI_SDO = PB02 PIN_SPI_SCK = PB03 PIN_SPI_SS = PB01 SS = PIN_SPI_SS SDO = PIN_SPI_SDO SDI = PIN_SPI_SDI SCK = PIN_SPI_SCK // SPI1 RTL8720D_SPI PIN_SPI1_SDI = PC24 PIN_SPI1_SDO = PB24 PIN_SPI1_SCK = PB25 PIN_SPI1_SS = PC25 SS1 = PIN_SPI1_SS SDO1 = PIN_SPI1_SDO SDI1 = PIN_SPI1_SDI SCK1 = PIN_SPI1_SCK // SPI2 SD_SPI PIN_SPI2_SDI = PC18 PIN_SPI2_SDO = PC16 PIN_SPI2_SCK = PC17 PIN_SPI2_SS = PC19 SS2 = PIN_SPI2_SS SDO2 = PIN_SPI2_SDO SDI2 = PIN_SPI2_SDI SCK2 = PIN_SPI2_SCK // SPI3 LCD_SPI PIN_SPI3_SDI = PB18 PIN_SPI3_SDO = PB19 PIN_SPI3_SCK = PB20 PIN_SPI3_SS = PB21 SS3 = PIN_SPI3_SS SDO3 = PIN_SPI3_SDO SDI3 = PIN_SPI3_SDI SCK3 = PIN_SPI3_SCK // Needed for SD library SDCARD_SDI_PIN = PIN_SPI2_SDI SDCARD_SDO_PIN = PIN_SPI2_SDO SDCARD_SCK_PIN = PIN_SPI2_SCK SDCARD_SS_PIN = PIN_SPI2_SS SDCARD_DET_PIN = PD21 LCD_SDI_PIN = PIN_SPI3_SDI LCD_SDO_PIN = PIN_SPI3_SDO LCD_SCK_PIN = PIN_SPI3_SCK LCD_SS_PIN = PIN_SPI3_SS LCD_DC = PC06 LCD_RESET = PC07 LCD_BACKLIGHT = PC05 // 4 WIRE LCD TOUCH LCD_XL = PC10 LCD_YU = PC11 LCD_XR = PC12 LCD_YD = PC13 // Needed for RTL8720D RTL8720D_SDI_PIN = PIN_SPI1_SDI RTL8720D_SDO_PIN = PIN_SPI1_SDO RTL8720D_SCK_PIN = PIN_SPI1_SCK RTL8720D_SS_PIN = PIN_SPI1_SS //QSPI Pins PIN_QSPI_IO0 = PA08 PIN_QSPI_IO1 = PA09 PIN_QSPI_IO2 = PA10 PIN_QSPI_IO3 = PA11 PIN_QSPI_SCK = PB10 PIN_QSPI_CS = PB11 // I2S Interfaces PIN_I2S_FS = PA20 PIN_I2S_SCK = PB16 PIN_I2S_SDO = PA22 PIN_I2S_SDI = PA21 I2S_LRCLK = PA20 I2S_BLCK = PB16 I2S_SDOUT = PA22 I2S_SDIN = PA21 // RTL8720D Interfaces RTL8720D_CHIP_PU = PA18 RTL8720D_GPIO0 = PA19 // SYNC // SWD SWDCLK = PA30 SWDIO = PA31 SWO = PB30 // light sensor WIO_LIGHT = PD01 // ir sensor WIO_IR = PB31 // OUTPUT_CTR OUTPUT_CTR_5V = PC14 OUTPUT_CTR_3V3 = PC15 ) // USBCDC pins const ( USBCDC_DM_PIN = PIN_USB_DM USBCDC_DP_PIN = PIN_USB_DP ) // UART1 pins const ( UART_TX_PIN = PIN_SERIAL1_TX UART_RX_PIN = PIN_SERIAL1_RX ) // UART2 pins RTL8720D const ( UART2_TX_PIN = PIN_SERIAL2_TX UART2_RX_PIN = PIN_SERIAL2_RX ) var ( DefaultUART = UART1 UART1 = &sercomUSART2 // RTL8720D (tx: PC22, rx: PC23) UART2 = &sercomUSART1 // RTL8720D (tx: PB24, rx: PC24) UART3 = &sercomUSART0 // Right-hand grove port (tx: D0, rx: D1) UART4 = &sercomUSART4 ) // I2C pins const ( SDA1_PIN = PA17 // SDA: SERCOM3/PAD[0] SCL1_PIN = PA16 // SCL: SERCOM3/PAD[1] SDA0_PIN = PA13 // SDA: SERCOM4/PAD[0] SCL0_PIN = PA12 // SCL: SERCOM4/PAD[1] SDA_PIN = SDA1_PIN SCL_PIN = SCL1_PIN ) // I2C on the Wio Terminal var ( I2C0 = sercomI2CM4 I2C1 = sercomI2CM3 ) // I2S pins const ( I2S_SCK_PIN = BCM18 I2S_SDO_PIN = BCM21 I2S_SDI_PIN = BCM20 I2S_WS_PIN = BCM19 ) // SPI pins const ( SPI0_SCK_PIN = SCK // SCK: SERCOM5/PAD[1] SPI0_SDO_PIN = SDO // SDO: SERCOM5/PAD[0] SPI0_SDI_PIN = SDI // SDI: SERCOM5/PAD[2] // RTL8720D SPI1_SCK_PIN = SCK1 // SCK: SERCOM0/PAD[1] SPI1_SDO_PIN = SDO1 // SDO: SERCOM0/PAD[0] SPI1_SDI_PIN = SDI1 // SDI: SERCOM0/PAD[2] // SD SPI2_SCK_PIN = SCK2 // SCK: SERCOM6/PAD[1] SPI2_SDO_PIN = SDO2 // SDO: SERCOM6/PAD[0] SPI2_SDI_PIN = SDI2 // SDI: SERCOM6/PAD[2] // LCD SPI3_SCK_PIN = SCK3 // SCK: SERCOM7/PAD[1] SPI3_SDO_PIN = SDO3 // SDO: SERCOM7/PAD[3] SPI3_SDI_PIN = SDI3 // SDI: SERCOM7/PAD[2] ) // SPI on the Wio Terminal var ( SPI0 = sercomSPIM5 // RTL8720D SPI1 = sercomSPIM0 // SD SPI2 = sercomSPIM6 // LCD SPI3 = sercomSPIM7 ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Seeed Wio Terminal" usb_STRING_MANUFACTURER = "Seeed" ) var ( usb_VID uint16 = 0x2886 usb_PID uint16 = 0x802D ) ================================================ FILE: src/machine/board_x9pro.go ================================================ //go:build x9pro package machine // https://hackaday.io/project/144350-hacking-wearables-for-mental-health-and-more/details const ( LED Pin = 4 // HR LED pin UART_TX_PIN Pin = NoPin UART_RX_PIN Pin = NoPin SCL_PIN Pin = NoPin SDA_PIN Pin = NoPin SPI0_SCK_PIN Pin = 18 SPI0_SDI_PIN Pin = 19 SPI0_SDO_PIN Pin = 20 ) // LCD pins. const ( OLED_CS Pin = 15 // chip select OLED_RES Pin = 14 // reset pin OLED_DC Pin = 13 // data/command OLED_SCK Pin = 12 // SPI clock OLED_SDO Pin = 11 // SPI SDO (chip-out, peripheral-in) OLED_LED_POW Pin = 16 OLED_IC_POW Pin = 17 ) const HasLowFrequencyCrystal = true var DefaultUART = UART0 ================================================ FILE: src/machine/board_xiao-ble.go ================================================ //go:build xiao_ble // This file contains the pin mappings for the Seeed XIAO BLE nRF52840 [Sense] boards. // // Seeed XIAO BLE is an ultra-small size, ultra-low power Bluetooth development board based on the Nordic nRF52840. // It features an onboard Bluetooth antenna, onboard battery charging chip, and 21*17.5mm thumb size, which makes it ideal for IoT projects. // // Seeed XIAO BLE nRF52840 Sense is a tiny Bluetooth LE development board designed for IoT and AI applications. // It features an onboard antenna, 6 Dof IMU, microphone, all of which make it an ideal board to run AI using TinyML and TensorFlow Lite. // // SoftDevice (s140v7) is pre-flashed on this board already. // See https://github.com/tinygo-org/bluetooth // // - https://www.seeedstudio.com/Seeed-XIAO-BLE-nRF52840-p-5201.html // - https://www.seeedstudio.com/Seeed-XIAO-BLE-Sense-nRF52840-p-5253.html // // - https://wiki.seeedstudio.com/XIAO_BLE/ // - https://github.com/Seeed-Studio/ArduinoCore-mbed/tree/master/variants/SEEED_XIAO_NRF52840_SENSE package machine const HasLowFrequencyCrystal = true // Digital Pins const ( D0 Pin = P0_02 D1 Pin = P0_03 D2 Pin = P0_28 D3 Pin = P0_29 D4 Pin = P0_04 D5 Pin = P0_05 D6 Pin = P1_11 D7 Pin = P1_12 D8 Pin = P1_13 D9 Pin = P1_14 D10 Pin = P1_15 ) // Analog pins const ( A0 Pin = P0_02 A1 Pin = P0_03 A2 Pin = P0_28 A3 Pin = P0_29 A4 Pin = P0_04 A5 Pin = P0_05 ) // Onboard LEDs const ( LED = LED_CHG LED1 = LED_RED LED2 = LED_GREEN LED3 = LED_BLUE LED_CHG = P0_17 LED_RED = P0_26 LED_GREEN = P0_30 LED_BLUE = P0_06 ) // UART0 pins const ( UART_RX_PIN = P1_12 UART_TX_PIN = P1_11 ) // I2C pins const ( // Defaults to internal SDA_PIN = SDA1_PIN SCL_PIN = SCL1_PIN // I2C0 (external) pins SDA0_PIN = P0_04 SCL0_PIN = P0_05 // I2C1 (internal) pins SDA1_PIN = P0_07 SCL1_PIN = P0_27 ) // SPI pins const ( SPI0_SCK_PIN = P1_13 SPI0_SDO_PIN = P1_14 SPI0_SDI_PIN = P1_15 ) // Peripherals const ( LSM_PWR = P1_08 // IMU (LSM6DS3TR) power LSM_INT = P0_11 // IMU (LSM6DS3TR) interrupt MIC_PWR = P1_10 // Microphone (MSM261D3526H1CPM) power MIC_CLK = P1_00 MIC_DIN = P0_16 ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "XIAO nRF52840 Sense" usb_STRING_MANUFACTURER = "Seeed" ) var ( usb_VID uint16 = 0x2886 usb_PID uint16 = 0x8045 ) var ( DefaultUART = UART0 ) ================================================ FILE: src/machine/board_xiao-esp32c3.go ================================================ //go:build xiao_esp32c3 // This file contains the pin mappings for the Seeed XIAO ESP32C3 boards. // // Seeed Studio XIAO ESP32C3 is an IoT mini development board based on // the Espressif ESP32-C3 WiFi/Bluetooth dual-mode chip. // // - https://www.seeedstudio.com/Seeed-XIAO-ESP32C3-p-5431.html // - https://wiki.seeedstudio.com/XIAO_ESP32C3_Getting_Started/ package machine // Digital Pins const ( D0 = GPIO2 D1 = GPIO3 D2 = GPIO4 D3 = GPIO5 D4 = GPIO6 D5 = GPIO7 D6 = GPIO21 D7 = GPIO20 D8 = GPIO8 D9 = GPIO9 D10 = GPIO10 ) // Analog pins const ( A0 = GPIO2 A1 = GPIO3 A2 = GPIO4 A3 = GPIO5 ) // UART pins const ( UART_RX_PIN = GPIO20 UART_TX_PIN = GPIO21 ) // I2C pins const ( SDA_PIN = GPIO6 SCL_PIN = GPIO7 ) // SPI pins const ( SPI_SCK_PIN = GPIO8 SPI_SDI_PIN = GPIO9 SPI_SDO_PIN = GPIO10 ) ================================================ FILE: src/machine/board_xiao-esp32s3.go ================================================ //go:build xiao_esp32s3 // This file contains the pin mappings for the Seeed XIAO ESP32S3 boards. // // Seeed Studio XIAO ESP32S3 is an IoT mini development board based on // the Espressif ESP32-S3 WiFi/Bluetooth dual-mode chip. // // - https://www.seeedstudio.com/XIAO-ESP32S3-p-5627.html // - https://wiki.seeedstudio.com/xiao_esp32s3_getting_started/ package machine // Digital Pins const ( D0 = GPIO1 D1 = GPIO2 D2 = GPIO3 D3 = GPIO4 D4 = GPIO5 D5 = GPIO6 D6 = GPIO43 D7 = GPIO44 D8 = GPIO7 D9 = GPIO8 D10 = GPIO9 ) // Analog pins const ( A0 = GPIO1 A1 = GPIO2 A2 = GPIO3 A3 = GPIO4 ) // UART pins const ( UART_RX_PIN = GPIO44 UART_TX_PIN = GPIO43 ) // I2C pins const ( SDA_PIN = GPIO5 SCL_PIN = GPIO6 ) // SPI pins const ( SPI_SCK_PIN = GPIO7 SPI_SDI_PIN = GPIO9 SPI_SDO_PIN = GPIO8 ) // Onboard LEDs const ( LED = GPIO21 ) ================================================ FILE: src/machine/board_xiao-rp2040.go ================================================ //go:build xiao_rp2040 // This file contains the pin mappings for the Seeed XIAO RP2040 boards. // // XIAO RP2040 is a microcontroller using the Raspberry Pi RP2040 chip. // // - https://wiki.seeedstudio.com/XIAO-RP2040/ package machine // Digital Pins const ( D0 Pin = GPIO26 D1 Pin = GPIO27 D2 Pin = GPIO28 D3 Pin = GPIO29 D4 Pin = GPIO6 D5 Pin = GPIO7 D6 Pin = GPIO0 D7 Pin = GPIO1 D8 Pin = GPIO2 D9 Pin = GPIO4 D10 Pin = GPIO3 ) // Analog pins const ( A0 Pin = D0 A1 Pin = D1 A2 Pin = D2 A3 Pin = D3 ) // Onboard LEDs const ( NEOPIXEL = GPIO12 WS2812 = GPIO12 NEO_PWR = GPIO11 NEOPIXEL_POWER = GPIO11 LED = GPIO17 LED_RED = GPIO17 LED_GREEN = GPIO16 LED_BLUE = GPIO25 ) // I2C pins const ( I2C0_SDA_PIN Pin = D2 I2C0_SCL_PIN Pin = D3 I2C1_SDA_PIN Pin = D4 I2C1_SCL_PIN Pin = D5 ) // SPI pins const ( SPI0_SCK_PIN Pin = D8 SPI0_SDO_PIN Pin = D10 SPI0_SDI_PIN Pin = D9 SPI1_SCK_PIN Pin = NoPin SPI1_SDO_PIN Pin = NoPin SPI1_SDI_PIN Pin = NoPin ) // Onboard crystal oscillator frequency, in MHz. const ( xoscFreq = 12 // MHz ) // UART pins const ( UART0_TX_PIN = GPIO0 UART0_RX_PIN = GPIO1 UART_TX_PIN = UART0_TX_PIN UART_RX_PIN = UART0_RX_PIN ) var DefaultUART = UART0 // USB CDC identifiers const ( usb_STRING_PRODUCT = "XIAO RP2040" usb_STRING_MANUFACTURER = "Seeed" ) var ( usb_VID uint16 = 0x2e8a usb_PID uint16 = 0x000a ) ================================================ FILE: src/machine/board_xiao.go ================================================ //go:build sam && atsamd21 && xiao package machine // used to reset into bootloader const resetMagicValue = 0xf01669ef // GPIO Pins const ( D0 = PA02 // can be used for PWM or DAC D1 = PA04 // PWM available D2 = PA10 // PWM available D3 = PA11 // PWM available D4 = PA08 // can be used for PWM or I2C SDA D5 = PA09 // can be used for PWM or I2C SCL D6 = PB08 // can be used for PWM or UART1 TX D7 = PB09 // can be used for PWM or UART1 RX D8 = PA07 // can be used for PWM or SPI SCK D9 = PA05 // can be used for PWM or SPI SDI D10 = PA06 // can be used for PWM or SPI SDO ) // Analog pins const ( A0 = PA02 // ADC/AIN[0] A1 = PA04 // ADC/AIN[4] A2 = PA10 // ADC/AIN[18] A3 = PA11 // ADC/AIN[19] A4 = PA08 // ADC/AIN[16] A5 = PA09 // ADC/AIN[17] A6 = PB08 // ADC/AIN[2] A7 = PB09 // ADC/AIN[3] A8 = PA07 // ADC/AIN[7] A9 = PA05 // ADC/AIN[6] A10 = PA06 // ADC/AIN[5] ) const ( LED = PA17 LED_RXL = PA18 LED_TXL = PA19 LED2 = LED_RXL LED3 = LED_TXL ) // USBCDC pins const ( USBCDC_DM_PIN = PA24 USBCDC_DP_PIN = PA25 ) // UART1 pins const ( UART_TX_PIN = D6 UART_RX_PIN = D7 ) // UART1 on the Xiao var UART1 = &sercomUSART4 // I2C pins const ( SDA_PIN = PA08 // SDA: SERCOM2/PAD[0] SCL_PIN = PA09 // SCL: SERCOM2/PAD[1] ) // I2C on the Xiao var ( I2C0 = sercomI2CM2 ) // SPI pins const ( SPI0_SCK_PIN = PA07 // SCK: SERCOM0/PAD[3] SPI0_SDO_PIN = PA06 // SDO: SERCOM0/PAD[2] SPI0_SDI_PIN = PA05 // SDI: SERCOM0/PAD[1] ) // SPI on the Xiao var SPI0 = sercomSPIM0 // I2S pins const ( I2S_SCK_PIN = PA10 I2S_SDO_PIN = PA08 I2S_SDI_PIN = NoPin // TODO: figure out what this is on Xiao I2S_WS_PIN = NoPin // TODO: figure out what this is on Xiao ) // USB CDC identifiers const ( usb_STRING_PRODUCT = "Seeed XIAO M0" usb_STRING_MANUFACTURER = "Seeed" ) var ( usb_VID uint16 = 0x2886 usb_PID uint16 = 0x802F ) var ( DefaultUART = UART1 ) ================================================ FILE: src/machine/buffer.go ================================================ package machine import ( "runtime/volatile" ) // RingBuffer is ring buffer implementation inspired by post at // https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php type RingBuffer struct { rxbuffer [bufferSize]volatile.Register8 head volatile.Register8 tail volatile.Register8 } // NewRingBuffer returns a new ring buffer. func NewRingBuffer() *RingBuffer { return &RingBuffer{} } // Used returns how many bytes in buffer have been used. func (rb *RingBuffer) Used() uint8 { return uint8(rb.head.Get() - rb.tail.Get()) } // Put stores a byte in the buffer. If the buffer is already // full, the method will return false. func (rb *RingBuffer) Put(val byte) bool { if rb.Used() != bufferSize { rb.head.Set(rb.head.Get() + 1) rb.rxbuffer[rb.head.Get()%bufferSize].Set(val) return true } return false } // Get returns a byte from the buffer. If the buffer is empty, // the method will return a false as the second value. func (rb *RingBuffer) Get() (byte, bool) { if rb.Used() != 0 { rb.tail.Set(rb.tail.Get() + 1) return rb.rxbuffer[rb.tail.Get()%bufferSize].Get(), true } return 0, false } // Clear resets the head and tail pointer to zero. func (rb *RingBuffer) Clear() { rb.head.Set(0) rb.tail.Set(0) } ================================================ FILE: src/machine/buffer_atmega.go ================================================ //go:build atmega package machine const bufferSize = 32 ================================================ FILE: src/machine/buffer_generic.go ================================================ //go:build !atmega package machine const bufferSize = 128 ================================================ FILE: src/machine/deviceid.go ================================================ //go:build rp2040 || nrf || sam package machine // DeviceID returns an identifier that is unique within // a particular chipset. // // The identity is one burnt into the MCU itself, or the // flash chip at time of manufacture. // // It's possible that two different vendors may allocate // the same DeviceID, so callers should take this into // account if needing to generate a globally unique id. // // The length of the hardware ID is vendor-specific, but // 8 bytes (64 bits) and 16 bytes (128 bits) are common. var _ = (func() []byte)(DeviceID) ================================================ FILE: src/machine/flash.go ================================================ //go:build nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32l4 || stm32wlx || atsamd21 || atsamd51 || atsame5x || rp2040 || rp2350 package machine import ( "errors" "io" "unsafe" ) //go:extern __flash_data_start var flashDataStart [0]byte //go:extern __flash_data_end var flashDataEnd [0]byte // Return the start of the writable flash area, aligned on a page boundary. This // is usually just after the program and static data. func FlashDataStart() uintptr { pagesize := uintptr(eraseBlockSize()) return (uintptr(unsafe.Pointer(&flashDataStart)) + pagesize - 1) &^ (pagesize - 1) } // Return the end of the writable flash area. Usually this is the address one // past the end of the on-chip flash. func FlashDataEnd() uintptr { return uintptr(unsafe.Pointer(&flashDataEnd)) } var ( errFlashCannotErasePage = errors.New("cannot erase flash page") errFlashInvalidWriteLength = errors.New("write flash data must align to correct number of bits") errFlashNotAllowedWriteData = errors.New("not allowed to write flash data") errFlashCannotWriteData = errors.New("cannot write flash data") errFlashCannotReadPastEOF = errors.New("cannot read beyond end of flash data") errFlashCannotWritePastEOF = errors.New("cannot write beyond end of flash data") errFlashCannotErasePastEOF = errors.New("cannot erase beyond end of flash data") ) // BlockDevice is the raw device that is meant to store flash data. type BlockDevice interface { // ReadAt reads the given number of bytes from the block device. io.ReaderAt // WriteAt writes the given number of bytes to the block device. // // This interface directly writes data to the underlying block device. // Different kinds of devices have different requirements: most can only // write data after the page has been erased, and many can only write data // with specific alignment (such as 4-byte alignment). io.WriterAt // Size returns the number of bytes in this block device. Size() int64 // WriteBlockSize returns the block size in which data can be written to // memory. It can be used by a client to optimize writes, non-aligned writes // should always work correctly. WriteBlockSize() int64 // EraseBlockSize returns the smallest erasable area on this particular chip // in bytes. This is used for the block size in EraseBlocks. // It must be a power of two, and may be as small as 1. A typical size is 4096. EraseBlockSize() int64 // EraseBlocks erases the given number of blocks. An implementation may // transparently coalesce ranges of blocks into larger bundles if the chip // supports this. The start and len parameters are in block numbers, use // EraseBlockSize to map addresses to blocks. EraseBlocks(start, len int64) error } // pad data if needed so it is long enough for correct byte alignment on writes. func flashPad(p []byte, writeBlockSize int) []byte { overflow := len(p) % writeBlockSize if overflow != 0 { for i := 0; i < writeBlockSize-overflow; i++ { p = append(p, 0xff) } } return p } ================================================ FILE: src/machine/i2c.go ================================================ //go:build !baremetal || atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || rp2350 || mimxrt1062 || (esp32c3 && !m5stamp_c3) || esp32 package machine import ( "errors" ) // If you are getting a compile error on this line please check to see you've // correctly implemented the methods on the I2C type. They must match // the i2cController interface method signatures type to type perfectly. // If not implementing the I2C type please remove your target from the build tags // at the top of this file. var _ interface { // 2 Configure(config I2CConfig) error Tx(addr uint16, w, r []byte) error SetBaudRate(br uint32) error } = (*I2C)(nil) // TWI_FREQ is the I2C bus speed. Normally either 100 kHz, or 400 kHz for high-speed bus. // // Deprecated: use 100 * machine.KHz or 400 * machine.KHz instead. const ( TWI_FREQ_100KHZ = 100000 TWI_FREQ_400KHZ = 400000 ) var ( errI2CWriteTimeout = errors.New("i2c: timeout during write") errI2CReadTimeout = errors.New("i2c: timeout during read") errI2CBusReadyTimeout = errors.New("i2c: timeout on bus ready") errI2CSignalStartTimeout = errors.New("i2c: timeout on signal start") errI2CSignalReadTimeout = errors.New("i2c: timeout on signal read") errI2CSignalStopTimeout = errors.New("i2c: timeout on signal stop") errI2CAckExpected = errors.New("i2c: error: expected ACK not NACK") errI2CBusError = errors.New("i2c: bus error") errI2COverflow = errors.New("i2c: receive buffer overflow") errI2COverread = errors.New("i2c: transmit buffer overflow") errI2CNotImplemented = errors.New("i2c: operation not yet implemented") errI2CNoDevices = errors.New("i2c: bus has no devices") // simulator only errI2CMultipleDevices = errors.New("i2c: bus has address conflict") // simulator only errI2CWrongAddress = errors.New("i2c: bus has devices but none with this address") // simulator only ) // I2CTargetEvent reflects events on the I2C bus type I2CTargetEvent uint8 const ( // I2CReceive indicates target has received a message from the controller. I2CReceive I2CTargetEvent = iota // I2CRequest indicates the controller is expecting a message from the target. I2CRequest // I2CFinish indicates the controller has ended the transaction. // // I2C controllers can chain multiple receive/request messages without // relinquishing the bus by doing 'restarts'. I2CFinish indicates the // bus has been relinquished by an I2C 'stop'. I2CFinish ) // I2CMode determines if an I2C peripheral is in Controller or Target mode. type I2CMode int const ( // I2CModeController represents an I2C peripheral in controller mode. I2CModeController I2CMode = iota // I2CModeTarget represents an I2C peripheral in target mode. I2CModeTarget ) // WriteRegister transmits first the register and then the data to the // peripheral device. // // Many I2C-compatible devices are organized in terms of registers. This method // is a shortcut to easily write to such registers. Also, it only works for // devices with 7-bit addresses, which is the vast majority. func (i2c *I2C) WriteRegister(address uint8, register uint8, data []byte) error { buf := make([]uint8, len(data)+1) buf[0] = register copy(buf[1:], data) return i2c.Tx(uint16(address), buf, nil) } // ReadRegister transmits the register, restarts the connection as a read // operation, and reads the response. // // Many I2C-compatible devices are organized in terms of registers. This method // is a shortcut to easily read such registers. Also, it only works for devices // with 7-bit addresses, which is the vast majority. func (i2c *I2C) ReadRegister(address uint8, register uint8, data []byte) error { return i2c.Tx(uint16(address), []byte{register}, data) } ================================================ FILE: src/machine/i2s.go ================================================ //go:build sam && atsamd21 // This is the definition for I2S bus functions. // Actual implementations if available for any given hardware // are to be found in its the board definition. // // For more info about I2S, see: https://en.wikipedia.org/wiki/I%C2%B2S // package machine import "errors" // If you are getting a compile error on this line please check to see you've // correctly implemented the methods on the I2S type. They must match // the interface method signatures type to type perfectly. // If not implementing the I2S type please remove your target from the build tags // at the top of this file. var _ interface { SetSampleFrequency(freq uint32) error ReadMono(b []uint16) (int, error) ReadStereo(b []uint32) (int, error) WriteMono(b []uint16) (int, error) WriteStereo(b []uint32) (int, error) Enable(enabled bool) } = (*I2S)(nil) type I2SMode uint8 type I2SStandard uint8 type I2SClockSource uint8 type I2SDataFormat uint8 const ( I2SModeSource I2SMode = iota I2SModeReceiver I2SModePDM I2SModeSourceReceiver ) const ( I2StandardPhilips I2SStandard = iota I2SStandardMSB I2SStandardLSB ) const ( I2SClockSourceInternal I2SClockSource = iota I2SClockSourceExternal ) const ( I2SDataFormatDefault I2SDataFormat = 0 I2SDataFormat8bit = 8 I2SDataFormat16bit = 16 I2SDataFormat24bit = 24 I2SDataFormat32bit = 32 ) var ( ErrInvalidSampleFrequency = errors.New("i2s: invalid sample frequency") ) // All fields are optional and may not be required or used on a particular platform. type I2SConfig struct { // clock SCK Pin // word select WS Pin // data out SDO Pin // data in SDI Pin Mode I2SMode Standard I2SStandard ClockSource I2SClockSource DataFormat I2SDataFormat AudioFrequency uint32 MainClockOutput bool Stereo bool } ================================================ FILE: src/machine/machine.go ================================================ package machine import ( "errors" "unsafe" ) var ( ErrTimeoutRNG = errors.New("machine: RNG Timeout") ErrClockRNG = errors.New("machine: RNG Clock Error") ErrSeedRNG = errors.New("machine: RNG Seed Error") ErrInvalidInputPin = errors.New("machine: invalid input pin") ErrInvalidOutputPin = errors.New("machine: invalid output pin") ErrInvalidClockPin = errors.New("machine: invalid clock pin") ErrInvalidDataPin = errors.New("machine: invalid data pin") ErrNoPinChangeChannel = errors.New("machine: no channel available for pin interrupt") ) // Device is the running program's chip name, such as "ATSAMD51J19A" or // "nrf52840". It is not the same as the CPU name. // // The constant is some hardcoded default value if the program does not target a // particular chip but instead runs in WebAssembly for example. const Device = deviceName // Generic constants. const ( KHz = 1000 MHz = 1000_000 GHz = 1000_000_000 ) // PinMode sets the direction and pull mode of the pin. For example, PinOutput // sets the pin as an output and PinInputPullup sets the pin as an input with a // pull-up. type PinMode uint8 type PinConfig struct { Mode PinMode } // Pin is a single pin on a chip, which may be connected to other hardware // devices. It can either be used directly as GPIO pin or it can be used in // other peripherals like ADC, I2C, etc. type Pin uint8 // NoPin explicitly indicates "not a pin". Use this pin if you want to leave one // of the pins in a peripheral unconfigured (if supported by the hardware). const NoPin = Pin(0xff) // High sets this GPIO pin to high, assuming it has been configured as an output // pin. It is hardware dependent (and often undefined) what happens if you set a // pin to high that is not configured as an output pin. func (p Pin) High() { p.Set(true) } // Low sets this GPIO pin to low, assuming it has been configured as an output // pin. It is hardware dependent (and often undefined) what happens if you set a // pin to low that is not configured as an output pin. func (p Pin) Low() { p.Set(false) } type ADC struct { Pin Pin } // Convert the pointer to a uintptr, to be used for memory I/O (DMA for // example). It also means the pointer is "gone" as far as the compiler is // concerned, and a GC cycle might deallocate the object. To prevent this from // happening, also call keepAliveNoEscape at a point after the address isn't // accessed anymore by the hardware. // The only exception is if the pointer is accessed later in a volatile way // (volatile read/write), which also forces the value to stay alive until that // point. // // This function is treated specially by the compiler to mark the 'ptr' // parameter as not escaping. // // TODO: this function should eventually be replaced with the proposed ptrtoaddr // instruction in LLVM. See: // https://discourse.llvm.org/t/clarifiying-the-semantics-of-ptrtoint/83987/10 // https://github.com/llvm/llvm-project/pull/139357 func unsafeNoEscape(ptr unsafe.Pointer) uintptr { return uintptr(ptr) } // Make sure the given pointer stays alive until this point. This is similar to // runtime.KeepAlive, with the difference that it won't let the pointer escape. // This is typically used together with unsafeNoEscape. // // This is a compiler intrinsic. func keepAliveNoEscape(ptr unsafe.Pointer) ================================================ FILE: src/machine/machine_atmega.go ================================================ //go:build avr && atmega package machine import ( "device/avr" "runtime/interrupt" "runtime/volatile" "unsafe" ) // I2C on AVR. type I2C struct { srReg *volatile.Register8 brReg *volatile.Register8 crReg *volatile.Register8 drReg *volatile.Register8 srPS0 byte srPS1 byte crEN byte crINT byte crSTO byte crEA byte crSTA byte } // I2CConfig is used to store config info for I2C. type I2CConfig struct { Frequency uint32 } // Configure is intended to setup the I2C interface. func (i2c *I2C) Configure(config I2CConfig) error { // Default I2C bus speed is 100 kHz. if config.Frequency == 0 { config.Frequency = 100 * KHz } // Activate internal pullups for twi. avr.PORTC.SetBits((avr.DIDR0_ADC4D | avr.DIDR0_ADC5D)) return i2c.SetBaudRate(config.Frequency) } // SetBaudRate sets the communication speed for I2C. func (i2c *I2C) SetBaudRate(br uint32) error { // Initialize twi prescaler and bit rate. i2c.srReg.SetBits((i2c.srPS0 | i2c.srPS1)) // twi bit rate formula from atmega128 manual pg. 204: // SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR)) // NOTE: TWBR should be 10 or higher for controller mode. // It is 72 for a 16mhz board with 100kHz TWI i2c.brReg.Set(uint8(((CPUFrequency() / br) - 16) / 2)) // Enable twi module. i2c.crReg.Set(i2c.crEN) return nil } // Tx does a single I2C transaction at the specified address. // It clocks out the given address, writes the bytes in w, reads back len(r) // bytes and stores them in r, and generates a stop condition on the bus. func (i2c *I2C) Tx(addr uint16, w, r []byte) error { if len(w) != 0 { i2c.start(uint8(addr), true) // start transmission for writing for _, b := range w { i2c.writeByte(b) } } if len(r) != 0 { i2c.start(uint8(addr), false) // re-start transmission for reading for i := range r { // read each char r[i] = i2c.readByte() } } if len(w) != 0 || len(r) != 0 { // Stop the transmission after it has been started. i2c.stop() } return nil } // start starts an I2C communication session. func (i2c *I2C) start(address uint8, write bool) { // Clear TWI interrupt flag, put start condition on SDA, and enable TWI. i2c.crReg.Set((i2c.crINT | i2c.crSTA | i2c.crEN)) // Wait till start condition is transmitted. for !i2c.crReg.HasBits(i2c.crINT) { } // Write 7-bit shifted peripheral address. address <<= 1 if !write { address |= 1 // set read flag } i2c.writeByte(address) } // stop ends an I2C communication session. func (i2c *I2C) stop() { // Send stop condition. i2c.crReg.Set(i2c.crEN | i2c.crINT | i2c.crSTO) // Wait for stop condition to be executed on bus. for !i2c.crReg.HasBits(i2c.crSTO) { } } // writeByte writes a single byte to the I2C bus. func (i2c *I2C) writeByte(data byte) error { // Write data to register. i2c.drReg.Set(data) // Clear TWI interrupt flag and enable TWI. i2c.crReg.Set(i2c.crEN | i2c.crINT) // Wait till data is transmitted. for !i2c.crReg.HasBits(i2c.crINT) { } return nil } // readByte reads a single byte from the I2C bus. func (i2c *I2C) readByte() byte { // Clear TWI interrupt flag and enable TWI. i2c.crReg.Set(i2c.crEN | i2c.crINT | i2c.crEA) // Wait till read request is transmitted. for !i2c.crReg.HasBits(i2c.crINT) { } return byte(i2c.drReg.Get()) } // Always use UART0 as the serial output. var DefaultUART = UART0 // UART var ( // UART0 is the hardware serial port on the AVR. UART0 = &_UART0 _UART0 = UART{ Buffer: NewRingBuffer(), dataReg: avr.UDR0, baudRegH: avr.UBRR0H, baudRegL: avr.UBRR0L, statusRegA: avr.UCSR0A, statusRegB: avr.UCSR0B, statusRegC: avr.UCSR0C, } ) func init() { // Register the UART interrupt. interrupt.New(irq_USART0_RX, _UART0.handleInterrupt) } // UART on the AVR. type UART struct { Buffer *RingBuffer dataReg *volatile.Register8 baudRegH *volatile.Register8 baudRegL *volatile.Register8 statusRegA *volatile.Register8 statusRegB *volatile.Register8 statusRegC *volatile.Register8 } // Configure the UART on the AVR. Defaults to 9600 baud on Arduino. func (uart *UART) Configure(config UARTConfig) { if config.BaudRate == 0 { config.BaudRate = 9600 } // Prescale formula for u2x mode from AVR MiniCore source code. // Same as formula from specification but taking into account rounding error. ps := (CPUFrequency()/4/config.BaudRate - 1) / 2 uart.statusRegA.SetBits(avr.UCSR0A_U2X0) // Hardcoded exception for 57600 for compatibility with older bootloaders. // Also, prescale cannot be > 4095, so switch back to non-u2x mode if the baud rate is too low. if (CPUFrequency() == 16000000 && config.BaudRate == 57600) || ps > 0xfff { ps = (CPUFrequency()/8/config.BaudRate - 1) / 2 uart.statusRegA.ClearBits(avr.UCSR0A_U2X0) } uart.baudRegH.Set(uint8(ps >> 8)) uart.baudRegL.Set(uint8(ps & 0xff)) // enable RX, TX and RX interrupt uart.statusRegB.Set(avr.UCSR0B_RXEN0 | avr.UCSR0B_TXEN0 | avr.UCSR0B_RXCIE0) // 8-bits data uart.statusRegC.Set(avr.UCSR0C_UCSZ01 | avr.UCSR0C_UCSZ00) } func (uart *UART) handleInterrupt(intr interrupt.Interrupt) { // Read register to clear it. data := uart.dataReg.Get() // Ensure no error. if !uart.statusRegA.HasBits(avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0) { // Put data from UDR register into buffer. uart.Receive(byte(data)) } } // WriteByte writes a byte of data to the UART. func (uart *UART) writeByte(c byte) error { // Wait until UART buffer is not busy. for !uart.statusRegA.HasBits(avr.UCSR0A_UDRE0) { } uart.dataReg.Set(c) // send char return nil } func (uart *UART) flush() {} // SPIConfig is used to store config info for SPI. type SPIConfig struct { Frequency uint32 LSBFirst bool Mode uint8 } // SPI is for the Serial Peripheral Interface // Data is taken from http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf page 169 and following type SPI struct { // The registers for the SPIx port set by the chip spcr *volatile.Register8 spdr *volatile.Register8 spsr *volatile.Register8 spcrR0 byte spcrR1 byte spcrCPHA byte spcrCPOL byte spcrDORD byte spcrSPE byte spcrMSTR byte spsrI2X byte spsrSPIF byte // The io pins for the SPIx port set by the chip sck Pin sdi Pin sdo Pin cs Pin } // Configure is intended to setup the SPI interface. func (s *SPI) Configure(config SPIConfig) error { // This is only here to help catch a bug with the configuration // where a machine missed a value. if s.spcr == (*volatile.Register8)(unsafe.Pointer(uintptr(0))) || s.spsr == (*volatile.Register8)(unsafe.Pointer(uintptr(0))) || s.spdr == (*volatile.Register8)(unsafe.Pointer(uintptr(0))) || s.sck == 0 || s.sdi == 0 || s.sdo == 0 || s.cs == 0 { return errSPIInvalidMachineConfig } // Make the defaults meaningful if config.Frequency == 0 { config.Frequency = 4000000 } // Default all port configuration bits to 0 for simplicity s.spcr.Set(0) s.spsr.Set(0) // Setup pins output configuration s.sck.Configure(PinConfig{Mode: PinOutput}) s.sdi.Configure(PinConfig{Mode: PinInput}) s.sdo.Configure(PinConfig{Mode: PinOutput}) // Prevent CS glitches if the pin is enabled Low (0, default) s.cs.High() // If the CS pin is not configured as output the SPI port operates in // slave mode. s.cs.Configure(PinConfig{Mode: PinOutput}) frequencyDivider := CPUFrequency() / config.Frequency switch { case frequencyDivider >= 128: s.spcr.SetBits(s.spcrR0 | s.spcrR1) case frequencyDivider >= 64: s.spcr.SetBits(s.spcrR1) case frequencyDivider >= 32: s.spcr.SetBits(s.spcrR1) s.spsr.SetBits(s.spsrI2X) case frequencyDivider >= 16: s.spcr.SetBits(s.spcrR0) case frequencyDivider >= 8: s.spcr.SetBits(s.spcrR0) s.spsr.SetBits(s.spsrI2X) case frequencyDivider >= 4: // The clock is already set to all 0's. default: // defaults to fastest which is /2 s.spsr.SetBits(s.spsrI2X) } switch config.Mode { case Mode1: s.spcr.SetBits(s.spcrCPHA) case Mode2: s.spcr.SetBits(s.spcrCPHA) case Mode3: s.spcr.SetBits(s.spcrCPHA | s.spcrCPOL) default: // default is mode 0 } if config.LSBFirst { s.spcr.SetBits(s.spcrDORD) } // enable SPI, set controller, set clock rate s.spcr.SetBits(s.spcrSPE | s.spcrMSTR) return nil } // Transfer writes the byte into the register and returns the read content func (s *SPI) Transfer(b byte) (byte, error) { s.spdr.Set(uint8(b)) for !s.spsr.HasBits(s.spsrSPIF) { } return byte(s.spdr.Get()), nil } ================================================ FILE: src/machine/machine_atmega1280.go ================================================ //go:build avr && atmega1280 package machine import ( "device/avr" "runtime/interrupt" "runtime/volatile" ) const irq_USART0_RX = avr.IRQ_USART0_RX const ( portA Pin = iota * 8 portB portC portD portE portF portG portH portJ portK portL ) const ( PA0 = portA + 0 PA1 = portA + 1 PA2 = portA + 2 PA3 = portA + 3 PA4 = portA + 4 PA5 = portA + 5 PA6 = portA + 6 PA7 = portA + 7 PB0 = portB + 0 PB1 = portB + 1 PB2 = portB + 2 PB3 = portB + 3 PB4 = portB + 4 // peripherals: Timer2 channel A PB5 = portB + 5 // peripherals: Timer1 channel A PB6 = portB + 6 // peripherals: Timer1 channel B PB7 = portB + 7 // peripherals: Timer0 channel A PC0 = portC + 0 PC1 = portC + 1 PC2 = portC + 2 PC3 = portC + 3 PC4 = portC + 4 PC5 = portC + 5 PC6 = portC + 6 PC7 = portC + 7 PD0 = portD + 0 // peripherals: I2C0 SCL PD1 = portD + 1 // peripherals: I2C0 SDA PD2 = portD + 2 PD3 = portD + 3 PD7 = portD + 7 PE0 = portE + 0 PE1 = portE + 1 PE3 = portE + 3 // peripherals: Timer3 channel A PE4 = portE + 4 // peripherals: Timer3 channel B PE5 = portE + 5 // peripherals: Timer3 channel C PE6 = portE + 6 PF0 = portF + 0 PF1 = portF + 1 PF2 = portF + 2 PF3 = portF + 3 PF4 = portF + 4 PF5 = portF + 5 PF6 = portF + 6 PF7 = portF + 7 PG0 = portG + 0 PG1 = portG + 1 PG2 = portG + 2 PG5 = portG + 5 // peripherals: Timer0 channel B PH0 = portH + 0 PH1 = portH + 1 PH3 = portH + 3 // peripherals: Timer4 channel A PH4 = portH + 4 // peripherals: Timer4 channel B PH5 = portH + 5 // peripherals: Timer4 channel C PH6 = portH + 6 // peripherals: Timer0 channel B PJ0 = portJ + 0 PJ1 = portJ + 1 PK0 = portK + 0 PK1 = portK + 1 PK2 = portK + 2 PK3 = portK + 3 PK4 = portK + 4 PK5 = portK + 5 PK6 = portK + 6 PK7 = portK + 7 PL0 = portL + 0 PL1 = portL + 1 PL2 = portL + 2 PL3 = portL + 3 // peripherals: Timer5 channel A PL4 = portL + 4 // peripherals: Timer5 channel B PL5 = portL + 5 // peripherals: Timer5 channel C PL6 = portL + 6 PL7 = portL + 7 ) // getPortMask returns the PORTx register and mask for the pin. func (p Pin) getPortMask() (*volatile.Register8, uint8) { switch { case p >= PA0 && p <= PA7: return avr.PORTA, 1 << uint8(p-portA) case p >= PB0 && p <= PB7: return avr.PORTB, 1 << uint8(p-portB) case p >= PC0 && p <= PC7: return avr.PORTC, 1 << uint8(p-portC) case p >= PD0 && p <= PD7: return avr.PORTD, 1 << uint8(p-portD) case p >= PE0 && p <= PE6: return avr.PORTE, 1 << uint8(p-portE) case p >= PF0 && p <= PF7: return avr.PORTF, 1 << uint8(p-portF) case p >= PG0 && p <= PG5: return avr.PORTG, 1 << uint8(p-portG) case p >= PH0 && p <= PH6: return avr.PORTH, 1 << uint8(p-portH) case p >= PJ0 && p <= PJ1: return avr.PORTJ, 1 << uint8(p-portJ) case p >= PK0 && p <= PK7: return avr.PORTK, 1 << uint8(p-portK) case p >= PL0 && p <= PL7: return avr.PORTL, 1 << uint8(p-portL) default: return avr.PORTA, 255 } } // PWM is one PWM peripheral, which consists of a counter and two output // channels (that can be connected to two fixed pins). You can set the frequency // using SetPeriod, but only for all the channels in this PWM peripheral at // once. type PWM struct { num uint8 } var ( Timer0 = PWM{0} // 8 bit timer for PB7 and PG5 Timer1 = PWM{1} // 16 bit timer for PB5 and PB6 Timer2 = PWM{2} // 8 bit timer for PB4 and PH6 Timer3 = PWM{3} // 16 bit timer for PE3, PE4 and PE5 Timer4 = PWM{4} // 16 bit timer for PH3, PH4 and PH5 Timer5 = PWM{5} // 16 bit timer for PL3, PL4 and PL5 ) // Configure enables and configures this PWM. // // For the two 8 bit timers, there is only a limited number of periods // available, namely the CPU frequency divided by 256 and again divided by 1, 8, // 64, 256, or 1024. For a MCU running at 16MHz, this would be a period of 16µs, // 128µs, 1024µs, 4096µs, or 16384µs. func (pwm PWM) Configure(config PWMConfig) error { switch pwm.num { case 0, 2: // 8-bit timers (Timer/counter 0 and Timer/counter 2) // Calculate the timer prescaler. // While we could configure a flexible top, that would sacrifice one of // the PWM output compare registers and thus a PWM channel. I've chosen // to instead limit this timer to a fixed number of frequencies. var prescaler uint8 switch config.Period { case 0, (uint64(1e9) * 256 * 1) / uint64(CPUFrequency()): prescaler = 1 case (uint64(1e9) * 256 * 8) / uint64(CPUFrequency()): prescaler = 2 case (uint64(1e9) * 256 * 64) / uint64(CPUFrequency()): prescaler = 3 case (uint64(1e9) * 256 * 256) / uint64(CPUFrequency()): prescaler = 4 case (uint64(1e9) * 256 * 1024) / uint64(CPUFrequency()): prescaler = 5 default: return ErrPWMPeriodTooLong } if pwm.num == 0 { avr.TCCR0B.Set(prescaler) // Set the PWM mode to fast PWM (mode = 3). avr.TCCR0A.Set(avr.TCCR0A_WGM00 | avr.TCCR0A_WGM01) // monotonic timer is using the same time as PWM:0 // we must adjust internal settings of monotonic timer when PWM:0 settings changed adjustMonotonicTimer() } else { avr.TCCR2B.Set(prescaler) // Set the PWM mode to fast PWM (mode = 3). avr.TCCR2A.Set(avr.TCCR2A_WGM20 | avr.TCCR2A_WGM21) } case 1, 3, 4, 5: // The top value is the number of PWM ticks a PWM period takes. It is // initially picked assuming an unlimited counter top and no PWM // prescaler. var top uint64 if config.Period == 0 { // Use a top appropriate for LEDs. Picking a relatively low period // here (0xff) for consistency with the other timers. top = 0xff } else { // The formula below calculates the following formula, optimized: // top = period * (CPUFrequency() / 1e9) // By dividing the CPU frequency first (an operation that is easily // optimized away) the period has less chance of overflowing. top = config.Period * (uint64(CPUFrequency()) / 1000000) / 1000 } // The ideal PWM period may be larger than would fit in the PWM counter, // which is 16 bits (see maxTop). Therefore, try to make the PWM clock // speed lower with a prescaler to make the top value fit the maximum // top value. const maxTop = 0x10000 var prescalingTop uint8 switch { case top <= maxTop: prescalingTop = 3<<3 | 1 // no prescaling case top/8 <= maxTop: prescalingTop = 3<<3 | 2 // divide by 8 top /= 8 case top/64 <= maxTop: prescalingTop = 3<<3 | 3 // divide by 64 top /= 64 case top/256 <= maxTop: prescalingTop = 3<<3 | 4 // divide by 256 top /= 256 case top/1024 <= maxTop: prescalingTop = 3<<3 | 5 // divide by 1024 top /= 1024 default: return ErrPWMPeriodTooLong } // A top of 0x10000 is at 100% duty cycle. Subtract one because the // counter counts from 0, not 1 (avoiding an off-by-one). top -= 1 switch pwm.num { case 1: avr.TCCR1A.Set(avr.TCCR1A_WGM11) avr.TCCR1B.Set(prescalingTop) avr.ICR1H.Set(uint8(top >> 8)) avr.ICR1L.Set(uint8(top)) case 3: avr.TCCR3A.Set(avr.TCCR3A_WGM31) avr.TCCR3B.Set(prescalingTop) avr.ICR3H.Set(uint8(top >> 8)) avr.ICR3L.Set(uint8(top)) case 4: avr.TCCR4A.Set(avr.TCCR4A_WGM41) avr.TCCR4B.Set(prescalingTop) avr.ICR4H.Set(uint8(top >> 8)) avr.ICR4L.Set(uint8(top)) case 5: avr.TCCR5A.Set(avr.TCCR5A_WGM51) avr.TCCR5B.Set(prescalingTop) avr.ICR5H.Set(uint8(top >> 8)) avr.ICR5L.Set(uint8(top)) } } return nil } // SetPeriod updates the period of this PWM peripheral. // To set a particular frequency, use the following formula: // // period = 1e9 / frequency // // If you use a period of 0, a period that works well for LEDs will be picked. // // SetPeriod will not change the prescaler, but also won't change the current // value in any of the channels. This means that you may need to update the // value for the particular channel. // // Note that you cannot pick any arbitrary period after the PWM peripheral has // been configured. If you want to switch between frequencies, pick the lowest // frequency (longest period) once when calling Configure and adjust the // frequency here as needed. func (pwm PWM) SetPeriod(period uint64) error { if pwm.num == 0 || pwm.num == 2 { return ErrPWMPeriodTooLong // TODO better error message } // The top value is the number of PWM ticks a PWM period takes. It is // initially picked assuming an unlimited counter top and no PWM // prescaler. var top uint64 if period == 0 { // Use a top appropriate for LEDs. Picking a relatively low period // here (0xff) for consistency with the other timers. top = 0xff } else { // The formula below calculates the following formula, optimized: // top = period * (CPUFrequency() / 1e9) // By dividing the CPU frequency first (an operation that is easily // optimized away) the period has less chance of overflowing. top = period * (uint64(CPUFrequency()) / 1000000) / 1000 } var prescaler uint8 switch pwm.num { case 1: prescaler = avr.TCCR1B.Get() & 0x7 case 3: prescaler = avr.TCCR3B.Get() & 0x7 case 4: prescaler = avr.TCCR4B.Get() & 0x7 case 5: prescaler = avr.TCCR5B.Get() & 0x7 } switch prescaler { case 1: top /= 1 case 2: top /= 8 case 3: top /= 64 case 4: top /= 256 case 5: top /= 1024 } // A top of 0x10000 is at 100% duty cycle. Subtract one because the counter // counts from 0, not 1 (avoiding an off-by-one). top -= 1 if top > 0xffff { return ErrPWMPeriodTooLong } switch pwm.num { case 1: // Warning: this change is not atomic! avr.ICR1H.Set(uint8(top >> 8)) avr.ICR1L.Set(uint8(top)) // ... and because of that, set the counter back to zero to avoid most of // the effects of this non-atomicity. avr.TCNT1H.Set(0) avr.TCNT1L.Set(0) case 3: // Warning: this change is not atomic! avr.ICR3H.Set(uint8(top >> 8)) avr.ICR3L.Set(uint8(top)) // ... and because of that, set the counter back to zero to avoid most of // the effects of this non-atomicity. avr.TCNT3H.Set(0) avr.TCNT3L.Set(0) case 4: // Warning: this change is not atomic! avr.ICR4H.Set(uint8(top >> 8)) avr.ICR4L.Set(uint8(top)) // ... and because of that, set the counter back to zero to avoid most of // the effects of this non-atomicity. avr.TCNT4H.Set(0) avr.TCNT4L.Set(0) case 5: // Warning: this change is not atomic! avr.ICR5H.Set(uint8(top >> 8)) avr.ICR5L.Set(uint8(top)) // ... and because of that, set the counter back to zero to avoid most of // the effects of this non-atomicity. avr.TCNT5H.Set(0) avr.TCNT5L.Set(0) } return nil } // Top returns the current counter top, for use in duty cycle calculation. It // will only change with a call to Configure or SetPeriod, otherwise it is // constant. // // The value returned here is hardware dependent. In general, it's best to treat // it as an opaque value that can be divided by some number and passed to Set // (see Set documentation for more information). func (pwm PWM) Top() uint32 { switch pwm.num { case 1: // Timer 1 has a configurable top value. low := avr.ICR1L.Get() high := avr.ICR1H.Get() return uint32(high)<<8 | uint32(low) + 1 case 3: // Timer 3 has a configurable top value. low := avr.ICR3L.Get() high := avr.ICR3H.Get() return uint32(high)<<8 | uint32(low) + 1 case 4: // Timer 4 has a configurable top value. low := avr.ICR4L.Get() high := avr.ICR4H.Get() return uint32(high)<<8 | uint32(low) + 1 case 5: // Timer 5 has a configurable top value. low := avr.ICR5L.Get() high := avr.ICR5H.Get() return uint32(high)<<8 | uint32(low) + 1 } // Other timers go from 0 to 0xff (0x100 or 256 in total). return 256 } // Counter returns the current counter value of the timer in this PWM // peripheral. It may be useful for debugging. func (pwm PWM) Counter() uint32 { switch pwm.num { case 0: return uint32(avr.TCNT0.Get()) case 1: mask := interrupt.Disable() low := avr.TCNT1L.Get() high := avr.TCNT1H.Get() interrupt.Restore(mask) return uint32(high)<<8 | uint32(low) case 2: return uint32(avr.TCNT2.Get()) case 3: mask := interrupt.Disable() low := avr.TCNT3L.Get() high := avr.TCNT3H.Get() interrupt.Restore(mask) return uint32(high)<<8 | uint32(low) case 4: mask := interrupt.Disable() low := avr.TCNT4L.Get() high := avr.TCNT4H.Get() interrupt.Restore(mask) return uint32(high)<<8 | uint32(low) case 5: mask := interrupt.Disable() low := avr.TCNT5L.Get() high := avr.TCNT5H.Get() interrupt.Restore(mask) return uint32(high)<<8 | uint32(low) } // Unknown PWM. return 0 } // Period returns the used PWM period in nanoseconds. It might deviate slightly // from the configured period due to rounding. func (pwm PWM) Period() uint64 { var prescaler uint8 switch pwm.num { case 0: prescaler = avr.TCCR0B.Get() & 0x7 case 1: prescaler = avr.TCCR1B.Get() & 0x7 case 2: prescaler = avr.TCCR2B.Get() & 0x7 case 3: prescaler = avr.TCCR3B.Get() & 0x7 case 4: prescaler = avr.TCCR4B.Get() & 0x7 case 5: prescaler = avr.TCCR5B.Get() & 0x7 } top := uint64(pwm.Top()) switch prescaler { case 1: // prescaler 1 return 1 * top * 1000 / uint64(CPUFrequency()/1e6) case 2: // prescaler 8 return 8 * top * 1000 / uint64(CPUFrequency()/1e6) case 3: // prescaler 64 return 64 * top * 1000 / uint64(CPUFrequency()/1e6) case 4: // prescaler 256 return 256 * top * 1000 / uint64(CPUFrequency()/1e6) case 5: // prescaler 1024 return 1024 * top * 1000 / uint64(CPUFrequency()/1e6) default: // unknown clock source return 0 } } // Channel returns a PWM channel for the given pin. func (pwm PWM) Channel(pin Pin) (uint8, error) { pin.Configure(PinConfig{Mode: PinOutput}) pin.Low() switch pwm.num { case 0: switch pin { case PB7: // channel A avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) return 0, nil case PG5: // channel B avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) return 1, nil } case 1: switch pin { case PB5: // channel A avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1) return 0, nil case PB6: // channel B avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1) return 1, nil } case 2: switch pin { case PB4: // channel A avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1) return 0, nil case PH6: // channel B avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1) return 1, nil } case 3: switch pin { case PE3: // channel A avr.TCCR3A.SetBits(avr.TCCR3A_COM3A1) return 0, nil case PE4: //channel B avr.TCCR3A.SetBits(avr.TCCR3A_COM3B1) return 1, nil case PE5: //channel C avr.TCCR3A.SetBits(avr.TCCR3A_COM3C1) return 2, nil } case 4: switch pin { case PH3: // channel A avr.TCCR4A.SetBits(avr.TCCR4A_COM4A1) return 0, nil case PH4: //channel B avr.TCCR4A.SetBits(avr.TCCR4A_COM4B1) return 1, nil case PH5: //channel C avr.TCCR4A.SetBits(avr.TCCR4A_COM4C1) return 2, nil } case 5: switch pin { case PL3: // channel A avr.TCCR5A.SetBits(avr.TCCR5A_COM5A1) return 0, nil case PL4: //channel B avr.TCCR5A.SetBits(avr.TCCR5A_COM5B1) return 1, nil case PL5: //channel C avr.TCCR5A.SetBits(avr.TCCR5A_COM5C1) return 2, nil } } return 0, ErrInvalidOutputPin } // SetInverting sets whether to invert the output of this channel. // Without inverting, a 25% duty cycle would mean the output is high for 25% of // the time and low for the rest. Inverting flips the output as if a NOT gate // was placed at the output, meaning that the output would be 25% low and 75% // high with a duty cycle of 25%. // // Note: the invert state may not be applied on the AVR until the next call to // ch.Set(). func (pwm PWM) SetInverting(channel uint8, inverting bool) { switch pwm.num { case 0: switch channel { case 0: // channel A, PB7 if inverting { avr.PORTB.SetBits(1 << 7) // PB7 high avr.TCCR0A.SetBits(avr.TCCR0A_COM0A0) } else { avr.PORTB.ClearBits(1 << 7) // PB7 low avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A0) } case 1: // channel B, PG5 if inverting { avr.PORTG.SetBits(1 << 5) // PG5 high avr.TCCR0A.SetBits(avr.TCCR0A_COM0B0) } else { avr.PORTG.ClearBits(1 << 5) // PG5 low avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B0) } } case 1: // Note: the COM1A0/COM1B0 bit is not set with the configuration below. // It will be set the following call to Set(), however. switch channel { case 0: // channel A, PB5 if inverting { avr.PORTB.SetBits(1 << 5) // PB5 high } else { avr.PORTB.ClearBits(1 << 5) // PB5 low } case 1: // channel B, PB6 if inverting { avr.PORTB.SetBits(1 << 6) // PB6 high } else { avr.PORTB.ClearBits(1 << 6) // PB6 low } } case 2: switch channel { case 0: // channel A, PB4 if inverting { avr.PORTB.SetBits(1 << 4) // PB4 high avr.TCCR2A.SetBits(avr.TCCR2A_COM2A0) } else { avr.PORTB.ClearBits(1 << 4) // PB4 low avr.TCCR2A.ClearBits(avr.TCCR2A_COM2A0) } case 1: // channel B, PH6 if inverting { avr.PORTH.SetBits(1 << 6) // PH6 high avr.TCCR2A.SetBits(avr.TCCR2A_COM2B0) } else { avr.PORTH.ClearBits(1 << 6) // PH6 low avr.TCCR2A.ClearBits(avr.TCCR2A_COM2B0) } } case 3: // Note: the COM3A0/COM3B0 bit is not set with the configuration below. // It will be set the following call to Set(), however. switch channel { case 0: // channel A, PE3 if inverting { avr.PORTE.SetBits(1 << 3) // PE3 high } else { avr.PORTE.ClearBits(1 << 3) // PE3 low } case 1: // channel B, PE4 if inverting { avr.PORTE.SetBits(1 << 4) // PE4 high } else { avr.PORTE.ClearBits(1 << 4) // PE4 low } case 2: // channel C, PE5 if inverting { avr.PORTE.SetBits(1 << 5) // PE4 high } else { avr.PORTE.ClearBits(1 << 5) // PE4 low } } case 4: // Note: the COM3A0/COM3B0 bit is not set with the configuration below. // It will be set the following call to Set(), however. switch channel { case 0: // channel A, PH3 if inverting { avr.PORTH.SetBits(1 << 3) // PH3 high } else { avr.PORTH.ClearBits(1 << 3) // PH3 low } case 1: // channel B, PH4 if inverting { avr.PORTH.SetBits(1 << 4) // PH4 high } else { avr.PORTH.ClearBits(1 << 4) // PH4 low } case 2: // channel C, PH5 if inverting { avr.PORTH.SetBits(1 << 5) // PH4 high } else { avr.PORTH.ClearBits(1 << 5) // PH4 low } } case 5: // Note: the COM3A0/COM3B0 bit is not set with the configuration below. // It will be set the following call to Set(), however. switch channel { case 0: // channel A, PL3 if inverting { avr.PORTL.SetBits(1 << 3) // PL3 high } else { avr.PORTL.ClearBits(1 << 3) // PL3 low } case 1: // channel B, PL4 if inverting { avr.PORTL.SetBits(1 << 4) // PL4 high } else { avr.PORTL.ClearBits(1 << 4) // PL4 low } case 2: // channel C, PH5 if inverting { avr.PORTL.SetBits(1 << 5) // PL4 high } else { avr.PORTL.ClearBits(1 << 5) // PL4 low } } } } // Set updates the channel value. This is used to control the channel duty // cycle, in other words the fraction of time the channel output is high (or low // when inverted). For example, to set it to a 25% duty cycle, use: // // pwm.Set(channel, pwm.Top() / 4) // // pwm.Set(channel, 0) will set the output to low and pwm.Set(channel, // pwm.Top()) will set the output to high, assuming the output isn't inverted. func (pwm PWM) Set(channel uint8, value uint32) { switch pwm.num { case 0: value := uint16(value) switch channel { case 0: // channel A if value == 0 { avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A1) } else { avr.OCR0A.Set(uint8(value - 1)) avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) } case 1: // channel B if value == 0 { avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B1) } else { avr.OCR0B.Set(uint8(value) - 1) avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) } } // monotonic timer is using the same time as PWM:0 // we must adjust internal settings of monotonic timer when PWM:0 settings changed adjustMonotonicTimer() case 1: mask := interrupt.Disable() switch channel { case 0: // channel A, PB5 if value == 0 { avr.TCCR1A.ClearBits(avr.TCCR1A_COM1A1 | avr.TCCR1A_COM1A0) } else { value := uint16(value) - 1 // yes, this is safe (it relies on underflow) avr.OCR1AH.Set(uint8(value >> 8)) avr.OCR1AL.Set(uint8(value)) if avr.PORTB.HasBits(1 << 5) { // is PB1 high? // Yes, set the inverting bit. avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1 | avr.TCCR1A_COM1A0) } else { // No, output is non-inverting. avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1) } } case 1: // channel B, PB6 if value == 0 { avr.TCCR1A.ClearBits(avr.TCCR1A_COM1B1 | avr.TCCR1A_COM1B0) } else { value := uint16(value) - 1 // yes, this is safe (it relies on underflow) avr.OCR1BH.Set(uint8(value >> 8)) avr.OCR1BL.Set(uint8(value)) if avr.PORTB.HasBits(1 << 6) { // is PB6 high? // Yes, set the inverting bit. avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1 | avr.TCCR1A_COM1B0) } else { // No, output is non-inverting. avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1) } } } interrupt.Restore(mask) case 2: value := uint16(value) switch channel { case 0: // channel A if value == 0 { avr.TCCR2A.ClearBits(avr.TCCR2A_COM2A1) } else { avr.OCR2A.Set(uint8(value - 1)) avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1) } case 1: // channel B if value == 0 { avr.TCCR2A.ClearBits(avr.TCCR2A_COM2B1) } else { avr.OCR2B.Set(uint8(value - 1)) avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1) } } case 3: mask := interrupt.Disable() switch channel { case 0: // channel A, PE3 if value == 0 { avr.TCCR3A.ClearBits(avr.TCCR3A_COM3A1 | avr.TCCR3A_COM3A0) } else { value := uint16(value) - 1 // yes, this is safe (it relies on underflow) avr.OCR3AH.Set(uint8(value >> 8)) avr.OCR3AL.Set(uint8(value)) if avr.PORTE.HasBits(1 << 3) { // is PE3 high? // Yes, set the inverting bit. avr.TCCR3A.SetBits(avr.TCCR3A_COM3A1 | avr.TCCR3A_COM3A0) } else { // No, output is non-inverting. avr.TCCR3A.SetBits(avr.TCCR3A_COM3A1) } } case 1: // channel B, PE4 if value == 0 { avr.TCCR3A.ClearBits(avr.TCCR3A_COM3B1 | avr.TCCR3A_COM3B0) } else { value := uint16(value) - 1 // yes, this is safe (it relies on underflow) avr.OCR3BH.Set(uint8(value >> 8)) avr.OCR3BL.Set(uint8(value)) if avr.PORTE.HasBits(1 << 4) { // is PE4 high? // Yes, set the inverting bit. avr.TCCR3A.SetBits(avr.TCCR3A_COM3B1 | avr.TCCR3A_COM3B0) } else { // No, output is non-inverting. avr.TCCR3A.SetBits(avr.TCCR3A_COM3B1) } } case 2: // channel C, PE5 if value == 0 { avr.TCCR3A.ClearBits(avr.TCCR3A_COM3C1 | avr.TCCR3A_COM3C0) } else { value := uint16(value) - 1 // yes, this is safe (it relies on underflow) avr.OCR3CH.Set(uint8(value >> 8)) avr.OCR3CL.Set(uint8(value)) if avr.PORTE.HasBits(1 << 5) { // is PE5 high? // Yes, set the inverting bit. avr.TCCR3A.SetBits(avr.TCCR3A_COM3C1 | avr.TCCR3A_COM3C0) } else { // No, output is non-inverting. avr.TCCR3A.SetBits(avr.TCCR3A_COM3C1) } } } interrupt.Restore(mask) case 4: mask := interrupt.Disable() switch channel { case 0: // channel A, PH3 if value == 0 { avr.TCCR4A.ClearBits(avr.TCCR4A_COM4A1 | avr.TCCR4A_COM4A0) } else { value := uint16(value) - 1 // yes, this is safe (it relies on underflow) avr.OCR4AH.Set(uint8(value >> 8)) avr.OCR4AL.Set(uint8(value)) if avr.PORTH.HasBits(1 << 3) { // is PH3 high? // Yes, set the inverting bit. avr.TCCR4A.SetBits(avr.TCCR4A_COM4A1 | avr.TCCR4A_COM4A0) } else { // No, output is non-inverting. avr.TCCR4A.SetBits(avr.TCCR4A_COM4A1) } } case 1: // channel B, PH4 if value == 0 { avr.TCCR4A.ClearBits(avr.TCCR4A_COM4B1 | avr.TCCR4A_COM4B0) } else { value := uint16(value) - 1 // yes, this is safe (it relies on underflow) avr.OCR4BH.Set(uint8(value >> 8)) avr.OCR4BL.Set(uint8(value)) if avr.PORTH.HasBits(1 << 4) { // is PH4 high? // Yes, set the inverting bit. avr.TCCR4A.SetBits(avr.TCCR4A_COM4B1 | avr.TCCR4A_COM4B0) } else { // No, output is non-inverting. avr.TCCR4A.SetBits(avr.TCCR4A_COM4B1) } } case 2: // channel C, PH5 if value == 0 { avr.TCCR4A.ClearBits(avr.TCCR4A_COM4C1 | avr.TCCR4A_COM4C0) } else { value := uint16(value) - 1 // yes, this is safe (it relies on underflow) avr.OCR4CH.Set(uint8(value >> 8)) avr.OCR4CL.Set(uint8(value)) if avr.PORTH.HasBits(1 << 5) { // is PH5 high? // Yes, set the inverting bit. avr.TCCR4A.SetBits(avr.TCCR4A_COM4C1 | avr.TCCR4A_COM4C0) } else { // No, output is non-inverting. avr.TCCR4A.SetBits(avr.TCCR4A_COM4C1) } } } interrupt.Restore(mask) case 5: mask := interrupt.Disable() switch channel { case 0: // channel A, PL3 if value == 0 { avr.TCCR5A.ClearBits(avr.TCCR5A_COM5A1 | avr.TCCR5A_COM5A0) } else { value := uint16(value) - 1 // yes, this is safe (it relies on underflow) avr.OCR5AH.Set(uint8(value >> 8)) avr.OCR5AL.Set(uint8(value)) if avr.PORTL.HasBits(1 << 3) { // is PL3 high? // Yes, set the inverting bit. avr.TCCR5A.SetBits(avr.TCCR5A_COM5A1 | avr.TCCR5A_COM5A0) } else { // No, output is non-inverting. avr.TCCR5A.SetBits(avr.TCCR5A_COM5A1) } } case 1: // channel B, PL4 if value == 0 { avr.TCCR5A.ClearBits(avr.TCCR5A_COM5B1 | avr.TCCR5A_COM5B0) } else { value := uint16(value) - 1 // yes, this is safe (it relies on underflow) avr.OCR5BH.Set(uint8(value >> 8)) avr.OCR5BL.Set(uint8(value)) if avr.PORTL.HasBits(1 << 4) { // is PL4 high? // Yes, set the inverting bit. avr.TCCR5A.SetBits(avr.TCCR5A_COM5B1 | avr.TCCR5A_COM5B0) } else { // No, output is non-inverting. avr.TCCR5A.SetBits(avr.TCCR5A_COM5B1) } } case 2: // channel C, PL5 if value == 0 { avr.TCCR5A.ClearBits(avr.TCCR5A_COM5C1 | avr.TCCR5A_COM5C0) } else { value := uint16(value) - 1 // yes, this is safe (it relies on underflow) avr.OCR5CH.Set(uint8(value >> 8)) avr.OCR5CL.Set(uint8(value)) if avr.PORTL.HasBits(1 << 5) { // is PL5 high? // Yes, set the inverting bit. avr.TCCR5A.SetBits(avr.TCCR5A_COM5C1 | avr.TCCR5A_COM5C0) } else { // No, output is non-inverting. avr.TCCR5A.SetBits(avr.TCCR5A_COM5C1) } } } interrupt.Restore(mask) } } // SPI configuration var SPI0 = &SPI{ spcr: avr.SPCR, spdr: avr.SPDR, spsr: avr.SPSR, sck: PB1, sdo: PB2, sdi: PB3, cs: PB0} ================================================ FILE: src/machine/machine_atmega1284p.go ================================================ //go:build avr && atmega1284p package machine import ( "device/avr" "runtime/volatile" ) const irq_USART0_RX = avr.IRQ_USART0_RX // Return the current CPU frequency in hertz. func CPUFrequency() uint32 { return 20000000 } const ( portA Pin = iota * 8 portB portC portD ) const ( PA0 = portA + 0 PA1 = portA + 1 PA2 = portA + 2 PA3 = portA + 3 PA4 = portA + 4 PA5 = portA + 5 PA6 = portA + 6 PA7 = portA + 7 PB0 = portB + 0 PB1 = portB + 1 PB2 = portB + 2 PB3 = portB + 3 PB4 = portB + 4 PB5 = portB + 5 PB6 = portB + 6 PB7 = portB + 7 PC0 = portC + 0 PC1 = portC + 1 PC2 = portC + 2 PC3 = portC + 3 PC4 = portC + 4 PC5 = portC + 5 PC6 = portC + 6 PC7 = portC + 7 PD0 = portD + 0 PD1 = portD + 1 PD2 = portD + 2 PD3 = portD + 3 PD4 = portD + 4 PD5 = portD + 5 PD6 = portD + 6 PD7 = portD + 7 ) // getPortMask returns the PORTx register and mask for the pin. func (p Pin) getPortMask() (*volatile.Register8, uint8) { switch { case p >= PA0 && p <= PA7: return avr.PORTA, 1 << uint8(p-portA) case p >= PB0 && p <= PB7: return avr.PORTB, 1 << uint8(p-portB) case p >= PC0 && p <= PC7: return avr.PORTC, 1 << uint8(p-portC) default: return avr.PORTD, 1 << uint8(p-portD) } } // SPI configuration var SPI0 = &SPI{ spcr: avr.SPCR, spsr: avr.SPSR, spdr: avr.SPDR, sck: PB7, sdo: PB5, sdi: PB6, cs: PB4} ================================================ FILE: src/machine/machine_atmega2560.go ================================================ //go:build avr && atmega2560 package machine import ( "device/avr" "runtime/volatile" ) const irq_USART0_RX = avr.IRQ_USART0_RX const irq_USART1_RX = avr.IRQ_USART1_RX const irq_USART2_RX = avr.IRQ_USART2_RX const irq_USART3_RX = avr.IRQ_USART3_RX const ( portA Pin = iota * 8 portB portC portD portE portF portG portH portJ portK portL ) const ( PA0 = portA + 0 PA1 = portA + 1 PA2 = portA + 2 PA3 = portA + 3 PA4 = portA + 4 PA5 = portA + 5 PA6 = portA + 6 PA7 = portA + 7 PB0 = portB + 0 PB1 = portB + 1 PB2 = portB + 2 PB3 = portB + 3 PB4 = portB + 4 PB5 = portB + 5 PB6 = portB + 6 PB7 = portB + 7 PC0 = portC + 0 PC1 = portC + 1 PC2 = portC + 2 PC3 = portC + 3 PC4 = portC + 4 PC5 = portC + 5 PC6 = portC + 6 PC7 = portC + 7 PD0 = portD + 0 PD1 = portD + 1 PD2 = portD + 2 PD3 = portD + 3 PD7 = portD + 7 PE0 = portE + 0 PE1 = portE + 1 PE3 = portE + 3 PE4 = portE + 4 PE5 = portE + 5 PE6 = portE + 6 PF0 = portF + 0 PF1 = portF + 1 PF2 = portF + 2 PF3 = portF + 3 PF4 = portF + 4 PF5 = portF + 5 PF6 = portF + 6 PF7 = portF + 7 PG0 = portG + 0 PG1 = portG + 1 PG2 = portG + 2 PG5 = portG + 5 PH0 = portH + 0 PH1 = portH + 1 PH3 = portH + 3 PH4 = portH + 4 PH5 = portH + 5 PH6 = portH + 6 PJ0 = portJ + 0 PJ1 = portJ + 1 PK0 = portK + 0 PK1 = portK + 1 PK2 = portK + 2 PK3 = portK + 3 PK4 = portK + 4 PK5 = portK + 5 PK6 = portK + 6 PK7 = portK + 7 PL0 = portL + 0 PL1 = portL + 1 PL2 = portL + 2 PL3 = portL + 3 PL4 = portL + 4 PL5 = portL + 5 PL6 = portL + 6 PL7 = portL + 7 ) // getPortMask returns the PORTx register and mask for the pin. func (p Pin) getPortMask() (*volatile.Register8, uint8) { switch { case p >= PA0 && p <= PA7: return avr.PORTA, 1 << uint8(p-portA) case p >= PB0 && p <= PB7: return avr.PORTB, 1 << uint8(p-portB) case p >= PC0 && p <= PC7: return avr.PORTC, 1 << uint8(p-portC) case p >= PD0 && p <= PD7: return avr.PORTD, 1 << uint8(p-portD) case p >= PE0 && p <= PE6: return avr.PORTE, 1 << uint8(p-portE) case p >= PF0 && p <= PF7: return avr.PORTF, 1 << uint8(p-portF) case p >= PG0 && p <= PG5: return avr.PORTG, 1 << uint8(p-portG) case p >= PH0 && p <= PH6: return avr.PORTH, 1 << uint8(p-portH) case p >= PJ0 && p <= PJ1: return avr.PORTJ, 1 << uint8(p-portJ) case p >= PK0 && p <= PK7: return avr.PORTK, 1 << uint8(p-portK) case p >= PL0 && p <= PL7: return avr.PORTL, 1 << uint8(p-portL) default: return avr.PORTA, 255 } } // SPI configuration var SPI0 = &SPI{ spcr: avr.SPCR, spdr: avr.SPDR, spsr: avr.SPSR, sck: PB1, sdo: PB2, sdi: PB3, cs: PB0} ================================================ FILE: src/machine/machine_atmega328.go ================================================ //go:build avr && (atmega328p || atmega328pb) package machine import ( "device/avr" "runtime/interrupt" "runtime/volatile" ) // PWM is one PWM peripheral, which consists of a counter and two output // channels (that can be connected to two fixed pins). You can set the frequency // using SetPeriod, but only for all the channels in this PWM peripheral at // once. type PWM struct { num uint8 } var ( Timer0 = PWM{0} // 8 bit timer for PD5 and PD6 Timer1 = PWM{1} // 16 bit timer for PB1 and PB2 Timer2 = PWM{2} // 8 bit timer for PB3 and PD3 ) // Configure enables and configures this PWM. // // For the two 8 bit timers, there is only a limited number of periods // available, namely the CPU frequency divided by 256 and again divided by 1, 8, // 64, 256, or 1024. For a MCU running at 16MHz, this would be a period of 16µs, // 128µs, 1024µs, 4096µs, or 16384µs. func (pwm PWM) Configure(config PWMConfig) error { switch pwm.num { case 0, 2: // 8-bit timers (Timer/counter 0 and Timer/counter 2) // Calculate the timer prescaler. // While we could configure a flexible top, that would sacrifice one of // the PWM output compare registers and thus a PWM channel. I've chosen // to instead limit this timer to a fixed number of frequencies. var prescaler uint8 switch config.Period { case 0, (uint64(1e9) * 256 * 1) / uint64(CPUFrequency()): prescaler = 1 case (uint64(1e9) * 256 * 8) / uint64(CPUFrequency()): prescaler = 2 case (uint64(1e9) * 256 * 64) / uint64(CPUFrequency()): prescaler = 3 case (uint64(1e9) * 256 * 256) / uint64(CPUFrequency()): prescaler = 4 case (uint64(1e9) * 256 * 1024) / uint64(CPUFrequency()): prescaler = 5 default: return ErrPWMPeriodTooLong } if pwm.num == 0 { avr.TCCR0B.Set(prescaler) // Set the PWM mode to fast PWM (mode = 3). avr.TCCR0A.Set(avr.TCCR0A_WGM00 | avr.TCCR0A_WGM01) // monotonic timer is using the same time as PWM:0 // we must adjust internal settings of monotonic timer when PWM:0 settings changed adjustMonotonicTimer() } else { avr.TCCR2B.Set(prescaler) // Set the PWM mode to fast PWM (mode = 3). avr.TCCR2A.Set(avr.TCCR2A_WGM20 | avr.TCCR2A_WGM21) } case 1: // Timer/counter 1 // The top value is the number of PWM ticks a PWM period takes. It is // initially picked assuming an unlimited counter top and no PWM // prescaler. var top uint64 if config.Period == 0 { // Use a top appropriate for LEDs. Picking a relatively low period // here (0xff) for consistency with the other timers. top = 0xff } else { // The formula below calculates the following formula, optimized: // top = period * (CPUFrequency() / 1e9) // By dividing the CPU frequency first (an operation that is easily // optimized away) the period has less chance of overflowing. top = config.Period * (uint64(CPUFrequency()) / 1000000) / 1000 } avr.TCCR1A.Set(avr.TCCR1A_WGM11) // The ideal PWM period may be larger than would fit in the PWM counter, // which is 16 bits (see maxTop). Therefore, try to make the PWM clock // speed lower with a prescaler to make the top value fit the maximum // top value. const maxTop = 0x10000 switch { case top <= maxTop: avr.TCCR1B.Set(3<<3 | 1) // no prescaling case top/8 <= maxTop: avr.TCCR1B.Set(3<<3 | 2) // divide by 8 top /= 8 case top/64 <= maxTop: avr.TCCR1B.Set(3<<3 | 3) // divide by 64 top /= 64 case top/256 <= maxTop: avr.TCCR1B.Set(3<<3 | 4) // divide by 256 top /= 256 case top/1024 <= maxTop: avr.TCCR1B.Set(3<<3 | 5) // divide by 1024 top /= 1024 default: return ErrPWMPeriodTooLong } // A top of 0x10000 is at 100% duty cycle. Subtract one because the // counter counts from 0, not 1 (avoiding an off-by-one). top -= 1 avr.ICR1H.Set(uint8(top >> 8)) avr.ICR1L.Set(uint8(top)) } return nil } // SetPeriod updates the period of this PWM peripheral. // To set a particular frequency, use the following formula: // // period = 1e9 / frequency // // If you use a period of 0, a period that works well for LEDs will be picked. // // SetPeriod will not change the prescaler, but also won't change the current // value in any of the channels. This means that you may need to update the // value for the particular channel. // // Note that you cannot pick any arbitrary period after the PWM peripheral has // been configured. If you want to switch between frequencies, pick the lowest // frequency (longest period) once when calling Configure and adjust the // frequency here as needed. func (pwm PWM) SetPeriod(period uint64) error { if pwm.num != 1 { return ErrPWMPeriodTooLong // TODO better error message } // The top value is the number of PWM ticks a PWM period takes. It is // initially picked assuming an unlimited counter top and no PWM // prescaler. var top uint64 if period == 0 { // Use a top appropriate for LEDs. Picking a relatively low period // here (0xff) for consistency with the other timers. top = 0xff } else { // The formula below calculates the following formula, optimized: // top = period * (CPUFrequency() / 1e9) // By dividing the CPU frequency first (an operation that is easily // optimized away) the period has less chance of overflowing. top = period * (uint64(CPUFrequency()) / 1000000) / 1000 } prescaler := avr.TCCR1B.Get() & 0x7 switch prescaler { case 1: top /= 1 case 2: top /= 8 case 3: top /= 64 case 4: top /= 256 case 5: top /= 1024 } // A top of 0x10000 is at 100% duty cycle. Subtract one because the counter // counts from 0, not 1 (avoiding an off-by-one). top -= 1 if top > 0xffff { return ErrPWMPeriodTooLong } // Warning: this change is not atomic! avr.ICR1H.Set(uint8(top >> 8)) avr.ICR1L.Set(uint8(top)) // ... and because of that, set the counter back to zero to avoid most of // the effects of this non-atomicity. avr.TCNT1H.Set(0) avr.TCNT1L.Set(0) return nil } // Top returns the current counter top, for use in duty cycle calculation. It // will only change with a call to Configure or SetPeriod, otherwise it is // constant. // // The value returned here is hardware dependent. In general, it's best to treat // it as an opaque value that can be divided by some number and passed to Set // (see Set documentation for more information). func (pwm PWM) Top() uint32 { if pwm.num == 1 { // Timer 1 has a configurable top value. low := avr.ICR1L.Get() high := avr.ICR1H.Get() return uint32(high)<<8 | uint32(low) + 1 } // Other timers go from 0 to 0xff (0x100 or 256 in total). return 256 } // Counter returns the current counter value of the timer in this PWM // peripheral. It may be useful for debugging. func (pwm PWM) Counter() uint32 { switch pwm.num { case 0: return uint32(avr.TCNT0.Get()) case 1: mask := interrupt.Disable() low := avr.TCNT1L.Get() high := avr.TCNT1H.Get() interrupt.Restore(mask) return uint32(high)<<8 | uint32(low) case 2: return uint32(avr.TCNT2.Get()) } // Unknown PWM. return 0 } // Period returns the used PWM period in nanoseconds. It might deviate slightly // from the configured period due to rounding. func (pwm PWM) Period() uint64 { var prescaler uint8 switch pwm.num { case 0: prescaler = avr.TCCR0B.Get() & 0x7 case 1: prescaler = avr.TCCR1B.Get() & 0x7 case 2: prescaler = avr.TCCR2B.Get() & 0x7 } top := uint64(pwm.Top()) switch prescaler { case 1: // prescaler 1 return 1 * top * 1000 / uint64(CPUFrequency()/1e6) case 2: // prescaler 8 return 8 * top * 1000 / uint64(CPUFrequency()/1e6) case 3: // prescaler 64 return 64 * top * 1000 / uint64(CPUFrequency()/1e6) case 4: // prescaler 256 return 256 * top * 1000 / uint64(CPUFrequency()/1e6) case 5: // prescaler 1024 return 1024 * top * 1000 / uint64(CPUFrequency()/1e6) default: // unknown clock source return 0 } } // Channel returns a PWM channel for the given pin. func (pwm PWM) Channel(pin Pin) (uint8, error) { pin.Configure(PinConfig{Mode: PinOutput}) pin.Low() switch pwm.num { case 0: switch pin { case PD6: // channel A avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) return 0, nil case PD5: // channel B avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) return 1, nil } case 1: switch pin { case PB1: // channel A avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1) return 0, nil case PB2: // channel B avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1) return 1, nil } case 2: switch pin { case PB3: // channel A avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1) return 0, nil case PD3: // channel B avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1) return 1, nil } } return 0, ErrInvalidOutputPin } // SetInverting sets whether to invert the output of this channel. // Without inverting, a 25% duty cycle would mean the output is high for 25% of // the time and low for the rest. Inverting flips the output as if a NOT gate // was placed at the output, meaning that the output would be 25% low and 75% // high with a duty cycle of 25%. // // Note: the invert state may not be applied on the AVR until the next call to // ch.Set(). func (pwm PWM) SetInverting(channel uint8, inverting bool) { switch pwm.num { case 0: switch channel { case 0: // channel A if inverting { avr.PORTB.SetBits(1 << 6) // PB6 high avr.TCCR0A.SetBits(avr.TCCR0A_COM0A0) } else { avr.PORTB.ClearBits(1 << 6) // PB6 low avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A0) } case 1: // channel B if inverting { avr.PORTB.SetBits(1 << 5) // PB5 high avr.TCCR0A.SetBits(avr.TCCR0A_COM0B0) } else { avr.PORTB.ClearBits(1 << 5) // PB5 low avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B0) } } case 1: // Note: the COM1A0/COM1B0 bit is not set with the configuration below. // It will be set the following call to Set(), however. switch channel { case 0: // channel A, PB1 if inverting { avr.PORTB.SetBits(1 << 1) // PB1 high } else { avr.PORTB.ClearBits(1 << 1) // PB1 low } case 1: // channel B, PB2 if inverting { avr.PORTB.SetBits(1 << 2) // PB2 high } else { avr.PORTB.ClearBits(1 << 2) // PB2 low } } case 2: switch channel { case 0: // channel A if inverting { avr.PORTB.SetBits(1 << 3) // PB3 high avr.TCCR2A.SetBits(avr.TCCR2A_COM2A0) } else { avr.PORTB.ClearBits(1 << 3) // PB3 low avr.TCCR2A.ClearBits(avr.TCCR2A_COM2A0) } case 1: // channel B if inverting { avr.PORTD.SetBits(1 << 3) // PD3 high avr.TCCR2A.SetBits(avr.TCCR2A_COM2B0) } else { avr.PORTD.ClearBits(1 << 3) // PD3 low avr.TCCR2A.ClearBits(avr.TCCR2A_COM2B0) } } } } // Set updates the channel value. This is used to control the channel duty // cycle, in other words the fraction of time the channel output is high (or low // when inverted). For example, to set it to a 25% duty cycle, use: // // pwm.Set(channel, pwm.Top() / 4) // // pwm.Set(channel, 0) will set the output to low and pwm.Set(channel, // pwm.Top()) will set the output to high, assuming the output isn't inverted. func (pwm PWM) Set(channel uint8, value uint32) { switch pwm.num { case 0: value := uint16(value) switch channel { case 0: // channel A if value == 0 { avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A1) } else { avr.OCR0A.Set(uint8(value - 1)) avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) } case 1: // channel B if value == 0 { avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B1) } else { avr.OCR0B.Set(uint8(value) - 1) avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) } } // monotonic timer is using the same time as PWM:0 // we must adjust internal settings of monotonic timer when PWM:0 settings changed adjustMonotonicTimer() case 1: mask := interrupt.Disable() switch channel { case 0: // channel A, PB1 if value == 0 { avr.TCCR1A.ClearBits(avr.TCCR1A_COM1A1 | avr.TCCR1A_COM1A0) } else { value := uint16(value) - 1 // yes, this is safe (it relies on underflow) avr.OCR1AH.Set(uint8(value >> 8)) avr.OCR1AL.Set(uint8(value)) if avr.PORTB.HasBits(1 << 1) { // is PB1 high? // Yes, set the inverting bit. avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1 | avr.TCCR1A_COM1A0) } else { // No, output is non-inverting. avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1) } } case 1: // channel B, PB2 if value == 0 { avr.TCCR1A.ClearBits(avr.TCCR1A_COM1B1 | avr.TCCR1A_COM1B0) } else { value := uint16(value) - 1 // yes, this is safe (it relies on underflow) avr.OCR1BH.Set(uint8(value >> 8)) avr.OCR1BL.Set(uint8(value)) if avr.PORTB.HasBits(1 << 2) { // is PB2 high? // Yes, set the inverting bit. avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1 | avr.TCCR1A_COM1B0) } else { // No, output is non-inverting. avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1) } } } interrupt.Restore(mask) case 2: value := uint16(value) switch channel { case 0: // channel A if value == 0 { avr.TCCR2A.ClearBits(avr.TCCR2A_COM2A1) } else { avr.OCR2A.Set(uint8(value - 1)) avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1) } case 1: // channel B if value == 0 { avr.TCCR2A.ClearBits(avr.TCCR2A_COM2B1) } else { avr.OCR2B.Set(uint8(value - 1)) avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1) } } } } // Pin Change Interrupts type PinChange uint8 const ( PinRising PinChange = 1 << iota PinFalling PinToggle = PinRising | PinFalling ) func (pin Pin) SetInterrupt(pinChange PinChange, callback func(Pin)) (err error) { switch { case pin >= PB0 && pin <= PB7: // PCMSK0 - PCINT0-7 pinStates[0] = avr.PINB.Get() pinIndex := pin - PB0 if pinChange&PinRising > 0 { pinCallbacks[0][pinIndex][0] = callback } if pinChange&PinFalling > 0 { pinCallbacks[0][pinIndex][1] = callback } if callback != nil { avr.PCMSK0.SetBits(1 << pinIndex) } else { avr.PCMSK0.ClearBits(1 << pinIndex) } avr.PCICR.SetBits(avr.PCICR_PCIE0) interrupt.New(avr.IRQ_PCINT0, handlePCINT0Interrupts) case pin >= PC0 && pin <= PC7: // PCMSK1 - PCINT8-14 pinStates[1] = avr.PINC.Get() pinIndex := pin - PC0 if pinChange&PinRising > 0 { pinCallbacks[1][pinIndex][0] = callback } if pinChange&PinFalling > 0 { pinCallbacks[1][pinIndex][1] = callback } if callback != nil { avr.PCMSK1.SetBits(1 << pinIndex) } else { avr.PCMSK1.ClearBits(1 << pinIndex) } avr.PCICR.SetBits(avr.PCICR_PCIE1) interrupt.New(avr.IRQ_PCINT1, handlePCINT1Interrupts) case pin >= PD0 && pin <= PD7: // PCMSK2 - PCINT16-23 pinStates[2] = avr.PIND.Get() pinIndex := pin - PD0 if pinChange&PinRising > 0 { pinCallbacks[2][pinIndex][0] = callback } if pinChange&PinFalling > 0 { pinCallbacks[2][pinIndex][1] = callback } if callback != nil { avr.PCMSK2.SetBits(1 << pinIndex) } else { avr.PCMSK2.ClearBits(1 << pinIndex) } avr.PCICR.SetBits(avr.PCICR_PCIE2) interrupt.New(avr.IRQ_PCINT2, handlePCINT2Interrupts) default: return ErrInvalidInputPin } return nil } var pinCallbacks [3][8][2]func(Pin) var pinStates [3]uint8 func handlePCINTInterrupts(intr uint8, port *volatile.Register8) { current := port.Get() change := pinStates[intr] ^ current pinStates[intr] = current for i := uint8(0); i < 8; i++ { if (change>>i)&0x01 != 0x01 { continue } pin := Pin(intr*8 + i) value := pin.Get() if value && pinCallbacks[intr][i][0] != nil { pinCallbacks[intr][i][0](pin) } if !value && pinCallbacks[intr][i][1] != nil { pinCallbacks[intr][i][1](pin) } } } func handlePCINT0Interrupts(intr interrupt.Interrupt) { handlePCINTInterrupts(0, avr.PINB) } func handlePCINT1Interrupts(intr interrupt.Interrupt) { handlePCINTInterrupts(1, avr.PINC) } func handlePCINT2Interrupts(intr interrupt.Interrupt) { handlePCINTInterrupts(2, avr.PIND) } ================================================ FILE: src/machine/machine_atmega328p.go ================================================ //go:build avr && atmega328p package machine import ( "device/avr" "runtime/volatile" ) const irq_USART0_RX = avr.IRQ_USART_RX // I2C0 is the only I2C interface on most AVRs. var I2C0 = &I2C{ srReg: avr.TWSR, brReg: avr.TWBR, crReg: avr.TWCR, drReg: avr.TWDR, srPS0: avr.TWSR_TWPS0, srPS1: avr.TWSR_TWPS1, crEN: avr.TWCR_TWEN, crINT: avr.TWCR_TWINT, crSTO: avr.TWCR_TWSTO, crEA: avr.TWCR_TWEA, crSTA: avr.TWCR_TWSTA, } // SPI configuration var SPI0 = &SPI{ spcr: avr.SPCR, spdr: avr.SPDR, spsr: avr.SPSR, spcrR0: avr.SPCR_SPR0, spcrR1: avr.SPCR_SPR1, spcrCPHA: avr.SPCR_CPHA, spcrCPOL: avr.SPCR_CPOL, spcrDORD: avr.SPCR_DORD, spcrSPE: avr.SPCR_SPE, spcrMSTR: avr.SPCR_MSTR, spsrI2X: avr.SPSR_SPI2X, spsrSPIF: avr.SPSR_SPIF, sck: PB5, sdo: PB3, sdi: PB4, cs: PB2, } // getPortMask returns the PORTx register and mask for the pin. func (p Pin) getPortMask() (*volatile.Register8, uint8) { switch { case p >= PB0 && p <= PB7: // port B return avr.PORTB, 1 << uint8(p-portB) case p >= PC0 && p <= PC7: // port C return avr.PORTC, 1 << uint8(p-portC) default: // port D return avr.PORTD, 1 << uint8(p-portD) } } ================================================ FILE: src/machine/machine_atmega328p_simulator.go ================================================ //go:build !baremetal && (arduino || arduino_nano) package machine var I2C0 = &I2C{Bus: 0, PinsSDA: []Pin{PC4}, PinsSCL: []Pin{PC5}} ================================================ FILE: src/machine/machine_atmega328pb.go ================================================ //go:build avr && atmega328pb package machine import ( "device/avr" "runtime/interrupt" "runtime/volatile" ) const irq_USART0_RX = avr.IRQ_USART0_RX const irq_USART1_RX = avr.IRQ_USART1_RX var ( UART1 = &_UART1 _UART1 = UART{ Buffer: NewRingBuffer(), dataReg: avr.UDR1, baudRegH: avr.UBRR1H, baudRegL: avr.UBRR1L, statusRegA: avr.UCSR1A, statusRegB: avr.UCSR1B, statusRegC: avr.UCSR1C, } ) func init() { // Register the UART interrupt. interrupt.New(irq_USART1_RX, _UART1.handleInterrupt) } // I2C0 is the only I2C interface on most AVRs. var I2C0 = &I2C{ srReg: avr.TWSR0, brReg: avr.TWBR0, crReg: avr.TWCR0, drReg: avr.TWDR0, srPS0: avr.TWSR0_TWPS0, srPS1: avr.TWSR0_TWPS1, crEN: avr.TWCR0_TWEN, crINT: avr.TWCR0_TWINT, crSTO: avr.TWCR0_TWSTO, crEA: avr.TWCR0_TWEA, crSTA: avr.TWCR0_TWSTA, } var I2C1 = &I2C{ srReg: avr.TWSR1, brReg: avr.TWBR1, crReg: avr.TWCR1, drReg: avr.TWDR1, srPS0: avr.TWSR1_TWPS10, srPS1: avr.TWSR1_TWPS11, crEN: avr.TWCR1_TWEN1, crINT: avr.TWCR1_TWINT1, crSTO: avr.TWCR1_TWSTO1, crEA: avr.TWCR1_TWEA1, crSTA: avr.TWCR1_TWSTA1, } // SPI configuration var SPI0 = &SPI{ spcr: avr.SPCR0, spdr: avr.SPDR0, spsr: avr.SPSR0, spcrR0: avr.SPCR0_SPR0, spcrR1: avr.SPCR0_SPR1, spcrCPHA: avr.SPCR0_CPHA, spcrCPOL: avr.SPCR0_CPOL, spcrDORD: avr.SPCR0_DORD, spcrSPE: avr.SPCR0_SPE, spcrMSTR: avr.SPCR0_MSTR, spsrI2X: avr.SPSR0_SPI2X, spsrSPIF: avr.SPSR0_SPIF, sck: PB5, sdo: PB3, sdi: PB4, cs: PB2, } var SPI1 = &SPI{ spcr: avr.SPCR1, spdr: avr.SPDR1, spsr: avr.SPSR1, spcrR0: avr.SPCR1_SPR10, spcrR1: avr.SPCR1_SPR11, spcrCPHA: avr.SPCR1_CPHA1, spcrCPOL: avr.SPCR1_CPOL1, spcrDORD: avr.SPCR1_DORD1, spcrSPE: avr.SPCR1_SPE1, spcrMSTR: avr.SPCR1_MSTR1, spsrI2X: avr.SPSR1_SPI2X1, spsrSPIF: avr.SPSR1_SPIF1, sck: PC1, sdo: PE3, sdi: PC0, cs: PE2, } // getPortMask returns the PORTx register and mask for the pin. func (p Pin) getPortMask() (*volatile.Register8, uint8) { switch { case p >= PB0 && p <= PB7: // port B return avr.PORTB, 1 << uint8(p-portB) case p >= PC0 && p <= PC7: // port C return avr.PORTC, 1 << uint8(p-portC) case p >= PD0 && p <= PD7: // port D return avr.PORTD, 1 << uint8(p-portD) default: // port E return avr.PORTE, 1 << uint8(p-portE) } } ================================================ FILE: src/machine/machine_atmega32u4.go ================================================ //go:build avr && atmega32u4 package machine import ( "device/avr" "runtime/volatile" ) const ( // Note: start at port B because there is no port A. portB Pin = iota * 8 portC portD portE portF ) const ( PB0 = portB + 0 PB1 = portB + 1 // peripherals: Timer1 channel A PB2 = portB + 2 // peripherals: Timer1 channel B PB3 = portB + 3 // peripherals: Timer2 channel A PB4 = portB + 4 PB5 = portB + 5 PB6 = portB + 6 PB7 = portB + 7 PC0 = portC + 0 PC1 = portC + 1 PC2 = portC + 2 PC3 = portC + 3 PC4 = portC + 4 PC5 = portC + 5 PC6 = portC + 6 PC7 = portC + 7 PD0 = portD + 0 PD1 = portD + 1 PD2 = portD + 2 PD3 = portD + 3 PD4 = portD + 4 PD5 = portD + 5 PD6 = portD + 6 PD7 = portD + 7 PE0 = portE + 0 PE1 = portE + 1 PE2 = portE + 2 PE3 = portE + 3 PE4 = portE + 4 PE5 = portE + 5 PE6 = portE + 6 PE7 = portE + 7 PF0 = portF + 0 PF1 = portF + 1 PF2 = portF + 2 PF3 = portF + 3 PF4 = portF + 4 PF5 = portF + 5 PF6 = portF + 6 PF7 = portF + 7 ) // getPortMask returns the PORTx register and mask for the pin. func (p Pin) getPortMask() (*volatile.Register8, uint8) { switch { case p >= PB0 && p <= PB7: // port B return avr.PORTB, 1 << uint8(p-portB) case p >= PC0 && p <= PC7: // port C return avr.PORTC, 1 << uint8(p-portC) case p >= PD0 && p <= PD7: // port D return avr.PORTD, 1 << uint8(p-portD) case p >= PE0 && p <= PE7: // port E return avr.PORTE, 1 << uint8(p-portE) default: // port F return avr.PORTF, 1 << uint8(p-portF) } } ================================================ FILE: src/machine/machine_atsam.go ================================================ //go:build sam package machine import ( "runtime/volatile" "unsafe" ) var deviceID [16]byte // DeviceID returns an identifier that is unique within // a particular chipset. // // The identity is one burnt into the MCU itself, or the // flash chip at time of manufacture. // // It's possible that two different vendors may allocate // the same DeviceID, so callers should take this into // account if needing to generate a globally unique id. // // The length of the hardware ID is vendor-specific, but // 8 bytes (64 bits) and 16 bytes (128 bits) are common. func DeviceID() []byte { for i := 0; i < len(deviceID); i++ { word := (*volatile.Register32)(unsafe.Pointer(deviceIDAddr[i/4])).Get() deviceID[i] = byte(word >> ((i % 4) * 8)) } return deviceID[:] } ================================================ FILE: src/machine/machine_atsamd21.go ================================================ //go:build sam && atsamd21 // Peripheral abstraction layer for the atsamd21. // // Datasheet: // http://ww1.microchip.com/downloads/en/DeviceDoc/SAMD21-Family-DataSheet-DS40001882D.pdf package machine import ( "device/arm" "device/sam" "errors" "internal/binary" "runtime/interrupt" "unsafe" ) const deviceName = sam.Device // DS40001882F, Section 10.3.3: Serial Number var deviceIDAddr = []uintptr{0x0080A00C, 0x0080A040, 0x0080A044, 0x0080A048} const ( PinAnalog PinMode = 1 PinSERCOM PinMode = 2 PinSERCOMAlt PinMode = 3 PinTimer PinMode = 4 PinTimerAlt PinMode = 5 PinCom PinMode = 6 //PinAC_CLK PinMode = 7 PinDigital PinMode = 8 PinInput PinMode = 9 PinInputPullup PinMode = 10 PinOutput PinMode = 11 PinTCC PinMode = PinTimer PinTCCAlt PinMode = PinTimerAlt PinInputPulldown PinMode = 12 ) type PinChange uint8 // Pin change interrupt constants for SetInterrupt. const ( PinRising PinChange = sam.EIC_CONFIG_SENSE0_RISE PinFalling PinChange = sam.EIC_CONFIG_SENSE0_FALL PinToggle PinChange = sam.EIC_CONFIG_SENSE0_BOTH ) // Callbacks to be called for pins configured with SetInterrupt. Unfortunately, // we also need to keep track of which interrupt channel is used by which pin, // as the only alternative would be iterating through all pins. // // We're using the magic constant 16 here because the SAM D21 has 16 interrupt // channels configurable for pins. var ( interruptPins [16]Pin // warning: the value is invalid when pinCallbacks[i] is not set! pinCallbacks [16]func(Pin) ) const ( pinPadMapSERCOM0Pad0 byte = (0x10 << 1) | 0x00 pinPadMapSERCOM1Pad0 byte = (0x20 << 1) | 0x00 pinPadMapSERCOM2Pad0 byte = (0x30 << 1) | 0x00 pinPadMapSERCOM3Pad0 byte = (0x40 << 1) | 0x00 pinPadMapSERCOM4Pad0 byte = (0x50 << 1) | 0x00 pinPadMapSERCOM5Pad0 byte = (0x60 << 1) | 0x00 pinPadMapSERCOM0Pad2 byte = (0x10 << 1) | 0x10 pinPadMapSERCOM1Pad2 byte = (0x20 << 1) | 0x10 pinPadMapSERCOM2Pad2 byte = (0x30 << 1) | 0x10 pinPadMapSERCOM3Pad2 byte = (0x40 << 1) | 0x10 pinPadMapSERCOM4Pad2 byte = (0x50 << 1) | 0x10 pinPadMapSERCOM5Pad2 byte = (0x60 << 1) | 0x10 pinPadMapSERCOM0AltPad0 byte = (0x01 << 1) | 0x00 pinPadMapSERCOM1AltPad0 byte = (0x02 << 1) | 0x00 pinPadMapSERCOM2AltPad0 byte = (0x03 << 1) | 0x00 pinPadMapSERCOM3AltPad0 byte = (0x04 << 1) | 0x00 pinPadMapSERCOM4AltPad0 byte = (0x05 << 1) | 0x00 pinPadMapSERCOM5AltPad0 byte = (0x06 << 1) | 0x00 pinPadMapSERCOM0AltPad2 byte = (0x01 << 1) | 0x01 pinPadMapSERCOM1AltPad2 byte = (0x02 << 1) | 0x01 pinPadMapSERCOM2AltPad2 byte = (0x03 << 1) | 0x01 pinPadMapSERCOM3AltPad2 byte = (0x04 << 1) | 0x01 pinPadMapSERCOM4AltPad2 byte = (0x05 << 1) | 0x01 pinPadMapSERCOM5AltPad2 byte = (0x06 << 1) | 0x01 ) // pinPadMapping lists which pins have which SERCOMs attached to them. // The encoding is rather dense, with each byte encoding two pins and both // SERCOM and SERCOM-ALT. // // Observations: // - There are six SERCOMs. Those SERCOM numbers can be encoded in 3 bits. // - Even pad numbers are always on even pins, and odd pad numbers are always on // odd pins. // - Pin pads come in pairs. If PA00 has pad 0, then PA01 has pad 1. // // With this information, we can encode SERCOM pin/pad numbers much more // efficiently. First of all, due to pads coming in pairs, we can ignore half // the pins: the information for an odd pin can be calculated easily from the // preceding even pin. And second, if odd pads are always on odd pins and even // pads on even pins, we can drop a single bit from the pad number. // // Each byte below is split in two nibbles. The 4 high bits are for SERCOM and // the 4 low bits are for SERCOM-ALT. Of each nibble, the 3 high bits encode the // SERCOM + 1 while the low bit encodes whether this is PAD0 or PAD2 (0 means // PAD0, 1 means PAD2). It encodes SERCOM + 1 instead of just the SERCOM number, // to make it easy to check whether a nibble is set at all. var pinPadMapping = [32]byte{ // page 21 PA00 / 2: 0 | pinPadMapSERCOM1AltPad0, PB08 / 2: 0 | pinPadMapSERCOM4AltPad0, PA04 / 2: 0 | pinPadMapSERCOM0AltPad0, PA06 / 2: 0 | pinPadMapSERCOM0AltPad2, PA08 / 2: pinPadMapSERCOM0Pad0 | pinPadMapSERCOM2AltPad0, PA10 / 2: pinPadMapSERCOM0Pad2 | pinPadMapSERCOM2AltPad2, // page 22 PB10 / 2: 0 | pinPadMapSERCOM4AltPad2, PB12 / 2: pinPadMapSERCOM4Pad0 | 0, PB14 / 2: pinPadMapSERCOM4Pad2 | 0, PA12 / 2: pinPadMapSERCOM2Pad0 | pinPadMapSERCOM4AltPad0, PA14 / 2: pinPadMapSERCOM2Pad2 | pinPadMapSERCOM4AltPad2, PA16 / 2: pinPadMapSERCOM1Pad0 | pinPadMapSERCOM3AltPad0, PA18 / 2: pinPadMapSERCOM1Pad2 | pinPadMapSERCOM3AltPad2, PB16 / 2: pinPadMapSERCOM5Pad0 | 0, PA20 / 2: pinPadMapSERCOM5Pad2 | pinPadMapSERCOM3AltPad2, PA22 / 2: pinPadMapSERCOM3Pad0 | pinPadMapSERCOM5AltPad0, PA24 / 2: pinPadMapSERCOM3Pad2 | pinPadMapSERCOM5AltPad2, // page 23 PB22 / 2: 0 | pinPadMapSERCOM5AltPad2, PA30 / 2: 0 | pinPadMapSERCOM1AltPad2, PB30 / 2: 0 | pinPadMapSERCOM5AltPad0, PB00 / 2: 0 | pinPadMapSERCOM5AltPad2, PB02 / 2: 0 | pinPadMapSERCOM5AltPad0, } // findPinPadMapping looks up the pad number and the pinmode for a given pin, // given a SERCOM number. The result can either be SERCOM, SERCOM-ALT, or "not // found" (indicated by returning ok=false). The pad number is returned to // calculate the DOPO/DIPO bitfields of the various serial peripherals. func findPinPadMapping(sercom uint8, pin Pin) (pinMode PinMode, pad uint32, ok bool) { if int(pin)/2 >= len(pinPadMapping) { // This is probably NoPin, for which no mapping is available. return } nibbles := pinPadMapping[pin/2] upper := nibbles >> 4 lower := nibbles & 0xf if upper != 0 { // SERCOM if (upper>>1)-1 == sercom { pinMode = PinSERCOM pad |= uint32((upper & 1) << 1) ok = true } } if lower != 0 { // SERCOM-ALT if (lower>>1)-1 == sercom { pinMode = PinSERCOMAlt pad |= uint32((lower & 1) << 1) ok = true } } if ok { // The lower bit of the pad is the same as the lower bit of the pin number. pad |= uint32(pin & 1) } return } // SetInterrupt sets an interrupt to be executed when a particular pin changes // state. The pin should already be configured as an input, including a pull up // or down if no external pull is provided. // // This call will replace a previously set callback on this pin. You can pass a // nil func to unset the pin change interrupt. If you do so, the change // parameter is ignored and can be set to any value (such as 0). func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { // Most pins follow a common pattern where the EXTINT value is the pin // number modulo 16. However, there are a few exceptions, as you can see // below. extint := uint8(0) switch p { case PA08: // Connected to NMI. This is not currently supported. return ErrInvalidInputPin case PA24: extint = 12 case PA25: extint = 13 case PA27: extint = 15 case PA28: extint = 8 case PA30: extint = 10 case PA31: extint = 11 default: // All other pins follow a normal pattern. extint = uint8(p) % 16 } if callback == nil { // Disable this pin interrupt (if it was enabled). sam.EIC.INTENCLR.Set(1 << extint) if pinCallbacks[extint] != nil { pinCallbacks[extint] = nil } return nil } if pinCallbacks[extint] != nil { // The pin was already configured. // To properly re-configure a pin, unset it first and set a new // configuration. return ErrNoPinChangeChannel } pinCallbacks[extint] = callback interruptPins[extint] = p if sam.EIC.CTRL.Get() == 0 { // EIC peripheral has not yet been initialized. Initialize it now. // The EIC needs two clocks: CLK_EIC_APB and GCLK_EIC. CLK_EIC_APB is // enabled by default, so doesn't have to be re-enabled. The other is // required for detecting edges and must be enabled manually. sam.GCLK.CLKCTRL.Set(sam.GCLK_CLKCTRL_ID_EIC<= 8 { addr = &sam.EIC.CONFIG1 } pos := (extint % 8) * 4 // bit position in register addr.ReplaceBits(uint32(change), 0xf, pos) // Enable external interrupt for this pin. sam.EIC.INTENSET.Set(1 << extint) // Set the PMUXEN flag, while keeping the INEN and PULLEN flags (if they // were set before). This avoids clearing the pin pull mode while // configuring the pin interrupt. p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | (p.getPinCfg() & (sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN))) if p&1 > 0 { // odd pin, so save the even pins val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk p.setPMux(val | (sam.PORT_PMUX0_PMUXO_A << sam.PORT_PMUX0_PMUXO_Pos)) } else { // even pin, so save the odd pins val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk p.setPMux(val | (sam.PORT_PMUX0_PMUXE_A << sam.PORT_PMUX0_PMUXE_Pos)) } interrupt.New(sam.IRQ_EIC, func(interrupt.Interrupt) { flags := sam.EIC.INTFLAG.Get() sam.EIC.INTFLAG.Set(flags) // clear interrupt for i := uint(0); i < 16; i++ { // there are 16 channels if flags&(1<>3) & uint16(0x7) // ADC Linearity bits 4:0 linearity0Fuse := *(*uint32)(unsafe.Pointer(uintptr(0x00806020))) linearity := uint16(linearity0Fuse>>27) & uint16(0x1f) // ADC Linearity bits 7:5 linearity1Fuse := *(*uint32)(unsafe.Pointer(uintptr(0x00806020) + 4)) linearity |= uint16(linearity1Fuse) & uint16(0x7) << 5 // set calibration sam.ADC.CALIB.Set((bias << 8) | linearity) } // Configure configures a ADC pin to be able to be used to read data. func (a ADC) Configure(config ADCConfig) { // Wait for synchronization waitADCSync() var resolution uint32 switch config.Resolution { case 8: resolution = sam.ADC_CTRLB_RESSEL_8BIT case 10: resolution = sam.ADC_CTRLB_RESSEL_10BIT case 12: resolution = sam.ADC_CTRLB_RESSEL_12BIT case 16: resolution = sam.ADC_CTRLB_RESSEL_16BIT default: resolution = sam.ADC_CTRLB_RESSEL_12BIT } // Divide Clock by 32 with 12 bits resolution as default sam.ADC.CTRLB.Set((sam.ADC_CTRLB_PRESCALER_DIV32 << sam.ADC_CTRLB_PRESCALER_Pos) | uint16(resolution<> sam.ADC_CTRLB_RESSEL_Pos { case sam.ADC_CTRLB_RESSEL_8BIT: val = val << 8 case sam.ADC_CTRLB_RESSEL_10BIT: val = val << 6 case sam.ADC_CTRLB_RESSEL_16BIT: val = val << 4 case sam.ADC_CTRLB_RESSEL_12BIT: val = val << 4 } return val } func (a ADC) getADCChannel() uint8 { switch a.Pin { case PA02: return 0 case PA03: return 1 case PB04: return 12 case PB05: return 13 case PB06: return 14 case PB07: return 15 case PB08: return 2 case PB09: return 3 case PA04: return 4 case PA05: return 5 case PA06: return 6 case PA07: return 7 case PA08: return 16 case PA09: return 17 case PA10: return 18 case PA11: return 19 case PB00: return 8 case PB01: return 9 case PB02: return 10 case PB03: return 11 default: return 0 } } func waitADCSync() { for sam.ADC.STATUS.HasBits(sam.ADC_STATUS_SYNCBUSY) { } } // UART on the SAMD21. type UART struct { Buffer *RingBuffer Bus *sam.SERCOM_USART_Type SERCOM uint8 Interrupt interrupt.Interrupt } const ( sampleRate16X = 16 lsbFirst = 1 ) // Configure the UART. func (uart *UART) Configure(config UARTConfig) error { // Default baud rate to 115200. if config.BaudRate == 0 { config.BaudRate = 115200 } // Use default pins if pins are not set. if config.TX == 0 && config.RX == 0 { // use default pins config.TX = UART_TX_PIN config.RX = UART_RX_PIN } // Determine transmit pinout. txPinMode, txPad, ok := findPinPadMapping(uart.SERCOM, config.TX) if !ok { return ErrInvalidOutputPin } var txPadOut uint32 // See table 25-9 of the datasheet (page 459) for how pads are mapped to // pinout values. switch txPad { case 0: txPadOut = 0 case 2: txPadOut = 1 default: // this should be a flow control (RTS/CTS) pin return ErrInvalidOutputPin } // Determine receive pinout. rxPinMode, rxPad, ok := findPinPadMapping(uart.SERCOM, config.RX) if !ok { return ErrInvalidInputPin } // As you can see in table 25-8 on page 459 of the datasheet, input pins // are mapped directly. rxPadOut := rxPad // configure pins config.TX.Configure(PinConfig{Mode: txPinMode}) config.RX.Configure(PinConfig{Mode: rxPinMode}) // configure RTS/CTS pins if provided if config.RTS != 0 && config.CTS != 0 { rtsPinMode, _, ok := findPinPadMapping(uart.SERCOM, config.RTS) if !ok { return ErrInvalidOutputPin } ctsPinMode, _, ok := findPinPadMapping(uart.SERCOM, config.CTS) if !ok { return ErrInvalidInputPin } // See table 25-9 of the datasheet (page 459) for how pads are mapped to // pinout values. if txPadOut == 1 { return ErrInvalidOutputPin } txPadOut = 2 config.RTS.Configure(PinConfig{Mode: rtsPinMode}) config.CTS.Configure(PinConfig{Mode: ctsPinMode}) } // reset SERCOM0 uart.Bus.CTRLA.SetBits(sam.SERCOM_USART_CTRLA_SWRST) for uart.Bus.CTRLA.HasBits(sam.SERCOM_USART_CTRLA_SWRST) || uart.Bus.SYNCBUSY.HasBits(sam.SERCOM_USART_SYNCBUSY_SWRST) { } // set UART mode/sample rate // SERCOM_USART_CTRLA_MODE(mode) | // SERCOM_USART_CTRLA_SAMPR(sampleRate); uart.Bus.CTRLA.Set((sam.SERCOM_USART_CTRLA_MODE_USART_INT_CLK << sam.SERCOM_USART_CTRLA_MODE_Pos) | (1 << sam.SERCOM_USART_CTRLA_SAMPR_Pos)) // sample rate of 16x // Set baud rate uart.SetBaudRate(config.BaudRate) // setup UART frame // SERCOM_USART_CTRLA_FORM( (parityMode == SERCOM_NO_PARITY ? 0 : 1) ) | // dataOrder << SERCOM_USART_CTRLA_DORD_Pos; uart.Bus.CTRLA.SetBits((0 << sam.SERCOM_USART_CTRLA_FORM_Pos) | // no parity (lsbFirst << sam.SERCOM_USART_CTRLA_DORD_Pos)) // data order // set UART stop bits/parity // SERCOM_USART_CTRLB_CHSIZE(charSize) | // nbStopBits << SERCOM_USART_CTRLB_SBMODE_Pos | // (parityMode == SERCOM_NO_PARITY ? 0 : parityMode) << SERCOM_USART_CTRLB_PMODE_Pos; //If no parity use default value uart.Bus.CTRLB.SetBits((0 << sam.SERCOM_USART_CTRLB_CHSIZE_Pos) | // 8 bits is 0 (0 << sam.SERCOM_USART_CTRLB_SBMODE_Pos) | // 1 stop bit is zero (0 << sam.SERCOM_USART_CTRLB_PMODE_Pos)) // no parity // set UART pads. This is not same as pins... // SERCOM_USART_CTRLA_TXPO(txPad) | // SERCOM_USART_CTRLA_RXPO(rxPad); uart.Bus.CTRLA.SetBits((txPadOut << sam.SERCOM_USART_CTRLA_TXPO_Pos) | (rxPadOut << sam.SERCOM_USART_CTRLA_RXPO_Pos)) // Enable Transceiver and Receiver //sercom->USART.CTRLB.reg |= SERCOM_USART_CTRLB_TXEN | SERCOM_USART_CTRLB_RXEN ; uart.Bus.CTRLB.SetBits(sam.SERCOM_USART_CTRLB_TXEN | sam.SERCOM_USART_CTRLB_RXEN) // Enable USART1 port. // sercom->USART.CTRLA.bit.ENABLE = 0x1u; uart.Bus.CTRLA.SetBits(sam.SERCOM_USART_CTRLA_ENABLE) for uart.Bus.SYNCBUSY.HasBits(sam.SERCOM_USART_SYNCBUSY_ENABLE) { } // setup interrupt on receive uart.Bus.INTENSET.Set(sam.SERCOM_USART_INTENSET_RXC) // Enable RX IRQ. uart.Interrupt.Enable() return nil } // SetBaudRate sets the communication speed for the UART. func (uart *UART) SetBaudRate(br uint32) { // Asynchronous fractional mode (Table 24-2 in datasheet) // BAUD = fref / (sampleRateValue * fbaud) // (multiply by 8, to calculate fractional piece) // uint32_t baudTimes8 = (SystemCoreClock * 8) / (16 * baudrate); baud := (CPUFrequency() * 8) / (sampleRate16X * br) // sercom->USART.BAUD.FRAC.FP = (baudTimes8 % 8); // sercom->USART.BAUD.FRAC.BAUD = (baudTimes8 / 8); uart.Bus.BAUD.Set(uint16(((baud % 8) << sam.SERCOM_USART_BAUD_FRAC_MODE_FP_Pos) | ((baud / 8) << sam.SERCOM_USART_BAUD_FRAC_MODE_BAUD_Pos))) } // WriteByte writes a byte of data to the UART. func (uart *UART) writeByte(c byte) error { // wait until ready to receive for !uart.Bus.INTFLAG.HasBits(sam.SERCOM_USART_INTFLAG_DRE) { } uart.Bus.DATA.Set(uint16(c)) return nil } func (uart *UART) flush() {} // handleInterrupt should be called from the appropriate interrupt handler for // this UART instance. func (uart *UART) handleInterrupt(interrupt.Interrupt) { // should reset IRQ uart.Receive(byte((uart.Bus.DATA.Get() & 0xFF))) uart.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INTFLAG_RXC) } // I2C on the SAMD21. type I2C struct { Bus *sam.SERCOM_I2CM_Type SERCOM uint8 } // I2CConfig is used to store config info for I2C. type I2CConfig struct { Frequency uint32 SCL Pin SDA Pin } const ( // Default rise time in nanoseconds, based on 4.7K ohm pull up resistors riseTimeNanoseconds = 125 // wire bus states wireUnknownState = 0 wireIdleState = 1 wireOwnerState = 2 wireBusyState = 3 // wire commands wireCmdNoAction = 0 wireCmdRepeatStart = 1 wireCmdRead = 2 wireCmdStop = 3 ) const i2cTimeout = 1000 // Configure is intended to setup the I2C interface. func (i2c *I2C) Configure(config I2CConfig) error { // Default I2C bus speed is 100 kHz. if config.Frequency == 0 { config.Frequency = 100 * KHz } if config.SDA == 0 && config.SCL == 0 { config.SDA = SDA_PIN config.SCL = SCL_PIN } sclPinMode, sclPad, ok := findPinPadMapping(i2c.SERCOM, config.SCL) if !ok || sclPad != 1 { // SCL must be on pad 1, according to section 27.5 of the datasheet. // Note: this is not an exhaustive test for I2C support on the pin: not // all pins support I2C. return ErrInvalidClockPin } sdaPinMode, sdaPad, ok := findPinPadMapping(i2c.SERCOM, config.SDA) if !ok || sdaPad != 0 { // SDA must be on pad 0, according to section 27.5 of the datasheet. // Note: this is not an exhaustive test for I2C support on the pin: not // all pins support I2C. return ErrInvalidDataPin } // reset SERCOM i2c.Bus.CTRLA.SetBits(sam.SERCOM_I2CM_CTRLA_SWRST) for i2c.Bus.CTRLA.HasBits(sam.SERCOM_I2CM_CTRLA_SWRST) || i2c.Bus.SYNCBUSY.HasBits(sam.SERCOM_I2CM_SYNCBUSY_SWRST) { } // Set i2c controller mode //SERCOM_I2CM_CTRLA_MODE( I2C_MASTER_OPERATION ) i2c.Bus.CTRLA.Set(sam.SERCOM_I2CM_CTRLA_MODE_I2C_MASTER << sam.SERCOM_I2CM_CTRLA_MODE_Pos) // | i2c.SetBaudRate(config.Frequency) // Enable I2CM port. // sercom->USART.CTRLA.bit.ENABLE = 0x1u; i2c.Bus.CTRLA.SetBits(sam.SERCOM_I2CM_CTRLA_ENABLE) for i2c.Bus.SYNCBUSY.HasBits(sam.SERCOM_I2CM_SYNCBUSY_ENABLE) { } // set bus idle mode i2c.Bus.STATUS.SetBits(wireIdleState << sam.SERCOM_I2CM_STATUS_BUSSTATE_Pos) for i2c.Bus.SYNCBUSY.HasBits(sam.SERCOM_I2CM_SYNCBUSY_SYSOP) { } // enable pins config.SDA.Configure(PinConfig{Mode: sdaPinMode}) config.SCL.Configure(PinConfig{Mode: sclPinMode}) return nil } // SetBaudRate sets the communication speed for I2C. func (i2c *I2C) SetBaudRate(br uint32) error { // Synchronous arithmetic baudrate, via Arduino SAMD implementation: // SystemCoreClock / ( 2 * baudrate) - 5 - (((SystemCoreClock / 1000000) * WIRE_RISE_TIME_NANOSECONDS) / (2 * 1000)); baud := CPUFrequency()/(2*br) - 5 - (((CPUFrequency() / 1000000) * riseTimeNanoseconds) / (2 * 1000)) i2c.Bus.BAUD.Set(baud) return nil } // Tx does a single I2C transaction at the specified address. // It clocks out the given address, writes the bytes in w, reads back len(r) // bytes and stores them in r, and generates a stop condition on the bus. func (i2c *I2C) Tx(addr uint16, w, r []byte) error { var err error if len(w) != 0 { // send start/address for write i2c.sendAddress(addr, true) // wait until transmission complete timeout := i2cTimeout for !i2c.Bus.INTFLAG.HasBits(sam.SERCOM_I2CM_INTFLAG_MB) { timeout-- if timeout == 0 { return errI2CWriteTimeout } } // ACK received (0: ACK, 1: NACK) if i2c.Bus.STATUS.HasBits(sam.SERCOM_I2CM_STATUS_RXNACK) { return errI2CAckExpected } // write data for _, b := range w { err = i2c.WriteByte(b) if err != nil { return err } } err = i2c.signalStop() if err != nil { return err } } if len(r) != 0 { // send start/address for read i2c.sendAddress(addr, false) // wait transmission complete for !i2c.Bus.INTFLAG.HasBits(sam.SERCOM_I2CM_INTFLAG_SB) { // If the peripheral NACKS the address, the MB bit will be set. // In that case, send a stop condition and return error. if i2c.Bus.INTFLAG.HasBits(sam.SERCOM_I2CM_INTFLAG_MB) { i2c.Bus.CTRLB.SetBits(wireCmdStop << sam.SERCOM_I2CM_CTRLB_CMD_Pos) // Stop condition return errI2CAckExpected } } // ACK received (0: ACK, 1: NACK) if i2c.Bus.STATUS.HasBits(sam.SERCOM_I2CM_STATUS_RXNACK) { return errI2CAckExpected } // read first byte r[0] = i2c.readByte() for i := 1; i < len(r); i++ { // Send an ACK i2c.Bus.CTRLB.ClearBits(sam.SERCOM_I2CM_CTRLB_ACKACT) i2c.signalRead() // Read data and send the ACK r[i] = i2c.readByte() } // Send NACK to end transmission i2c.Bus.CTRLB.SetBits(sam.SERCOM_I2CM_CTRLB_ACKACT) err = i2c.signalStop() if err != nil { return err } } return nil } // WriteByte writes a single byte to the I2C bus. func (i2c *I2C) WriteByte(data byte) error { // Send data byte i2c.Bus.DATA.Set(data) // wait until transmission successful timeout := i2cTimeout for !i2c.Bus.INTFLAG.HasBits(sam.SERCOM_I2CM_INTFLAG_MB) { // check for bus error if sam.SERCOM3_I2CM.STATUS.HasBits(sam.SERCOM_I2CM_STATUS_BUSERR) { return errI2CBusError } timeout-- if timeout == 0 { return errI2CWriteTimeout } } if i2c.Bus.STATUS.HasBits(sam.SERCOM_I2CM_STATUS_RXNACK) { return errI2CAckExpected } return nil } // sendAddress sends the address and start signal func (i2c *I2C) sendAddress(address uint16, write bool) error { data := (address << 1) if !write { data |= 1 // set read flag } // wait until bus ready timeout := i2cTimeout for !i2c.Bus.STATUS.HasBits(wireIdleState< 0 { baudRate-- } spi.Bus.BAUD.Set(uint8(baudRate)) // Enable SPI port. spi.Bus.CTRLA.SetBits(sam.SERCOM_SPI_CTRLA_ENABLE) for spi.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPI_SYNCBUSY_ENABLE) { } return nil } // Transfer writes/reads a single byte using the SPI interface. func (spi *SPI) Transfer(w byte) (byte, error) { // write data spi.Bus.DATA.Set(uint32(w)) // wait for receive for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) { } // return data return byte(spi.Bus.DATA.Get()), nil } // Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read // interface, there must always be the same number of bytes written as bytes read. // The Tx method knows about this, and offers a few different ways of calling it. // // This form sends the bytes in tx buffer, putting the resulting bytes read into the rx buffer. // Note that the tx and rx buffers must be the same size: // // spi.Tx(tx, rx) // // This form sends the tx buffer, ignoring the result. Useful for sending "commands" that return zeros // until all the bytes in the command packet have been received: // // spi.Tx(tx, nil) // // This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": // // spi.Tx(nil, rx) func (spi *SPI) Tx(w, r []byte) error { switch { case w == nil: // read only, so write zero and read a result. spi.rx(r) case r == nil: // write only spi.tx(w) default: // write/read if len(w) != len(r) { return ErrTxInvalidSliceSize } spi.txrx(w, r) } return nil } func (spi *SPI) tx(tx []byte) { for i := 0; i < len(tx); i++ { for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) { } spi.Bus.DATA.Set(uint32(tx[i])) } for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_TXC) { } // read to clear RXC register for spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) { spi.Bus.DATA.Get() } } func (spi *SPI) rx(rx []byte) { spi.Bus.DATA.Set(0) for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) { } for i := 1; i < len(rx); i++ { spi.Bus.DATA.Set(0) for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) { } rx[i-1] = byte(spi.Bus.DATA.Get()) } for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) { } rx[len(rx)-1] = byte(spi.Bus.DATA.Get()) } func (spi *SPI) txrx(tx, rx []byte) { spi.Bus.DATA.Set(uint32(tx[0])) for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) { } for i := 1; i < len(rx); i++ { spi.Bus.DATA.Set(uint32(tx[i])) for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) { } rx[i-1] = byte(spi.Bus.DATA.Get()) } for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) { } rx[len(rx)-1] = byte(spi.Bus.DATA.Get()) } // TCC is one timer/counter peripheral, which consists of a counter and multiple // output channels (that can be connected to actual pins). You can set the // frequency using SetPeriod, but only for all the channels in this TCC // peripheral at once. type TCC sam.TCC_Type // The SAM D21 has three TCC peripherals, which have PWM as one feature. var ( TCC0 = (*TCC)(sam.TCC0) TCC1 = (*TCC)(sam.TCC1) TCC2 = (*TCC)(sam.TCC2) ) //go:inline func (tcc *TCC) timer() *sam.TCC_Type { return (*sam.TCC_Type)(tcc) } // Configure enables and configures this TCC. func (tcc *TCC) Configure(config PWMConfig) error { // Enable the clock source for this timer. switch tcc.timer() { case sam.TCC0: sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_TCC0_) // Use GCLK0 for TCC0/TCC1 sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_TCC0_TCC1 << sam.GCLK_CLKCTRL_ID_Pos) | (sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) | sam.GCLK_CLKCTRL_CLKEN) for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) { } case sam.TCC1: sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_TCC1_) // Use GCLK0 for TCC0/TCC1 sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_TCC0_TCC1 << sam.GCLK_CLKCTRL_ID_Pos) | (sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) | sam.GCLK_CLKCTRL_CLKEN) for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) { } case sam.TCC2: sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_TCC2_) // Use GCLK0 for TCC2/TC3 sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_TCC2_TC3 << sam.GCLK_CLKCTRL_ID_Pos) | (sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) | sam.GCLK_CLKCTRL_CLKEN) for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) { } } // Disable timer (if it was enabled). This is necessary because // tcc.setPeriod may want to change the prescaler bits in CTRLA, which is // only allowed when the TCC is disabled. tcc.timer().CTRLA.ClearBits(sam.TCC_CTRLA_ENABLE) // Use "Normal PWM" (single-slope PWM) tcc.timer().WAVE.Set(sam.TCC_WAVE_WAVEGEN_NPWM) // Wait for synchronization of all changed registers. for tcc.timer().SYNCBUSY.Get() != 0 { } // Set the period and prescaler. err := tcc.setPeriod(config.Period, true) // Enable the timer. tcc.timer().CTRLA.SetBits(sam.TCC_CTRLA_ENABLE) // Wait for synchronization of all changed registers. for tcc.timer().SYNCBUSY.Get() != 0 { } // Return any error that might have occurred in the tcc.setPeriod call. return err } // SetPeriod updates the period of this TCC peripheral. // To set a particular frequency, use the following formula: // // period = 1e9 / frequency // // If you use a period of 0, a period that works well for LEDs will be picked. // // SetPeriod will not change the prescaler, but also won't change the current // value in any of the channels. This means that you may need to update the // value for the particular channel. // // Note that you cannot pick any arbitrary period after the TCC peripheral has // been configured. If you want to switch between frequencies, pick the lowest // frequency (longest period) once when calling Configure and adjust the // frequency here as needed. func (tcc *TCC) SetPeriod(period uint64) error { err := tcc.setPeriod(period, false) if err == nil { if tcc.Counter() >= tcc.Top() { // When setting the timer to a shorter period, there is a chance // that it passes the counter value and thus goes all the way to MAX // before wrapping back to zero. // To avoid this, reset the counter back to 0. tcc.timer().COUNT.Set(0) } } return err } // setPeriod sets the period of this TCC, possibly updating the prescaler as // well. The prescaler can only modified when the TCC is disabled, that is, in // the Configure function. func (tcc *TCC) setPeriod(period uint64, updatePrescaler bool) error { var top uint64 if period == 0 { // Make sure the TOP value is at 0xffff (enough for a 16-bit timer). top = 0xffff } else { // The formula below calculates the following formula, optimized: // period * (48e6 / 1e9) // This assumes that the chip is running at the (default) 48MHz speed. top = period * 6 / 125 } maxTop := uint64(0xffffff) if tcc.timer() == sam.TCC2 { // TCC2 is a 16-bit timer, not a 24-bit timer. maxTop = 0xffff } if updatePrescaler { // This function was called during Configure(), with the timer disabled. // Note that updating the prescaler can only happen while the peripheral // is disabled. var prescaler uint32 switch { case top <= maxTop: prescaler = sam.TCC_CTRLA_PRESCALER_DIV1 case top/2 <= maxTop: prescaler = sam.TCC_CTRLA_PRESCALER_DIV2 top = top / 2 case top/4 <= maxTop: prescaler = sam.TCC_CTRLA_PRESCALER_DIV4 top = top / 4 case top/8 <= maxTop: prescaler = sam.TCC_CTRLA_PRESCALER_DIV8 top = top / 8 case top/16 <= maxTop: prescaler = sam.TCC_CTRLA_PRESCALER_DIV16 top = top / 16 case top/64 <= maxTop: prescaler = sam.TCC_CTRLA_PRESCALER_DIV64 top = top / 64 case top/256 <= maxTop: prescaler = sam.TCC_CTRLA_PRESCALER_DIV256 top = top / 256 case top/1024 <= maxTop: prescaler = sam.TCC_CTRLA_PRESCALER_DIV1024 top = top / 1024 default: return ErrPWMPeriodTooLong } tcc.timer().CTRLA.Set((tcc.timer().CTRLA.Get() &^ sam.TCC_CTRLA_PRESCALER_Msk) | (prescaler << sam.TCC_CTRLA_PRESCALER_Pos)) } else { // Do not update the prescaler, but use the already-configured // prescaler. This is the normal SetPeriod case, where the prescaler // must not be changed. prescaler := (tcc.timer().CTRLA.Get() & sam.TCC_CTRLA_PRESCALER_Msk) >> sam.TCC_CTRLA_PRESCALER_Pos switch prescaler { case sam.TCC_CTRLA_PRESCALER_DIV1: top /= 1 // no-op case sam.TCC_CTRLA_PRESCALER_DIV2: top /= 2 case sam.TCC_CTRLA_PRESCALER_DIV4: top /= 4 case sam.TCC_CTRLA_PRESCALER_DIV8: top /= 8 case sam.TCC_CTRLA_PRESCALER_DIV16: top /= 16 case sam.TCC_CTRLA_PRESCALER_DIV64: top /= 64 case sam.TCC_CTRLA_PRESCALER_DIV256: top /= 256 case sam.TCC_CTRLA_PRESCALER_DIV1024: top /= 1024 default: // unreachable } if top > maxTop { return ErrPWMPeriodTooLong } } // Set the period (the counter top). tcc.timer().PER.Set(uint32(top) - 1) // Wait for synchronization of CTRLA.PRESCALER and PER registers. for tcc.timer().SYNCBUSY.Get() != 0 { } return nil } // Top returns the current counter top, for use in duty cycle calculation. It // will only change with a call to Configure or SetPeriod, otherwise it is // constant. // // The value returned here is hardware dependent. In general, it's best to treat // it as an opaque value that can be divided by some number and passed to Set // (see Set documentation for more information). func (tcc *TCC) Top() uint32 { return tcc.timer().PER.Get() + 1 } // Counter returns the current counter value of the timer in this TCC // peripheral. It may be useful for debugging. func (tcc *TCC) Counter() uint32 { tcc.timer().CTRLBSET.Set(sam.TCC_CTRLBSET_CMD_READSYNC << sam.TCC_CTRLBSET_CMD_Pos) for tcc.timer().SYNCBUSY.Get() != 0 { } return tcc.timer().COUNT.Get() } // Some constants to make pinTimerMapping below easier to read. const ( pinTCC0 = 1 pinTCC1 = 2 pinTCC2 = 3 pinTimerCh0 = 0 << 3 pinTimerCh2 = 1 << 3 pinTCC0Ch0 = pinTCC0 | pinTimerCh0 pinTCC0Ch2 = pinTCC0 | pinTimerCh2 pinTCC1Ch0 = pinTCC1 | pinTimerCh0 pinTCC1Ch2 = pinTCC1 | pinTimerCh2 pinTCC2Ch0 = pinTCC2 | pinTimerCh0 ) // Mapping from pin number to TCC peripheral and channel using a special // encoding. Note that only TCC0-TCC2 are included, not TC3 and up. // Every byte is split in two nibbles where the low nibble describes PinTCC and // the high nibble describes PinTCCAlt. Within a nibble, there is one bit that // indicates Ch0/Ch1 or Ch2/Ch3, and three other bits that contain the TCC // peripheral number plus one (to distinguish between TCC0Ch0 and 0). // // The encoding can be so compact because all pins are configured in pairs, so // if you know PA00 you can infer the configuration of PA01. And only channel 0 // or 2 need to be included (taking up just one bit), because channel 0 and 2 // are only ever used on odd pins and channel 1 and 3 on even pins, again using // the pin pair pattern to reduce the amount of information needed to be stored. // // Datasheet: https://cdn.sparkfun.com/datasheets/Dev/Arduino/Boards/Atmel-42181-SAM-D21_Datasheet.pdf var pinTimerMapping = [...]uint8{ // page 21 PA00 / 2: pinTCC2Ch0 | 0, PA04 / 2: pinTCC0Ch0 | 0, PA06 / 2: pinTCC1Ch0 | 0, PA08 / 2: pinTCC0Ch0 | pinTCC1Ch2<<4, PA10 / 2: pinTCC1Ch0 | pinTCC0Ch2<<4, // page 22 PB10 / 2: 0 | pinTCC0Ch0<<4, PB12 / 2: 0 | pinTCC0Ch2<<4, PA12 / 2: pinTCC2Ch0 | pinTCC0Ch2<<4, PA14 / 2: 0 | pinTCC0Ch0<<4, PA16 / 2: pinTCC2Ch0 | pinTCC0Ch2<<4, PA18 / 2: 0 | pinTCC0Ch2<<4, PB16 / 2: 0 | pinTCC0Ch0<<4, PA20 / 2: 0 | pinTCC0Ch2<<4, PA22 / 2: 0 | pinTCC0Ch0<<4, PA24 / 2: 0 | pinTCC1Ch2<<4, // page 23 PA30 / 2: 0 | pinTCC1Ch0<<4, PB30 / 2: pinTCC0Ch0 | pinTCC1Ch2<<4, } // findPinTimerMapping returns the pin mode (PinTCC or PinTCCAlt) and the channel // number for a given timer and pin. A zero PinMode is returned if no mapping // could be found. func findPinTimerMapping(timer uint8, pin Pin) (PinMode, uint8) { mapping := pinTimerMapping[pin/2] // evenChannel below indicates the channel 0 or 2, for the even part of the // pin pair. The next pin will also have the next channel (1 or 3). if mapping&0x07 == timer+1 { // PWM output is on peripheral function E. evenChannel := ((mapping >> 3) & 1) * 2 return PinTCC, evenChannel + uint8(pin&1) } if (mapping&0x70)>>4 == timer+1 { // PWM output is on peripheral function F. evenChannel := ((mapping >> 7) & 1) * 2 return PinTCCAlt, evenChannel + uint8(pin&1) } return 0, 0 } // Channel returns a PWM channel for the given pin. Note that one channel may be // shared between multiple pins, and so will have the same duty cycle. If this // is not desirable, look for a different TCC peripheral or consider using a // different pin. func (tcc *TCC) Channel(pin Pin) (uint8, error) { var pinMode PinMode var channel uint8 switch tcc.timer() { case sam.TCC0: pinMode, channel = findPinTimerMapping(0, pin) case sam.TCC1: pinMode, channel = findPinTimerMapping(1, pin) case sam.TCC2: pinMode, channel = findPinTimerMapping(2, pin) } if pinMode == 0 { // No pin could be found. return 0, ErrInvalidOutputPin } // Enable the port multiplexer for pin pin.setPinCfg(sam.PORT_PINCFG0_PMUXEN) if pin&1 > 0 { // odd pin, so save the even pins val := pin.getPMux() & sam.PORT_PMUX0_PMUXE_Msk pin.setPMux(val | uint8(pinMode<> 6) syncDAC() return nil } func syncDAC() { for sam.DAC.STATUS.HasBits(sam.DAC_STATUS_SYNCBUSY) { } } // Flash related code const memoryStart = 0x0 // compile-time check for ensuring we fulfill BlockDevice interface var _ BlockDevice = flashBlockDevice{} var Flash flashBlockDevice type flashBlockDevice struct { initComplete bool } // ReadAt reads the given number of bytes from the block device. func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) { if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() { return 0, errFlashCannotReadPastEOF } f.ensureInitComplete() waitWhileFlashBusy() data := unsafe.Slice((*byte)(unsafe.Add(unsafe.Pointer(FlashDataStart()), uintptr(off))), len(p)) copy(p, data) return len(p), nil } // WriteAt writes the given number of bytes to the block device. // Data is written to the page buffer in 4-byte chunks, then saved to flash memory. // See Atmel-42181G–SAM-D21_Datasheet–09/2015 page 359. // If the length of p is not long enough it will be padded with 0xFF bytes. // This method assumes that the destination is already erased. func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() { return 0, errFlashCannotWritePastEOF } f.ensureInitComplete() address := FlashDataStart() + uintptr(off) padded := flashPad(p, int(f.WriteBlockSize())) waitWhileFlashBusy() for j := 0; j < len(padded); j += int(f.WriteBlockSize()) { // page buffer is 64 bytes long, but only 4 bytes can be written at once for k := 0; k < int(f.WriteBlockSize()); k += 4 { *(*uint32)(unsafe.Pointer(address + uintptr(k))) = binary.LittleEndian.Uint32(padded[j+k : j+k+4]) } sam.NVMCTRL.SetADDR(uint32(address >> 1)) sam.NVMCTRL.CTRLA.Set(sam.NVMCTRL_CTRLA_CMD_WP | (sam.NVMCTRL_CTRLA_CMDEX_KEY << sam.NVMCTRL_CTRLA_CMDEX_Pos)) waitWhileFlashBusy() if err := checkFlashError(); err != nil { return j, err } address += uintptr(f.WriteBlockSize()) } return len(padded), nil } // Size returns the number of bytes in this block device. func (f flashBlockDevice) Size() int64 { return int64(FlashDataEnd() - FlashDataStart()) } const writeBlockSize = 64 // WriteBlockSize returns the block size in which data can be written to // memory. It can be used by a client to optimize writes, non-aligned writes // should always work correctly. func (f flashBlockDevice) WriteBlockSize() int64 { return writeBlockSize } const eraseBlockSizeValue = 256 func eraseBlockSize() int64 { return eraseBlockSizeValue } // EraseBlockSize returns the smallest erasable area on this particular chip // in bytes. This is used for the block size in EraseBlocks. func (f flashBlockDevice) EraseBlockSize() int64 { return eraseBlockSize() } // EraseBlocks erases the given number of blocks. An implementation may // transparently coalesce ranges of blocks into larger bundles if the chip // supports this. The start and len parameters are in block numbers, use // EraseBlockSize to map addresses to blocks. func (f flashBlockDevice) EraseBlocks(start, len int64) error { f.ensureInitComplete() address := FlashDataStart() + uintptr(start*f.EraseBlockSize()) waitWhileFlashBusy() for i := start; i < start+len; i++ { sam.NVMCTRL.SetADDR(uint32(address >> 1)) sam.NVMCTRL.CTRLA.Set(sam.NVMCTRL_CTRLA_CMD_ER | (sam.NVMCTRL_CTRLA_CMDEX_KEY << sam.NVMCTRL_CTRLA_CMDEX_Pos)) waitWhileFlashBusy() if err := checkFlashError(); err != nil { return err } address += uintptr(f.EraseBlockSize()) } return nil } func (f flashBlockDevice) ensureInitComplete() { if f.initComplete { return } sam.NVMCTRL.SetCTRLB_READMODE(sam.NVMCTRL_CTRLB_READMODE_NO_MISS_PENALTY) sam.NVMCTRL.SetCTRLB_SLEEPPRM(sam.NVMCTRL_CTRLB_SLEEPPRM_WAKEONACCESS) waitWhileFlashBusy() f.initComplete = true } func waitWhileFlashBusy() { for sam.NVMCTRL.GetINTFLAG_READY() != sam.NVMCTRL_INTFLAG_READY { } } var ( errFlashPROGE = errors.New("errFlashPROGE") errFlashLOCKE = errors.New("errFlashLOCKE") errFlashNVME = errors.New("errFlashNVME") ) func checkFlashError() error { switch { case sam.NVMCTRL.GetSTATUS_PROGE() != 0: return errFlashPROGE case sam.NVMCTRL.GetSTATUS_LOCKE() != 0: return errFlashLOCKE case sam.NVMCTRL.GetSTATUS_NVME() != 0: return errFlashNVME } return nil } // Watchdog provides access to the hardware watchdog available // in the SAMD21. var Watchdog = &watchdogImpl{} const ( // WatchdogMaxTimeout in milliseconds (16s) WatchdogMaxTimeout = (16384 * 1000) / 1024 ) type watchdogImpl struct{} // Configure the watchdog. // // This method should not be called after the watchdog is started and on // some platforms attempting to reconfigure after starting the watchdog // is explicitly forbidden / will not work. func (wd *watchdogImpl) Configure(config WatchdogConfig) error { // Use OSCULP32K as source for Generic Clock Generator 8, divided by 32 to get 1.024kHz sam.GCLK.GENDIV.Set(sam.GCLK_CLKCTRL_GEN_GCLK8 | (32 << sam.GCLK_GENDIV_DIV_Pos)) sam.GCLK.GENCTRL.Set(sam.GCLK_CLKCTRL_GEN_GCLK8 | (sam.GCLK_GENCTRL_SRC_OSCULP32K << sam.GCLK_GENCTRL_SRC_Pos) | sam.GCLK_GENCTRL_GENEN) waitForSync() // Use GCLK8 for watchdog sam.GCLK.CLKCTRL.Set(sam.GCLK_CLKCTRL_ID_WDT | (sam.GCLK_CLKCTRL_GEN_GCLK8 << sam.GCLK_CLKCTRL_GEN_Pos) | sam.GCLK_CLKCTRL_CLKEN) // Power on the watchdog peripheral sam.PM.APBAMASK.SetBits(sam.PM_APBAMASK_WDT_) // 1.024kHz clock cycles := int((int64(config.TimeoutMillis) * 1024) / 1000) // period is expressed as a power-of-two, starting at 8 / 1024ths of a second period := uint8(0) cfgCycles := 8 for cfgCycles < cycles { period++ cfgCycles <<= 1 if period >= 0xB { break } } sam.WDT.CONFIG.Set(period << sam.WDT_CONFIG_PER_Pos) return nil } // Starts the watchdog. func (wd *watchdogImpl) Start() error { sam.WDT.CTRL.SetBits(sam.WDT_CTRL_ENABLE) return nil } // Update the watchdog, indicating that `source` is healthy. func (wd *watchdogImpl) Update() { sam.WDT.CLEAR.Set(sam.WDT_CLEAR_CLEAR_KEY) } ================================================ FILE: src/machine/machine_atsamd21_simulator.go ================================================ //go:build !baremetal && (gemma_m0 || qtpy || trinket_m0 || arduino_mkr1000 || arduino_mkrwifi1010 || arduino_nano33 || arduino_zero || circuitplay_express || feather_m0_express || feather_m0 || itsybitsy_m0 || p1am_100 || xiao) // Simulated atsamd21 chips. package machine // The timer channels/pins match the hardware, and encode the same information // as pinTimerMapping but in a more generic (less efficient) way. var TCC0 = &timerType{ instance: 0, frequency: 48e6, bits: 24, prescalers: []int{1, 2, 4, 8, 16, 64, 256, 1024}, channelPins: [][]Pin{ {PA04, PA08, PB10, PA14, PB16, PA22, PB30}, // channel 0 {PA05, PA09, PB11, PA15, PB17, PA23, PB31}, // channel 1 {PA10, PB12, PA12, PA16, PA18, PA20}, // channel 2 {PA11, PB13, PA13, PA17, PA19, PA21}, // channel 3 }, } var TCC1 = &timerType{ instance: 1, frequency: 48e6, bits: 24, prescalers: []int{1, 2, 4, 8, 16, 64, 256, 1024}, channelPins: [][]Pin{ {PA06, PA10, PA30}, // channel 0 {PA07, PA11, PA31}, // channel 1 {PA08, PA24, PB30}, // channel 2 {PA09, PA25, PB31}, // channel 3 }, } var TCC2 = &timerType{ instance: 2, frequency: 48e6, bits: 16, prescalers: []int{1, 2, 4, 8, 16, 64, 256, 1024}, channelPins: [][]Pin{ {PA00, PA12, PA16}, // channel 0 {PA01, PA13, PA17}, // channel 1 }, } var ( // According to the datasheet, only some pins have I2C support. However it // looks like many boards just use any SERCOM I2C instance, even if the // datasheet says those don't support I2C. I guess they do work in practice, // then. // These are: // * PA00/PA01 for the Adafruit Circuit Playground Express (I2C1, SERCOM1). // * PB02/PB03 for the Adafruit Circuit Playground Express (I2C0, SERCOM5). // * PB08/PB09 for the Arduino Nano 33 IoT (I2C0, SERCOM4). // https://cdn.sparkfun.com/datasheets/Dev/Arduino/Boards/Atmel-42181-SAM-D21_Datasheet.pdf sercomI2CM0 = &I2C{Bus: 0, PinsSDA: []Pin{PA08}, PinsSCL: []Pin{PA09}} sercomI2CM1 = &I2C{Bus: 1, PinsSDA: []Pin{PA00, PA16}, PinsSCL: []Pin{PA01, PA17}} sercomI2CM2 = &I2C{Bus: 2, PinsSDA: []Pin{PA08, PA12}, PinsSCL: []Pin{PA09, PA13}} sercomI2CM3 = &I2C{Bus: 3, PinsSDA: []Pin{PA16, PA22}, PinsSCL: []Pin{PA17, PA23}} sercomI2CM4 = &I2C{Bus: 4, PinsSDA: []Pin{PA12, PB08, PB12}, PinsSCL: []Pin{PA13, PB09, PB13}} sercomI2CM5 = &I2C{Bus: 5, PinsSDA: []Pin{PA22, PB02, PB16, PB30}, PinsSCL: []Pin{PA23, PB03, PB17, PB31}} ) ================================================ FILE: src/machine/machine_atsamd21_usb.go ================================================ //go:build sam && atsamd21 package machine import ( "device/sam" "machine/usb" "runtime/interrupt" "unsafe" ) const ( // these are SAMD21 specific. usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos = 0 usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask = 0x3FFF usb_DEVICE_PCKSIZE_SIZE_Pos = 28 usb_DEVICE_PCKSIZE_SIZE_Mask = 0x7 usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos = 14 usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Mask = 0x3FFF NumberOfUSBEndpoints = 8 ) var ( endPoints = []uint32{ usb.CONTROL_ENDPOINT: usb.ENDPOINT_TYPE_CONTROL, usb.CDC_ENDPOINT_ACM: (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn), usb.CDC_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut), usb.CDC_ENDPOINT_IN: (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn), usb.HID_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt In usb.HID_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt Out usb.MIDI_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Bulk In usb.MIDI_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Bulk Out } ) // Configure the USB peripheral. The config is here for compatibility with the UART interface. func (dev *USBDevice) Configure(config UARTConfig) { if dev.initcomplete { return } // reset USB interface sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_SWRST) for sam.USB_DEVICE.SYNCBUSY.HasBits(sam.USB_DEVICE_SYNCBUSY_SWRST) || sam.USB_DEVICE.SYNCBUSY.HasBits(sam.USB_DEVICE_SYNCBUSY_ENABLE) { } sam.USB_DEVICE.DESCADD.Set(uint32(uintptr(unsafe.Pointer(&usbEndpointDescriptors)))) // configure pins USBCDC_DM_PIN.Configure(PinConfig{Mode: PinCom}) USBCDC_DP_PIN.Configure(PinConfig{Mode: PinCom}) // performs pad calibration from store fuses handlePadCalibration() // run in standby sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_RUNSTDBY) // set full speed sam.USB_DEVICE.CTRLB.SetBits(sam.USB_DEVICE_CTRLB_SPDCONF_FS << sam.USB_DEVICE_CTRLB_SPDCONF_Pos) // attach sam.USB_DEVICE.CTRLB.ClearBits(sam.USB_DEVICE_CTRLB_DETACH) // enable interrupt for end of reset sam.USB_DEVICE.INTENSET.SetBits(sam.USB_DEVICE_INTENSET_EORST) // enable interrupt for start of frame sam.USB_DEVICE.INTENSET.SetBits(sam.USB_DEVICE_INTENSET_SOF) // enable USB sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_ENABLE) // enable IRQ interrupt.New(sam.IRQ_USB, handleUSBIRQ).Enable() dev.initcomplete = true } func handlePadCalibration() { // Load Pad Calibration data from non-volatile memory // This requires registers that are not included in the SVD file. // Modeled after defines from samd21g18a.h and nvmctrl.h: // // #define NVMCTRL_OTP4 0x00806020 // // #define USB_FUSES_TRANSN_ADDR (NVMCTRL_OTP4 + 4) // #define USB_FUSES_TRANSN_Pos 13 /**< \brief (NVMCTRL_OTP4) USB pad Transn calibration */ // #define USB_FUSES_TRANSN_Msk (0x1Fu << USB_FUSES_TRANSN_Pos) // #define USB_FUSES_TRANSN(value) ((USB_FUSES_TRANSN_Msk & ((value) << USB_FUSES_TRANSN_Pos))) // #define USB_FUSES_TRANSP_ADDR (NVMCTRL_OTP4 + 4) // #define USB_FUSES_TRANSP_Pos 18 /**< \brief (NVMCTRL_OTP4) USB pad Transp calibration */ // #define USB_FUSES_TRANSP_Msk (0x1Fu << USB_FUSES_TRANSP_Pos) // #define USB_FUSES_TRANSP(value) ((USB_FUSES_TRANSP_Msk & ((value) << USB_FUSES_TRANSP_Pos))) // #define USB_FUSES_TRIM_ADDR (NVMCTRL_OTP4 + 4) // #define USB_FUSES_TRIM_Pos 23 /**< \brief (NVMCTRL_OTP4) USB pad Trim calibration */ // #define USB_FUSES_TRIM_Msk (0x7u << USB_FUSES_TRIM_Pos) // #define USB_FUSES_TRIM(value) ((USB_FUSES_TRIM_Msk & ((value) << USB_FUSES_TRIM_Pos))) // fuse := *(*uint32)(unsafe.Pointer(uintptr(0x00806020) + 4)) calibTransN := uint16(fuse>>13) & uint16(0x1f) calibTransP := uint16(fuse>>18) & uint16(0x1f) calibTrim := uint16(fuse>>23) & uint16(0x7) if calibTransN == 0x1f { calibTransN = 5 } sam.USB_DEVICE.PADCAL.SetBits(calibTransN << sam.USB_DEVICE_PADCAL_TRANSN_Pos) if calibTransP == 0x1f { calibTransP = 29 } sam.USB_DEVICE.PADCAL.SetBits(calibTransP << sam.USB_DEVICE_PADCAL_TRANSP_Pos) if calibTrim == 0x7 { calibTrim = 3 } sam.USB_DEVICE.PADCAL.SetBits(calibTrim << sam.USB_DEVICE_PADCAL_TRIM_Pos) } func handleUSBIRQ(intr interrupt.Interrupt) { // reset all interrupt flags flags := sam.USB_DEVICE.INTFLAG.Get() sam.USB_DEVICE.INTFLAG.Set(flags) // End of reset if (flags & sam.USB_DEVICE_INTFLAG_EORST) > 0 { // Configure control endpoint initEndpoint(0, usb.ENDPOINT_TYPE_CONTROL) usbConfiguration = 0 // ack the End-Of-Reset interrupt sam.USB_DEVICE.INTFLAG.Set(sam.USB_DEVICE_INTFLAG_EORST) } // Start of frame if (flags & sam.USB_DEVICE_INTFLAG_SOF) > 0 { // if you want to blink LED showing traffic, this would be the place... } // Endpoint 0 Setup interrupt if getEPINTFLAG(0)&sam.USB_DEVICE_EPINTFLAG_RXSTP > 0 { // ack setup received setEPINTFLAG(0, sam.USB_DEVICE_EPINTFLAG_RXSTP) // parse setup setup := usb.NewSetup(udd_ep_out_cache_buffer[0][:]) // Clear the Bank 0 ready flag on Control OUT usbEndpointDescriptors[0].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[0])))) usbEndpointDescriptors[0].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) setEPSTATUSCLR(0, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY) ok := false if (setup.BmRequestType & usb.REQUEST_TYPE) == usb.REQUEST_STANDARD { // Standard Requests ok = handleStandardSetup(setup) } else { // Class Interface Requests if setup.WIndex < uint16(len(usbSetupHandler)) && usbSetupHandler[setup.WIndex] != nil { ok = usbSetupHandler[setup.WIndex](setup) } } if ok { // set Bank1 ready setEPSTATUSSET(0, sam.USB_DEVICE_EPSTATUSSET_BK1RDY) } else { // Stall endpoint setEPSTATUSSET(0, sam.USB_DEVICE_EPINTFLAG_STALL1) } if getEPINTFLAG(0)&sam.USB_DEVICE_EPINTFLAG_STALL1 > 0 { // ack the stall setEPINTFLAG(0, sam.USB_DEVICE_EPINTFLAG_STALL1) // clear stall request setEPINTENCLR(0, sam.USB_DEVICE_EPINTENCLR_STALL1) } } // Now the actual transfer handlers, ignore endpoint number 0 (setup) var i uint32 for i = 1; i < uint32(len(endPoints)); i++ { // Check if endpoint has a pending interrupt epFlags := getEPINTFLAG(i) setEPINTFLAG(i, epFlags) if (epFlags & sam.USB_DEVICE_EPINTFLAG_TRCPT0) > 0 { buf := handleEndpointRx(i) if usbRxHandler[i] == nil || usbRxHandler[i](buf) { AckUsbOutTransfer(i) } } else if (epFlags & sam.USB_DEVICE_EPINTFLAG_TRCPT1) > 0 { if usbTxHandler[i] != nil { usbTxHandler[i]() } } } } func initEndpoint(ep, config uint32) { switch config { case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn: // set packet size usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.SetBits(epPacketSize(64) << usb_DEVICE_PCKSIZE_SIZE_Pos) // set data buffer address usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep])))) // set endpoint type setEPCFG(ep, ((usb.ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_EPCFG_EPTYPE1_Pos)) setEPINTENSET(ep, sam.USB_DEVICE_EPINTENSET_TRCPT1) case usb.ENDPOINT_TYPE_BULK | usb.EndpointOut: // set packet size usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.SetBits(epPacketSize(64) << usb_DEVICE_PCKSIZE_SIZE_Pos) // set data buffer address usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep])))) // set endpoint type setEPCFG(ep, ((usb.ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_EPCFG_EPTYPE0_Pos)) // receive interrupts when current transfer complete setEPINTENSET(ep, sam.USB_DEVICE_EPINTENSET_TRCPT0) // set byte count to zero, we have not received anything yet usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) // ready for next transfer setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY) case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut: // set packet size usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.SetBits(epPacketSize(64) << usb_DEVICE_PCKSIZE_SIZE_Pos) // set data buffer address usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep])))) // set endpoint type setEPCFG(ep, ((usb.ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_EPCFG_EPTYPE0_Pos)) // receive interrupts when current transfer complete setEPINTENSET(ep, sam.USB_DEVICE_EPINTENSET_TRCPT0) // set byte count to zero, we have not received anything yet usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) // ready for next transfer setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY) case usb.ENDPOINT_TYPE_BULK | usb.EndpointIn: // set packet size usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.SetBits(epPacketSize(64) << usb_DEVICE_PCKSIZE_SIZE_Pos) // set data buffer address usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep])))) // set endpoint type setEPCFG(ep, ((usb.ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_EPCFG_EPTYPE1_Pos)) // NAK on endpoint IN, the bank is not yet filled in. setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_BK1RDY) setEPINTENSET(ep, sam.USB_DEVICE_EPINTENSET_TRCPT1) case usb.ENDPOINT_TYPE_CONTROL: // Control OUT // set packet size usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.SetBits(epPacketSize(64) << usb_DEVICE_PCKSIZE_SIZE_Pos) // set data buffer address usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep])))) // set endpoint type setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_CONTROL+1)<> usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) if bytesread != cdcLineInfoSize { return b, ErrUSBBytesRead } copy(b[:7], udd_ep_out_cache_buffer[0][:7]) return b, nil } func handleEndpointRx(ep uint32) []byte { // get data count := int((usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.Get() >> usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) return udd_ep_out_cache_buffer[ep][:count] } // AckUsbOutTransfer is called to acknowledge the completion of a USB OUT transfer. func AckUsbOutTransfer(ep uint32) { // set byte count to zero usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) // set multi packet size to 64 usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.SetBits(64 << usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos) // set ready for next data setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY) } func SendZlp() { usbEndpointDescriptors[0].DeviceDescBank[1].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) } func epPacketSize(size uint16) uint32 { switch size { case 8: return 0 case 16: return 1 case 32: return 2 case 64: return 3 case 128: return 4 case 256: return 5 case 512: return 6 case 1023: return 7 default: return 0 } } func getEPCFG(ep uint32) uint8 { switch ep { case 0: return sam.USB_DEVICE.EPCFG0.Get() case 1: return sam.USB_DEVICE.EPCFG1.Get() case 2: return sam.USB_DEVICE.EPCFG2.Get() case 3: return sam.USB_DEVICE.EPCFG3.Get() case 4: return sam.USB_DEVICE.EPCFG4.Get() case 5: return sam.USB_DEVICE.EPCFG5.Get() case 6: return sam.USB_DEVICE.EPCFG6.Get() case 7: return sam.USB_DEVICE.EPCFG7.Get() default: return 0 } } func setEPCFG(ep uint32, val uint8) { switch ep { case 0: sam.USB_DEVICE.EPCFG0.Set(val) case 1: sam.USB_DEVICE.EPCFG1.Set(val) case 2: sam.USB_DEVICE.EPCFG2.Set(val) case 3: sam.USB_DEVICE.EPCFG3.Set(val) case 4: sam.USB_DEVICE.EPCFG4.Set(val) case 5: sam.USB_DEVICE.EPCFG5.Set(val) case 6: sam.USB_DEVICE.EPCFG6.Set(val) case 7: sam.USB_DEVICE.EPCFG7.Set(val) default: return } } func setEPSTATUSCLR(ep uint32, val uint8) { switch ep { case 0: sam.USB_DEVICE.EPSTATUSCLR0.Set(val) case 1: sam.USB_DEVICE.EPSTATUSCLR1.Set(val) case 2: sam.USB_DEVICE.EPSTATUSCLR2.Set(val) case 3: sam.USB_DEVICE.EPSTATUSCLR3.Set(val) case 4: sam.USB_DEVICE.EPSTATUSCLR4.Set(val) case 5: sam.USB_DEVICE.EPSTATUSCLR5.Set(val) case 6: sam.USB_DEVICE.EPSTATUSCLR6.Set(val) case 7: sam.USB_DEVICE.EPSTATUSCLR7.Set(val) default: return } } func setEPSTATUSSET(ep uint32, val uint8) { switch ep { case 0: sam.USB_DEVICE.EPSTATUSSET0.Set(val) case 1: sam.USB_DEVICE.EPSTATUSSET1.Set(val) case 2: sam.USB_DEVICE.EPSTATUSSET2.Set(val) case 3: sam.USB_DEVICE.EPSTATUSSET3.Set(val) case 4: sam.USB_DEVICE.EPSTATUSSET4.Set(val) case 5: sam.USB_DEVICE.EPSTATUSSET5.Set(val) case 6: sam.USB_DEVICE.EPSTATUSSET6.Set(val) case 7: sam.USB_DEVICE.EPSTATUSSET7.Set(val) default: return } } func getEPSTATUS(ep uint32) uint8 { switch ep { case 0: return sam.USB_DEVICE.EPSTATUS0.Get() case 1: return sam.USB_DEVICE.EPSTATUS1.Get() case 2: return sam.USB_DEVICE.EPSTATUS2.Get() case 3: return sam.USB_DEVICE.EPSTATUS3.Get() case 4: return sam.USB_DEVICE.EPSTATUS4.Get() case 5: return sam.USB_DEVICE.EPSTATUS5.Get() case 6: return sam.USB_DEVICE.EPSTATUS6.Get() case 7: return sam.USB_DEVICE.EPSTATUS7.Get() default: return 0 } } func getEPINTFLAG(ep uint32) uint8 { switch ep { case 0: return sam.USB_DEVICE.EPINTFLAG0.Get() case 1: return sam.USB_DEVICE.EPINTFLAG1.Get() case 2: return sam.USB_DEVICE.EPINTFLAG2.Get() case 3: return sam.USB_DEVICE.EPINTFLAG3.Get() case 4: return sam.USB_DEVICE.EPINTFLAG4.Get() case 5: return sam.USB_DEVICE.EPINTFLAG5.Get() case 6: return sam.USB_DEVICE.EPINTFLAG6.Get() case 7: return sam.USB_DEVICE.EPINTFLAG7.Get() default: return 0 } } func setEPINTFLAG(ep uint32, val uint8) { switch ep { case 0: sam.USB_DEVICE.EPINTFLAG0.Set(val) case 1: sam.USB_DEVICE.EPINTFLAG1.Set(val) case 2: sam.USB_DEVICE.EPINTFLAG2.Set(val) case 3: sam.USB_DEVICE.EPINTFLAG3.Set(val) case 4: sam.USB_DEVICE.EPINTFLAG4.Set(val) case 5: sam.USB_DEVICE.EPINTFLAG5.Set(val) case 6: sam.USB_DEVICE.EPINTFLAG6.Set(val) case 7: sam.USB_DEVICE.EPINTFLAG7.Set(val) default: return } } func setEPINTENCLR(ep uint32, val uint8) { switch ep { case 0: sam.USB_DEVICE.EPINTENCLR0.Set(val) case 1: sam.USB_DEVICE.EPINTENCLR1.Set(val) case 2: sam.USB_DEVICE.EPINTENCLR2.Set(val) case 3: sam.USB_DEVICE.EPINTENCLR3.Set(val) case 4: sam.USB_DEVICE.EPINTENCLR4.Set(val) case 5: sam.USB_DEVICE.EPINTENCLR5.Set(val) case 6: sam.USB_DEVICE.EPINTENCLR6.Set(val) case 7: sam.USB_DEVICE.EPINTENCLR7.Set(val) default: return } } func setEPINTENSET(ep uint32, val uint8) { switch ep { case 0: sam.USB_DEVICE.EPINTENSET0.Set(val) case 1: sam.USB_DEVICE.EPINTENSET1.Set(val) case 2: sam.USB_DEVICE.EPINTENSET2.Set(val) case 3: sam.USB_DEVICE.EPINTENSET3.Set(val) case 4: sam.USB_DEVICE.EPINTENSET4.Set(val) case 5: sam.USB_DEVICE.EPINTENSET5.Set(val) case 6: sam.USB_DEVICE.EPINTENSET6.Set(val) case 7: sam.USB_DEVICE.EPINTENSET7.Set(val) default: return } } ================================================ FILE: src/machine/machine_atsamd21e18.go ================================================ //go:build sam && atsamd21 && atsamd21e18 // Peripheral abstraction layer for the atsamd21. // // Datasheet: // http://ww1.microchip.com/downloads/en/DeviceDoc/SAMD21-Family-DataSheet-DS40001882D.pdf package machine import ( "device/sam" "runtime/interrupt" ) var ( sercomUSART0 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM0_USART, SERCOM: 0} sercomUSART1 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM1_USART, SERCOM: 1} sercomUSART2 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM2_USART, SERCOM: 2} sercomUSART3 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM3_USART, SERCOM: 3} sercomI2CM0 = &I2C{Bus: sam.SERCOM0_I2CM, SERCOM: 0} sercomI2CM1 = &I2C{Bus: sam.SERCOM1_I2CM, SERCOM: 1} sercomI2CM2 = &I2C{Bus: sam.SERCOM2_I2CM, SERCOM: 2} sercomI2CM3 = &I2C{Bus: sam.SERCOM3_I2CM, SERCOM: 3} sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPI, SERCOM: 0} sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPI, SERCOM: 1} sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPI, SERCOM: 2} sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPI, SERCOM: 3} ) func init() { sercomUSART0.Interrupt = interrupt.New(sam.IRQ_SERCOM0, sercomUSART0.handleInterrupt) sercomUSART1.Interrupt = interrupt.New(sam.IRQ_SERCOM1, sercomUSART1.handleInterrupt) sercomUSART2.Interrupt = interrupt.New(sam.IRQ_SERCOM2, sercomUSART2.handleInterrupt) sercomUSART3.Interrupt = interrupt.New(sam.IRQ_SERCOM3, sercomUSART3.handleInterrupt) } // Return the register and mask to enable a given GPIO pin. This can be used to // implement bit-banged drivers. func (p Pin) PortMaskSet() (*uint32, uint32) { return &sam.PORT.OUTSET0.Reg, 1 << uint8(p) } // Return the register and mask to disable a given port. This can be used to // implement bit-banged drivers. func (p Pin) PortMaskClear() (*uint32, uint32) { return &sam.PORT.OUTCLR0.Reg, 1 << uint8(p) } // Set the pin to high or low. // Warning: only use this on an output pin! func (p Pin) Set(high bool) { if high { sam.PORT.OUTSET0.Set(1 << uint8(p)) } else { sam.PORT.OUTCLR0.Set(1 << uint8(p)) } } // Get returns the current value of a GPIO pin when configured as an input or as // an output. func (p Pin) Get() bool { return (sam.PORT.IN0.Get()>>uint8(p))&1 > 0 } // Configure this pin with the given configuration. func (p Pin) Configure(config PinConfig) { switch config.Mode { case PinOutput: sam.PORT.DIRSET0.Set(1 << uint8(p)) // output is also set to input enable so pin can read back its own value p.setPinCfg(sam.PORT_PINCFG0_INEN) case PinInput: sam.PORT.DIRCLR0.Set(1 << uint8(p)) p.setPinCfg(sam.PORT_PINCFG0_INEN) case PinInputPulldown: sam.PORT.DIRCLR0.Set(1 << uint8(p)) sam.PORT.OUTCLR0.Set(1 << uint8(p)) p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN) case PinInputPullup: sam.PORT.DIRCLR0.Set(1 << uint8(p)) sam.PORT.OUTSET0.Set(1 << uint8(p)) p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN) case PinSERCOM: if uint8(p)&1 > 0 { // odd pin, so save the even pins val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk p.setPMux(val | (uint8(PinSERCOM) << sam.PORT_PMUX0_PMUXO_Pos)) } else { // even pin, so save the odd pins val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk p.setPMux(val | (uint8(PinSERCOM) << sam.PORT_PMUX0_PMUXE_Pos)) } // enable port config p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | sam.PORT_PINCFG0_DRVSTR | sam.PORT_PINCFG0_INEN) case PinSERCOMAlt: if uint8(p)&1 > 0 { // odd pin, so save the even pins val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk p.setPMux(val | (uint8(PinSERCOMAlt) << sam.PORT_PMUX0_PMUXO_Pos)) } else { // even pin, so save the odd pins val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk p.setPMux(val | (uint8(PinSERCOMAlt) << sam.PORT_PMUX0_PMUXE_Pos)) } // enable port config p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | sam.PORT_PINCFG0_DRVSTR) case PinCom: if uint8(p)&1 > 0 { // odd pin, so save the even pins val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk p.setPMux(val | (uint8(PinCom) << sam.PORT_PMUX0_PMUXO_Pos)) } else { // even pin, so save the odd pins val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk p.setPMux(val | (uint8(PinCom) << sam.PORT_PMUX0_PMUXE_Pos)) } // enable port config p.setPinCfg(sam.PORT_PINCFG0_PMUXEN) case PinAnalog: if uint8(p)&1 > 0 { // odd pin, so save the even pins val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk p.setPMux(val | (uint8(PinAnalog) << sam.PORT_PMUX0_PMUXO_Pos)) } else { // even pin, so save the odd pins val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk p.setPMux(val | (uint8(PinAnalog) << sam.PORT_PMUX0_PMUXE_Pos)) } // enable port config p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | sam.PORT_PINCFG0_DRVSTR) } } // getPMux returns the value for the correct PMUX register for this pin. func (p Pin) getPMux() uint8 { switch p >> 1 { case 0: return sam.PORT.PMUX0_0.Get() case 1: return sam.PORT.PMUX0_1.Get() case 2: return sam.PORT.PMUX0_2.Get() case 3: return sam.PORT.PMUX0_3.Get() case 4: return sam.PORT.PMUX0_4.Get() case 5: return sam.PORT.PMUX0_5.Get() case 6: return sam.PORT.PMUX0_6.Get() case 7: return sam.PORT.PMUX0_7.Get() case 8: return sam.PORT.PMUX0_8.Get() case 9: return sam.PORT.PMUX0_9.Get() case 10: return sam.PORT.PMUX0_10.Get() case 11: return sam.PORT.PMUX0_11.Get() case 12: return sam.PORT.PMUX0_12.Get() case 13: return sam.PORT.PMUX0_13.Get() case 14: return sam.PORT.PMUX0_14.Get() case 15: return sam.PORT.PMUX0_15.Get() default: return 0 } } // setPMux sets the value for the correct PMUX register for this pin. func (p Pin) setPMux(val uint8) { switch p >> 1 { case 0: sam.PORT.PMUX0_0.Set(val) case 1: sam.PORT.PMUX0_1.Set(val) case 2: sam.PORT.PMUX0_2.Set(val) case 3: sam.PORT.PMUX0_3.Set(val) case 4: sam.PORT.PMUX0_4.Set(val) case 5: sam.PORT.PMUX0_5.Set(val) case 6: sam.PORT.PMUX0_6.Set(val) case 7: sam.PORT.PMUX0_7.Set(val) case 8: sam.PORT.PMUX0_8.Set(val) case 9: sam.PORT.PMUX0_9.Set(val) case 10: sam.PORT.PMUX0_10.Set(val) case 11: sam.PORT.PMUX0_11.Set(val) case 12: sam.PORT.PMUX0_12.Set(val) case 13: sam.PORT.PMUX0_13.Set(val) case 14: sam.PORT.PMUX0_14.Set(val) case 15: sam.PORT.PMUX0_15.Set(val) } } // getPinCfg returns the value for the correct PINCFG register for this pin. func (p Pin) getPinCfg() uint8 { switch p { case 0: return sam.PORT.PINCFG0_0.Get() case 1: return sam.PORT.PINCFG0_1.Get() case 2: return sam.PORT.PINCFG0_2.Get() case 3: return sam.PORT.PINCFG0_3.Get() case 4: return sam.PORT.PINCFG0_4.Get() case 5: return sam.PORT.PINCFG0_5.Get() case 6: return sam.PORT.PINCFG0_6.Get() case 7: return sam.PORT.PINCFG0_7.Get() case 8: return sam.PORT.PINCFG0_8.Get() case 9: return sam.PORT.PINCFG0_9.Get() case 10: return sam.PORT.PINCFG0_10.Get() case 11: return sam.PORT.PINCFG0_11.Get() case 12: return sam.PORT.PINCFG0_12.Get() case 13: return sam.PORT.PINCFG0_13.Get() case 14: return sam.PORT.PINCFG0_14.Get() case 15: return sam.PORT.PINCFG0_15.Get() case 16: return sam.PORT.PINCFG0_16.Get() case 17: return sam.PORT.PINCFG0_17.Get() case 18: return sam.PORT.PINCFG0_18.Get() case 19: return sam.PORT.PINCFG0_19.Get() case 20: return sam.PORT.PINCFG0_20.Get() case 21: return sam.PORT.PINCFG0_21.Get() case 22: return sam.PORT.PINCFG0_22.Get() case 23: return sam.PORT.PINCFG0_23.Get() case 24: return sam.PORT.PINCFG0_24.Get() case 25: return sam.PORT.PINCFG0_25.Get() case 26: return sam.PORT.PINCFG0_26.Get() case 27: return sam.PORT.PINCFG0_27.Get() case 28: return sam.PORT.PINCFG0_28.Get() case 29: return sam.PORT.PINCFG0_29.Get() case 30: return sam.PORT.PINCFG0_30.Get() case 31: return sam.PORT.PINCFG0_31.Get() default: return 0 } } // setPinCfg sets the value for the correct PINCFG register for this pin. func (p Pin) setPinCfg(val uint8) { switch p { case 0: sam.PORT.PINCFG0_0.Set(val) case 1: sam.PORT.PINCFG0_1.Set(val) case 2: sam.PORT.PINCFG0_2.Set(val) case 3: sam.PORT.PINCFG0_3.Set(val) case 4: sam.PORT.PINCFG0_4.Set(val) case 5: sam.PORT.PINCFG0_5.Set(val) case 6: sam.PORT.PINCFG0_6.Set(val) case 7: sam.PORT.PINCFG0_7.Set(val) case 8: sam.PORT.PINCFG0_8.Set(val) case 9: sam.PORT.PINCFG0_9.Set(val) case 10: sam.PORT.PINCFG0_10.Set(val) case 11: sam.PORT.PINCFG0_11.Set(val) case 12: sam.PORT.PINCFG0_12.Set(val) case 13: sam.PORT.PINCFG0_13.Set(val) case 14: sam.PORT.PINCFG0_14.Set(val) case 15: sam.PORT.PINCFG0_15.Set(val) case 16: sam.PORT.PINCFG0_16.Set(val) case 17: sam.PORT.PINCFG0_17.Set(val) case 18: sam.PORT.PINCFG0_18.Set(val) case 19: sam.PORT.PINCFG0_19.Set(val) case 20: sam.PORT.PINCFG0_20.Set(val) case 21: sam.PORT.PINCFG0_21.Set(val) case 22: sam.PORT.PINCFG0_22.Set(val) case 23: sam.PORT.PINCFG0_23.Set(val) case 24: sam.PORT.PINCFG0_24.Set(val) case 25: sam.PORT.PINCFG0_25.Set(val) case 26: sam.PORT.PINCFG0_26.Set(val) case 27: sam.PORT.PINCFG0_27.Set(val) case 28: sam.PORT.PINCFG0_28.Set(val) case 29: sam.PORT.PINCFG0_29.Set(val) case 30: sam.PORT.PINCFG0_30.Set(val) case 31: sam.PORT.PINCFG0_31.Set(val) } } ================================================ FILE: src/machine/machine_atsamd21g18.go ================================================ //go:build sam && atsamd21 && atsamd21g18 // Peripheral abstraction layer for the atsamd21. // // Datasheet: // http://ww1.microchip.com/downloads/en/DeviceDoc/SAMD21-Family-DataSheet-DS40001882D.pdf package machine import ( "device/sam" "runtime/interrupt" ) var ( sercomUSART0 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM0_USART, SERCOM: 0} sercomUSART1 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM1_USART, SERCOM: 1} sercomUSART2 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM2_USART, SERCOM: 2} sercomUSART3 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM3_USART, SERCOM: 3} sercomUSART4 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM4_USART, SERCOM: 4} sercomUSART5 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM5_USART, SERCOM: 5} sercomI2CM0 = &I2C{Bus: sam.SERCOM0_I2CM, SERCOM: 0} sercomI2CM1 = &I2C{Bus: sam.SERCOM1_I2CM, SERCOM: 1} sercomI2CM2 = &I2C{Bus: sam.SERCOM2_I2CM, SERCOM: 2} sercomI2CM3 = &I2C{Bus: sam.SERCOM3_I2CM, SERCOM: 3} sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPI, SERCOM: 0} sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPI, SERCOM: 1} sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPI, SERCOM: 2} sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPI, SERCOM: 3} sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPI, SERCOM: 4} sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPI, SERCOM: 5} ) func init() { sercomUSART0.Interrupt = interrupt.New(sam.IRQ_SERCOM0, sercomUSART0.handleInterrupt) sercomUSART1.Interrupt = interrupt.New(sam.IRQ_SERCOM1, sercomUSART1.handleInterrupt) sercomUSART2.Interrupt = interrupt.New(sam.IRQ_SERCOM2, sercomUSART2.handleInterrupt) sercomUSART3.Interrupt = interrupt.New(sam.IRQ_SERCOM3, sercomUSART3.handleInterrupt) sercomUSART4.Interrupt = interrupt.New(sam.IRQ_SERCOM4, sercomUSART4.handleInterrupt) sercomUSART5.Interrupt = interrupt.New(sam.IRQ_SERCOM5, sercomUSART5.handleInterrupt) } // Return the register and mask to enable a given GPIO pin. This can be used to // implement bit-banged drivers. func (p Pin) PortMaskSet() (*uint32, uint32) { // Note: using PORT_IOBUS for faster pin accesses. // The regular PORT registers appear to take around 4 clock cycles to store, // which is longer than the ws2812 driver expects. The IOBUS is is fast // enough to avoid this issue. if p < 32 { return &sam.PORT_IOBUS.OUTSET0.Reg, 1 << uint8(p) } else { return &sam.PORT_IOBUS.OUTSET1.Reg, 1 << uint8(p-32) } } // Return the register and mask to disable a given port. This can be used to // implement bit-banged drivers. func (p Pin) PortMaskClear() (*uint32, uint32) { if p < 32 { return &sam.PORT_IOBUS.OUTCLR0.Reg, 1 << uint8(p) } else { return &sam.PORT_IOBUS.OUTCLR1.Reg, 1 << uint8(p-32) } } // Set the pin to high or low. // Warning: only use this on an output pin! func (p Pin) Set(high bool) { if p < 32 { if high { sam.PORT.OUTSET0.Set(1 << uint8(p)) } else { sam.PORT.OUTCLR0.Set(1 << uint8(p)) } } else { if high { sam.PORT.OUTSET1.Set(1 << uint8(p-32)) } else { sam.PORT.OUTCLR1.Set(1 << uint8(p-32)) } } } // Get returns the current value of a GPIO pin when configured as an input or as // an output. func (p Pin) Get() bool { if p < 32 { return (sam.PORT.IN0.Get()>>uint8(p))&1 > 0 } else { return (sam.PORT.IN1.Get()>>uint8(p-32))&1 > 0 } } // Configure this pin with the given configuration. func (p Pin) Configure(config PinConfig) { switch config.Mode { case PinOutput: if p < 32 { sam.PORT.DIRSET0.Set(1 << uint8(p)) // output is also set to input enable so pin can read back its own value p.setPinCfg(sam.PORT_PINCFG0_INEN) } else { sam.PORT.DIRSET1.Set(1 << uint8(p-32)) // output is also set to input enable so pin can read back its own value p.setPinCfg(sam.PORT_PINCFG0_INEN) } case PinInput: if p < 32 { sam.PORT.DIRCLR0.Set(1 << uint8(p)) p.setPinCfg(sam.PORT_PINCFG0_INEN) } else { sam.PORT.DIRCLR1.Set(1 << uint8(p-32)) p.setPinCfg(sam.PORT_PINCFG0_INEN) } case PinInputPulldown: if p < 32 { sam.PORT.DIRCLR0.Set(1 << uint8(p)) sam.PORT.OUTCLR0.Set(1 << uint8(p)) p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN) } else { sam.PORT.DIRCLR1.Set(1 << uint8(p-32)) sam.PORT.OUTCLR1.Set(1 << uint8(p-32)) p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN) } case PinInputPullup: if p < 32 { sam.PORT.DIRCLR0.Set(1 << uint8(p)) sam.PORT.OUTSET0.Set(1 << uint8(p)) p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN) } else { sam.PORT.DIRCLR1.Set(1 << uint8(p-32)) sam.PORT.OUTSET1.Set(1 << uint8(p-32)) p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN) } case PinSERCOM: if p&1 > 0 { // odd pin, so save the even pins val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk p.setPMux(val | (uint8(PinSERCOM) << sam.PORT_PMUX0_PMUXO_Pos)) } else { // even pin, so save the odd pins val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk p.setPMux(val | (uint8(PinSERCOM) << sam.PORT_PMUX0_PMUXE_Pos)) } // enable port config p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | sam.PORT_PINCFG0_DRVSTR | sam.PORT_PINCFG0_INEN) case PinSERCOMAlt: if p&1 > 0 { // odd pin, so save the even pins val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk p.setPMux(val | (uint8(PinSERCOMAlt) << sam.PORT_PMUX0_PMUXO_Pos)) } else { // even pin, so save the odd pins val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk p.setPMux(val | (uint8(PinSERCOMAlt) << sam.PORT_PMUX0_PMUXE_Pos)) } // enable port config p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | sam.PORT_PINCFG0_DRVSTR) case PinCom: if p&1 > 0 { // odd pin, so save the even pins val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk p.setPMux(val | (uint8(PinCom) << sam.PORT_PMUX0_PMUXO_Pos)) } else { // even pin, so save the odd pins val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk p.setPMux(val | (uint8(PinCom) << sam.PORT_PMUX0_PMUXE_Pos)) } // enable port config p.setPinCfg(sam.PORT_PINCFG0_PMUXEN) case PinAnalog: if p&1 > 0 { // odd pin, so save the even pins val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk p.setPMux(val | (uint8(PinAnalog) << sam.PORT_PMUX0_PMUXO_Pos)) } else { // even pin, so save the odd pins val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk p.setPMux(val | (uint8(PinAnalog) << sam.PORT_PMUX0_PMUXE_Pos)) } // enable port config p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | sam.PORT_PINCFG0_DRVSTR) } } // getPMux returns the value for the correct PMUX register for this pin. func (p Pin) getPMux() uint8 { switch uint8(p) >> 1 { case 0: return sam.PORT.PMUX0_0.Get() case 1: return sam.PORT.PMUX0_1.Get() case 2: return sam.PORT.PMUX0_2.Get() case 3: return sam.PORT.PMUX0_3.Get() case 4: return sam.PORT.PMUX0_4.Get() case 5: return sam.PORT.PMUX0_5.Get() case 6: return sam.PORT.PMUX0_6.Get() case 7: return sam.PORT.PMUX0_7.Get() case 8: return sam.PORT.PMUX0_8.Get() case 9: return sam.PORT.PMUX0_9.Get() case 10: return sam.PORT.PMUX0_10.Get() case 11: return sam.PORT.PMUX0_11.Get() case 12: return sam.PORT.PMUX0_12.Get() case 13: return sam.PORT.PMUX0_13.Get() case 14: return sam.PORT.PMUX0_14.Get() case 15: return sam.PORT.PMUX0_15.Get() case 16: return uint8(sam.PORT.PMUX1_0.Get()>>0) & 0xff case 17: return uint8(sam.PORT.PMUX1_0.Get()>>8) & 0xff case 18: return uint8(sam.PORT.PMUX1_0.Get()>>16) & 0xff case 19: return uint8(sam.PORT.PMUX1_0.Get()>>24) & 0xff case 20: return uint8(sam.PORT.PMUX1_4.Get()>>0) & 0xff case 21: return uint8(sam.PORT.PMUX1_4.Get()>>8) & 0xff case 22: return uint8(sam.PORT.PMUX1_4.Get()>>16) & 0xff case 23: return uint8(sam.PORT.PMUX1_4.Get()>>24) & 0xff case 24: return uint8(sam.PORT.PMUX1_8.Get()>>0) & 0xff case 25: return uint8(sam.PORT.PMUX1_8.Get()>>8) & 0xff case 26: return uint8(sam.PORT.PMUX1_8.Get()>>16) & 0xff case 27: return uint8(sam.PORT.PMUX1_8.Get()>>24) & 0xff case 28: return uint8(sam.PORT.PMUX1_12.Get()>>0) & 0xff case 29: return uint8(sam.PORT.PMUX1_12.Get()>>8) & 0xff case 30: return uint8(sam.PORT.PMUX1_12.Get()>>16) & 0xff case 31: return uint8(sam.PORT.PMUX1_12.Get()>>24) & 0xff default: return 0 } } // setPMux sets the value for the correct PMUX register for this pin. func (p Pin) setPMux(val uint8) { switch uint8(p) >> 1 { case 0: sam.PORT.PMUX0_0.Set(val) case 1: sam.PORT.PMUX0_1.Set(val) case 2: sam.PORT.PMUX0_2.Set(val) case 3: sam.PORT.PMUX0_3.Set(val) case 4: sam.PORT.PMUX0_4.Set(val) case 5: sam.PORT.PMUX0_5.Set(val) case 6: sam.PORT.PMUX0_6.Set(val) case 7: sam.PORT.PMUX0_7.Set(val) case 8: sam.PORT.PMUX0_8.Set(val) case 9: sam.PORT.PMUX0_9.Set(val) case 10: sam.PORT.PMUX0_10.Set(val) case 11: sam.PORT.PMUX0_11.Set(val) case 12: sam.PORT.PMUX0_12.Set(val) case 13: sam.PORT.PMUX0_13.Set(val) case 14: sam.PORT.PMUX0_14.Set(val) case 15: sam.PORT.PMUX0_15.Set(val) case 16: sam.PORT.PMUX1_0.ReplaceBits(uint32(val), 0xff, 0) case 17: sam.PORT.PMUX1_0.ReplaceBits(uint32(val), 0xff, 8) case 18: sam.PORT.PMUX1_0.ReplaceBits(uint32(val), 0xff, 16) case 19: sam.PORT.PMUX1_0.ReplaceBits(uint32(val), 0xff, 24) case 20: sam.PORT.PMUX1_4.ReplaceBits(uint32(val), 0xff, 0) case 21: sam.PORT.PMUX1_4.ReplaceBits(uint32(val), 0xff, 8) case 22: sam.PORT.PMUX1_4.ReplaceBits(uint32(val), 0xff, 16) case 23: sam.PORT.PMUX1_4.ReplaceBits(uint32(val), 0xff, 24) case 24: sam.PORT.PMUX1_8.ReplaceBits(uint32(val), 0xff, 0) case 25: sam.PORT.PMUX1_8.ReplaceBits(uint32(val), 0xff, 8) case 26: sam.PORT.PMUX1_8.ReplaceBits(uint32(val), 0xff, 16) case 27: sam.PORT.PMUX1_8.ReplaceBits(uint32(val), 0xff, 24) case 28: sam.PORT.PMUX1_12.ReplaceBits(uint32(val), 0xff, 0) case 29: sam.PORT.PMUX1_12.ReplaceBits(uint32(val), 0xff, 8) case 30: sam.PORT.PMUX1_12.ReplaceBits(uint32(val), 0xff, 16) case 31: sam.PORT.PMUX1_12.ReplaceBits(uint32(val), 0xff, 24) } } // getPinCfg returns the value for the correct PINCFG register for this pin. func (p Pin) getPinCfg() uint8 { switch p { case 0: return sam.PORT.PINCFG0_0.Get() case 1: return sam.PORT.PINCFG0_1.Get() case 2: return sam.PORT.PINCFG0_2.Get() case 3: return sam.PORT.PINCFG0_3.Get() case 4: return sam.PORT.PINCFG0_4.Get() case 5: return sam.PORT.PINCFG0_5.Get() case 6: return sam.PORT.PINCFG0_6.Get() case 7: return sam.PORT.PINCFG0_7.Get() case 8: return sam.PORT.PINCFG0_8.Get() case 9: return sam.PORT.PINCFG0_9.Get() case 10: return sam.PORT.PINCFG0_10.Get() case 11: return sam.PORT.PINCFG0_11.Get() case 12: return sam.PORT.PINCFG0_12.Get() case 13: return sam.PORT.PINCFG0_13.Get() case 14: return sam.PORT.PINCFG0_14.Get() case 15: return sam.PORT.PINCFG0_15.Get() case 16: return sam.PORT.PINCFG0_16.Get() case 17: return sam.PORT.PINCFG0_17.Get() case 18: return sam.PORT.PINCFG0_18.Get() case 19: return sam.PORT.PINCFG0_19.Get() case 20: return sam.PORT.PINCFG0_20.Get() case 21: return sam.PORT.PINCFG0_21.Get() case 22: return sam.PORT.PINCFG0_22.Get() case 23: return sam.PORT.PINCFG0_23.Get() case 24: return sam.PORT.PINCFG0_24.Get() case 25: return sam.PORT.PINCFG0_25.Get() case 26: return sam.PORT.PINCFG0_26.Get() case 27: return sam.PORT.PINCFG0_27.Get() case 28: return sam.PORT.PINCFG0_28.Get() case 29: return sam.PORT.PINCFG0_29.Get() case 30: return sam.PORT.PINCFG0_30.Get() case 31: return sam.PORT.PINCFG0_31.Get() case 32: // PB00 return uint8(sam.PORT.PINCFG1_0.Get()>>0) & 0xff case 33: // PB01 return uint8(sam.PORT.PINCFG1_0.Get()>>8) & 0xff case 34: // PB02 return uint8(sam.PORT.PINCFG1_0.Get()>>16) & 0xff case 35: // PB03 return uint8(sam.PORT.PINCFG1_0.Get()>>24) & 0xff case 36: // PB04 return uint8(sam.PORT.PINCFG1_4.Get()>>0) & 0xff case 37: // PB05 return uint8(sam.PORT.PINCFG1_4.Get()>>8) & 0xff case 38: // PB06 return uint8(sam.PORT.PINCFG1_4.Get()>>16) & 0xff case 39: // PB07 return uint8(sam.PORT.PINCFG1_4.Get()>>24) & 0xff case 40: // PB08 return uint8(sam.PORT.PINCFG1_8.Get()>>0) & 0xff case 41: // PB09 return uint8(sam.PORT.PINCFG1_8.Get()>>8) & 0xff case 42: // PB10 return uint8(sam.PORT.PINCFG1_8.Get()>>16) & 0xff case 43: // PB11 return uint8(sam.PORT.PINCFG1_8.Get()>>24) & 0xff case 44: // PB12 return uint8(sam.PORT.PINCFG1_12.Get()>>0) & 0xff case 45: // PB13 return uint8(sam.PORT.PINCFG1_12.Get()>>8) & 0xff case 46: // PB14 return uint8(sam.PORT.PINCFG1_12.Get()>>16) & 0xff case 47: // PB15 return uint8(sam.PORT.PINCFG1_12.Get()>>24) & 0xff case 48: // PB16 return uint8(sam.PORT.PINCFG1_16.Get()>>0) & 0xff case 49: // PB17 return uint8(sam.PORT.PINCFG1_16.Get()>>8) & 0xff case 50: // PB18 return uint8(sam.PORT.PINCFG1_16.Get()>>16) & 0xff case 51: // PB19 return uint8(sam.PORT.PINCFG1_16.Get()>>24) & 0xff case 52: // PB20 return uint8(sam.PORT.PINCFG1_20.Get()>>0) & 0xff case 53: // PB21 return uint8(sam.PORT.PINCFG1_20.Get()>>8) & 0xff case 54: // PB22 return uint8(sam.PORT.PINCFG1_20.Get()>>16) & 0xff case 55: // PB23 return uint8(sam.PORT.PINCFG1_20.Get()>>24) & 0xff case 56: // PB24 return uint8(sam.PORT.PINCFG1_24.Get()>>0) & 0xff case 57: // PB25 return uint8(sam.PORT.PINCFG1_24.Get()>>8) & 0xff case 58: // PB26 return uint8(sam.PORT.PINCFG1_24.Get()>>16) & 0xff case 59: // PB27 return uint8(sam.PORT.PINCFG1_24.Get()>>24) & 0xff case 60: // PB28 return uint8(sam.PORT.PINCFG1_28.Get()>>0) & 0xff case 61: // PB29 return uint8(sam.PORT.PINCFG1_28.Get()>>8) & 0xff case 62: // PB30 return uint8(sam.PORT.PINCFG1_28.Get()>>16) & 0xff case 63: // PB31 return uint8(sam.PORT.PINCFG1_28.Get()>>24) & 0xff default: return 0 } } // setPinCfg sets the value for the correct PINCFG register for this pin. func (p Pin) setPinCfg(val uint8) { switch p { case 0: sam.PORT.PINCFG0_0.Set(val) case 1: sam.PORT.PINCFG0_1.Set(val) case 2: sam.PORT.PINCFG0_2.Set(val) case 3: sam.PORT.PINCFG0_3.Set(val) case 4: sam.PORT.PINCFG0_4.Set(val) case 5: sam.PORT.PINCFG0_5.Set(val) case 6: sam.PORT.PINCFG0_6.Set(val) case 7: sam.PORT.PINCFG0_7.Set(val) case 8: sam.PORT.PINCFG0_8.Set(val) case 9: sam.PORT.PINCFG0_9.Set(val) case 10: sam.PORT.PINCFG0_10.Set(val) case 11: sam.PORT.PINCFG0_11.Set(val) case 12: sam.PORT.PINCFG0_12.Set(val) case 13: sam.PORT.PINCFG0_13.Set(val) case 14: sam.PORT.PINCFG0_14.Set(val) case 15: sam.PORT.PINCFG0_15.Set(val) case 16: sam.PORT.PINCFG0_16.Set(val) case 17: sam.PORT.PINCFG0_17.Set(val) case 18: sam.PORT.PINCFG0_18.Set(val) case 19: sam.PORT.PINCFG0_19.Set(val) case 20: sam.PORT.PINCFG0_20.Set(val) case 21: sam.PORT.PINCFG0_21.Set(val) case 22: sam.PORT.PINCFG0_22.Set(val) case 23: sam.PORT.PINCFG0_23.Set(val) case 24: sam.PORT.PINCFG0_24.Set(val) case 25: sam.PORT.PINCFG0_25.Set(val) case 26: sam.PORT.PINCFG0_26.Set(val) case 27: sam.PORT.PINCFG0_27.Set(val) case 28: sam.PORT.PINCFG0_28.Set(val) case 29: sam.PORT.PINCFG0_29.Set(val) case 30: sam.PORT.PINCFG0_30.Set(val) case 31: sam.PORT.PINCFG0_31.Set(val) case 32: // PB00 sam.PORT.PINCFG1_0.ReplaceBits(uint32(val), 0xff, 0) case 33: // PB01 sam.PORT.PINCFG1_0.ReplaceBits(uint32(val), 0xff, 8) case 34: // PB02 sam.PORT.PINCFG1_0.ReplaceBits(uint32(val), 0xff, 16) case 35: // PB03 sam.PORT.PINCFG1_0.ReplaceBits(uint32(val), 0xff, 24) case 36: // PB04 sam.PORT.PINCFG1_4.ReplaceBits(uint32(val), 0xff, 0) case 37: // PB05 sam.PORT.PINCFG1_4.ReplaceBits(uint32(val), 0xff, 8) case 38: // PB06 sam.PORT.PINCFG1_4.ReplaceBits(uint32(val), 0xff, 16) case 39: // PB07 sam.PORT.PINCFG1_4.ReplaceBits(uint32(val), 0xff, 24) case 40: // PB08 sam.PORT.PINCFG1_8.ReplaceBits(uint32(val), 0xff, 0) case 41: // PB09 sam.PORT.PINCFG1_8.ReplaceBits(uint32(val), 0xff, 8) case 42: // PB10 sam.PORT.PINCFG1_8.ReplaceBits(uint32(val), 0xff, 16) case 43: // PB11 sam.PORT.PINCFG1_8.ReplaceBits(uint32(val), 0xff, 24) case 44: // PB12 sam.PORT.PINCFG1_12.ReplaceBits(uint32(val), 0xff, 0) case 45: // PB13 sam.PORT.PINCFG1_12.ReplaceBits(uint32(val), 0xff, 8) case 46: // PB14 sam.PORT.PINCFG1_12.ReplaceBits(uint32(val), 0xff, 16) case 47: // PB15 sam.PORT.PINCFG1_12.ReplaceBits(uint32(val), 0xff, 24) case 48: // PB16 sam.PORT.PINCFG1_16.ReplaceBits(uint32(val), 0xff, 0) case 49: // PB17 sam.PORT.PINCFG1_16.ReplaceBits(uint32(val), 0xff, 8) case 50: // PB18 sam.PORT.PINCFG1_16.ReplaceBits(uint32(val), 0xff, 16) case 51: // PB19 sam.PORT.PINCFG1_16.ReplaceBits(uint32(val), 0xff, 24) case 52: // PB20 sam.PORT.PINCFG1_20.ReplaceBits(uint32(val), 0xff, 0) case 53: // PB21 sam.PORT.PINCFG1_20.ReplaceBits(uint32(val), 0xff, 8) case 54: // PB22 sam.PORT.PINCFG1_20.ReplaceBits(uint32(val), 0xff, 16) case 55: // PB23 sam.PORT.PINCFG1_20.ReplaceBits(uint32(val), 0xff, 24) case 56: // PB24 sam.PORT.PINCFG1_24.ReplaceBits(uint32(val), 0xff, 0) case 57: // PB25 sam.PORT.PINCFG1_24.ReplaceBits(uint32(val), 0xff, 8) case 58: // PB26 sam.PORT.PINCFG1_24.ReplaceBits(uint32(val), 0xff, 16) case 59: // PB27 sam.PORT.PINCFG1_24.ReplaceBits(uint32(val), 0xff, 24) case 60: // PB28 sam.PORT.PINCFG1_28.ReplaceBits(uint32(val), 0xff, 0) case 61: // PB29 sam.PORT.PINCFG1_28.ReplaceBits(uint32(val), 0xff, 8) case 62: // PB30 sam.PORT.PINCFG1_28.ReplaceBits(uint32(val), 0xff, 16) case 63: // PB31 sam.PORT.PINCFG1_28.ReplaceBits(uint32(val), 0xff, 24) } } ================================================ FILE: src/machine/machine_atsamd51.go ================================================ //go:build (sam && atsamd51) || (sam && atsame5x) // Peripheral abstraction layer for the atsamd51. // // Datasheet: // http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf package machine import ( "device/arm" "device/sam" "errors" "internal/binary" "runtime/interrupt" "unsafe" ) const deviceName = sam.Device // DS60001507, Section 9.6: Serial Number var deviceIDAddr = []uintptr{0x008061FC, 0x00806010, 0x00806014, 0x00806018} func CPUFrequency() uint32 { return 120000000 } const ( PinAnalog PinMode = 1 PinSERCOM PinMode = 2 PinSERCOMAlt PinMode = 3 PinTimer PinMode = 4 PinTimerAlt PinMode = 5 PinTCCPDEC PinMode = 6 PinCom PinMode = 7 PinSDHC PinMode = 8 PinI2S PinMode = 9 PinPCC PinMode = 10 PinGMAC PinMode = 11 PinACCLK PinMode = 12 PinCCL PinMode = 13 PinDigital PinMode = 14 PinInput PinMode = 15 PinInputPullup PinMode = 16 PinOutput PinMode = 17 PinTCCE PinMode = PinTimer PinTCCF PinMode = PinTimerAlt PinTCCG PinMode = PinTCCPDEC PinInputPulldown PinMode = 18 PinCAN PinMode = 19 PinCAN0 PinMode = PinSDHC PinCAN1 PinMode = PinCom ) type PinChange uint8 // Pin change interrupt constants for SetInterrupt. const ( PinRising PinChange = sam.EIC_CONFIG_SENSE0_RISE PinFalling PinChange = sam.EIC_CONFIG_SENSE0_FALL PinToggle PinChange = sam.EIC_CONFIG_SENSE0_BOTH ) // Callbacks to be called for pins configured with SetInterrupt. Unfortunately, // we also need to keep track of which interrupt channel is used by which pin, // as the only alternative would be iterating through all pins. // // We're using the magic constant 16 here because the SAM D21 has 16 interrupt // channels configurable for pins. var ( interruptPins [16]Pin // warning: the value is invalid when pinCallbacks[i] is not set! pinCallbacks [16]func(Pin) ) // Hardware pins const ( PA00 Pin = 0 PA01 Pin = 1 PA02 Pin = 2 PA03 Pin = 3 PA04 Pin = 4 PA05 Pin = 5 PA06 Pin = 6 PA07 Pin = 7 PA08 Pin = 8 // peripherals: TCC0 channel 0, TCC1 channel 4, sercomI2CM0 SDA, sercomI2CM2 SDA PA09 Pin = 9 // peripherals: TCC0 channel 1, TCC1 channel 5, sercomI2CM0 SCL, sercomI2CM2 SCL PA10 Pin = 10 // peripherals: TCC0 channel 2, TCC1 channel 6 PA11 Pin = 11 // peripherals: TCC0 channel 3, TCC1 channel 7 PA12 Pin = 12 // peripherals: TCC0 channel 6, TCC1 channel 2, sercomI2CM2 SDA, sercomI2CM4 SDA PA13 Pin = 13 // peripherals: TCC0 channel 7, TCC1 channel 3, sercomI2CM2 SCL, sercomI2CM4 SCL PA14 Pin = 14 // peripherals: TCC2 channel 0, TCC1 channel 2 PA15 Pin = 15 // peripherals: TCC2 channel 1, TCC1 channel 3 PA16 Pin = 16 // peripherals: TCC1 channel 0, TCC0 channel 4, sercomI2CM1 SDA, sercomI2CM3 SDA PA17 Pin = 17 // peripherals: TCC1 channel 1, TCC0 channel 5, sercomI2CM1 SCL, sercomI2CM3 SCL PA18 Pin = 18 // peripherals: TCC1 channel 2, TCC0 channel 6 PA19 Pin = 19 // peripherals: TCC1 channel 3, TCC0 channel 7 PA20 Pin = 20 // peripherals: TCC1 channel 4, TCC0 channel 0 PA21 Pin = 21 // peripherals: TCC1 channel 5, TCC0 channel 1 PA22 Pin = 22 // peripherals: TCC1 channel 6, TCC0 channel 2, sercomI2CM3 SDA, sercomI2CM5 SDA PA23 Pin = 23 // peripherals: TCC1 channel 7, TCC0 channel 3, sercomI2CM3 SCL, sercomI2CM5 SCL PA24 Pin = 24 // peripherals: TCC2 channel 2 PA25 Pin = 25 // peripherals: TCC2 channel 3 PA26 Pin = 26 PA27 Pin = 27 PA28 Pin = 28 PA29 Pin = 29 PA30 Pin = 30 // peripherals: TCC2 channel 0 PA31 Pin = 31 // peripherals: TCC2 channel 1 PB00 Pin = 32 PB01 Pin = 33 PB02 Pin = 34 // peripherals: TCC2 channel 2 PB03 Pin = 35 // peripherals: TCC2 channel 3 PB04 Pin = 36 PB05 Pin = 37 PB06 Pin = 38 PB07 Pin = 39 PB08 Pin = 40 PB09 Pin = 41 PB10 Pin = 42 // peripherals: TCC0 channel 4, TCC1 channel 0 PB11 Pin = 43 // peripherals: TCC0 channel 5, TCC1 channel 1 PB12 Pin = 44 // peripherals: TCC3 channel 0, TCC0 channel 0 PB13 Pin = 45 // peripherals: TCC3 channel 1, TCC0 channel 1 PB14 Pin = 46 // peripherals: TCC4 channel 0, TCC0 channel 2 PB15 Pin = 47 // peripherals: TCC4 channel 1, TCC0 channel 3 PB16 Pin = 48 // peripherals: TCC3 channel 0, TCC0 channel 4 PB17 Pin = 49 // peripherals: TCC3 channel 1, TCC0 channel 5 PB18 Pin = 50 // peripherals: TCC1 channel 0 PB19 Pin = 51 // peripherals: TCC1 channel 1 PB20 Pin = 52 // peripherals: TCC1 channel 2 PB21 Pin = 53 // peripherals: TCC1 channel 3 PB22 Pin = 54 PB23 Pin = 55 PB24 Pin = 56 PB25 Pin = 57 PB26 Pin = 58 // peripherals: TCC1 channel 2 PB27 Pin = 59 // peripherals: TCC1 channel 3 PB28 Pin = 60 // peripherals: TCC1 channel 4 PB29 Pin = 61 // peripherals: TCC1 channel 5 PB30 Pin = 62 // peripherals: TCC4 channel 0, TCC0 channel 6 PB31 Pin = 63 // peripherals: TCC4 channel 1, TCC0 channel 7 PC00 Pin = 64 PC01 Pin = 65 PC02 Pin = 66 PC03 Pin = 67 PC04 Pin = 68 // peripherals: TCC0 channel 0 PC05 Pin = 69 // peripherals: TCC0 channel 1 PC06 Pin = 70 PC07 Pin = 71 PC08 Pin = 72 PC09 Pin = 73 PC10 Pin = 74 // peripherals: TCC0 channel 0, TCC1 channel 4 PC11 Pin = 75 // peripherals: TCC0 channel 1, TCC1 channel 5 PC12 Pin = 76 // peripherals: TCC0 channel 2, TCC1 channel 6 PC13 Pin = 77 // peripherals: TCC0 channel 3, TCC1 channel 7 PC14 Pin = 78 // peripherals: TCC0 channel 4, TCC1 channel 0 PC15 Pin = 79 // peripherals: TCC0 channel 5, TCC1 channel 1 PC16 Pin = 80 // peripherals: TCC0 channel 0 PC17 Pin = 81 // peripherals: TCC0 channel 1 PC18 Pin = 82 // peripherals: TCC0 channel 2 PC19 Pin = 83 // peripherals: TCC0 channel 3 PC20 Pin = 84 // peripherals: TCC0 channel 4 PC21 Pin = 85 // peripherals: TCC0 channel 5 PC22 Pin = 86 // peripherals: TCC0 channel 6 PC23 Pin = 87 // peripherals: TCC0 channel 7 PC24 Pin = 88 PC25 Pin = 89 PC26 Pin = 90 PC27 Pin = 91 PC28 Pin = 92 PC29 Pin = 93 PC30 Pin = 94 PC31 Pin = 95 PD00 Pin = 96 PD01 Pin = 97 PD02 Pin = 98 PD03 Pin = 99 PD04 Pin = 100 PD05 Pin = 101 PD06 Pin = 102 PD07 Pin = 103 PD08 Pin = 104 // peripherals: TCC0 channel 1, sercomI2CM6 SDA, sercomI2CM7 SDA PD09 Pin = 105 // peripherals: TCC0 channel 2, sercomI2CM6 SCL, sercomI2CM7 SCL PD10 Pin = 106 // peripherals: TCC0 channel 3 PD11 Pin = 107 // peripherals: TCC0 channel 4 PD12 Pin = 108 // peripherals: TCC0 channel 5 PD13 Pin = 109 // peripherals: TCC0 channel 6 PD14 Pin = 110 PD15 Pin = 111 PD16 Pin = 112 PD17 Pin = 113 PD18 Pin = 114 PD19 Pin = 115 PD20 Pin = 116 // peripherals: TCC1 channel 0 PD21 Pin = 117 // peripherals: TCC1 channel 1 PD22 Pin = 118 PD23 Pin = 119 PD24 Pin = 120 PD25 Pin = 121 PD26 Pin = 122 PD27 Pin = 123 PD28 Pin = 124 PD29 Pin = 125 PD30 Pin = 126 PD31 Pin = 127 ) const ( pinPadMapSERCOM0Pad0 uint16 = 0x1000 pinPadMapSERCOM1Pad0 uint16 = 0x2000 pinPadMapSERCOM2Pad0 uint16 = 0x3000 pinPadMapSERCOM3Pad0 uint16 = 0x4000 pinPadMapSERCOM4Pad0 uint16 = 0x5000 pinPadMapSERCOM5Pad0 uint16 = 0x6000 pinPadMapSERCOM6Pad0 uint16 = 0x7000 pinPadMapSERCOM7Pad0 uint16 = 0x8000 pinPadMapSERCOM0Pad2 uint16 = 0x1200 pinPadMapSERCOM1Pad2 uint16 = 0x2200 pinPadMapSERCOM2Pad2 uint16 = 0x3200 pinPadMapSERCOM3Pad2 uint16 = 0x4200 pinPadMapSERCOM4Pad2 uint16 = 0x5200 pinPadMapSERCOM5Pad2 uint16 = 0x6200 pinPadMapSERCOM6Pad2 uint16 = 0x7200 pinPadMapSERCOM7Pad2 uint16 = 0x8200 pinPadMapSERCOM0AltPad0 uint16 = 0x0010 pinPadMapSERCOM1AltPad0 uint16 = 0x0020 pinPadMapSERCOM2AltPad0 uint16 = 0x0030 pinPadMapSERCOM3AltPad0 uint16 = 0x0040 pinPadMapSERCOM4AltPad0 uint16 = 0x0050 pinPadMapSERCOM5AltPad0 uint16 = 0x0060 pinPadMapSERCOM6AltPad0 uint16 = 0x0070 pinPadMapSERCOM7AltPad0 uint16 = 0x0080 pinPadMapSERCOM0AltPad1 uint16 = 0x0011 pinPadMapSERCOM1AltPad1 uint16 = 0x0021 pinPadMapSERCOM2AltPad1 uint16 = 0x0031 pinPadMapSERCOM3AltPad1 uint16 = 0x0041 pinPadMapSERCOM4AltPad1 uint16 = 0x0051 pinPadMapSERCOM5AltPad1 uint16 = 0x0061 pinPadMapSERCOM6AltPad1 uint16 = 0x0071 pinPadMapSERCOM7AltPad1 uint16 = 0x0081 pinPadMapSERCOM0AltPad2 uint16 = 0x0012 pinPadMapSERCOM1AltPad2 uint16 = 0x0022 pinPadMapSERCOM2AltPad2 uint16 = 0x0032 pinPadMapSERCOM3AltPad2 uint16 = 0x0042 pinPadMapSERCOM4AltPad2 uint16 = 0x0052 pinPadMapSERCOM5AltPad2 uint16 = 0x0062 pinPadMapSERCOM6AltPad2 uint16 = 0x0072 pinPadMapSERCOM7AltPad2 uint16 = 0x0082 ) // pinPadMapping lists which pins have which SERCOMs attached to them. // The encoding is rather dense, with each uint16 encoding two pins and both // SERCOM and SERCOM-ALT. // // Observations: // - There are eight SERCOMs. Those SERCOM numbers can be encoded in 4 bits. // - Even pad numbers are usually on even pins, and odd pad numbers are usually // on odd pins. The exception is SERCOM-ALT, which sometimes swaps pad 0 and 1. // With that, there is still an invariant that the pad number for an odd pin is // the pad number for the corresponding even pin with the low bit toggled. // - Pin pads come in pairs. If PA00 has pad 0, then PA01 has pad 1. // // With this information, we can encode SERCOM pin/pad numbers much more // efficiently. Due to pads coming in pairs, we can ignore half the pins: the // information for an odd pin can be calculated easily from the preceding even // pin. // // Each word below is split in two bytes. The 8 high bytes are for SERCOM and // the 8 low bits are for SERCOM-ALT. Of each byte, the 4 high bits encode the // SERCOM + 1 while the two low bits encodes the pad number (the pad number for // the odd pin can be trivially calculated by toggling the low bit of the pad // number). It encodes SERCOM + 1 instead of just the SERCOM number, to make it // easy to check whether a nibble is set at all. // // Datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/60001507E.pdf var pinPadMapping = [64]uint16{ // page 32 PA00 / 2: 0 | pinPadMapSERCOM1AltPad0, // page 33 PB08 / 2: 0 | pinPadMapSERCOM4AltPad0, PA04 / 2: 0 | pinPadMapSERCOM0AltPad0, PA06 / 2: 0 | pinPadMapSERCOM0AltPad2, PC04 / 2: pinPadMapSERCOM6Pad0 | 0, PC06 / 2: pinPadMapSERCOM6Pad2 | 0, PA08 / 2: pinPadMapSERCOM0Pad0 | pinPadMapSERCOM2AltPad1, PA10 / 2: pinPadMapSERCOM0Pad2 | pinPadMapSERCOM2AltPad2, PB10 / 2: 0 | pinPadMapSERCOM4AltPad2, PB12 / 2: pinPadMapSERCOM4Pad0 | 0, PB14 / 2: pinPadMapSERCOM4Pad2 | 0, PD08 / 2: pinPadMapSERCOM7Pad0 | pinPadMapSERCOM6AltPad1, PD10 / 2: pinPadMapSERCOM7Pad2 | pinPadMapSERCOM6AltPad2, PC10 / 2: pinPadMapSERCOM6Pad2 | pinPadMapSERCOM7AltPad2, // page 34 PC12 / 2: pinPadMapSERCOM7Pad0 | pinPadMapSERCOM6AltPad1, PC14 / 2: pinPadMapSERCOM7Pad2 | pinPadMapSERCOM6AltPad2, PA12 / 2: pinPadMapSERCOM2Pad0 | pinPadMapSERCOM4AltPad1, PA14 / 2: pinPadMapSERCOM2Pad2 | pinPadMapSERCOM4AltPad2, PA16 / 2: pinPadMapSERCOM1Pad0 | pinPadMapSERCOM3AltPad1, PA18 / 2: pinPadMapSERCOM1Pad2 | pinPadMapSERCOM3AltPad2, PC16 / 2: pinPadMapSERCOM6Pad0 | pinPadMapSERCOM0AltPad1, PC18 / 2: pinPadMapSERCOM6Pad2 | pinPadMapSERCOM0AltPad2, PC22 / 2: pinPadMapSERCOM1Pad0 | pinPadMapSERCOM3AltPad1, PD20 / 2: pinPadMapSERCOM1Pad2 | pinPadMapSERCOM3AltPad2, PB16 / 2: pinPadMapSERCOM5Pad0 | 0, PB18 / 2: pinPadMapSERCOM5Pad2 | pinPadMapSERCOM7AltPad2, // page 35 PB20 / 2: pinPadMapSERCOM3Pad0 | pinPadMapSERCOM7AltPad1, PA20 / 2: pinPadMapSERCOM5Pad2 | pinPadMapSERCOM3AltPad2, PA22 / 2: pinPadMapSERCOM3Pad0 | pinPadMapSERCOM5AltPad1, PA24 / 2: pinPadMapSERCOM3Pad2 | pinPadMapSERCOM5AltPad2, PB22 / 2: pinPadMapSERCOM1Pad2 | pinPadMapSERCOM5AltPad2, PB24 / 2: pinPadMapSERCOM0Pad0 | pinPadMapSERCOM2AltPad1, PB26 / 2: pinPadMapSERCOM2Pad0 | pinPadMapSERCOM4AltPad1, PB28 / 2: pinPadMapSERCOM2Pad2 | pinPadMapSERCOM4AltPad2, PC24 / 2: pinPadMapSERCOM0Pad2 | pinPadMapSERCOM2AltPad2, //PC26 / 2: pinPadMapSERCOM1Pad1 | 0, // note: PC26 doesn't support SERCOM, but PC27 does //PC28 / 2: pinPadMapSERCOM1Pad1 | 0, // note: PC29 doesn't exist in the datasheet? PA30 / 2: 0 | pinPadMapSERCOM1AltPad2, // page 36 PB30 / 2: 0 | pinPadMapSERCOM5AltPad1, PB00 / 2: 0 | pinPadMapSERCOM5AltPad2, PB02 / 2: 0 | pinPadMapSERCOM5AltPad0, } // findPinPadMapping looks up the pad number and the pinmode for a given pin and // SERCOM number. The result can either be SERCOM, SERCOM-ALT, or "not found" // (indicated by returning ok=false). The pad number is returned to calculate // the DOPO/DIPO bitfields of the various serial peripherals. func findPinPadMapping(sercom uint8, pin Pin) (pinMode PinMode, pad uint32, ok bool) { if int(pin)/2 >= len(pinPadMapping) { // This is probably NoPin, for which no mapping is available. return } bytes := pinPadMapping[pin/2] upper := byte(bytes >> 8) lower := byte(bytes & 0xff) if upper != 0 { // SERCOM if (upper>>4)-1 == sercom { pinMode = PinSERCOM pad |= uint32(upper % 4) ok = true } } if lower != 0 { // SERCOM-ALT if (lower>>4)-1 == sercom { pinMode = PinSERCOMAlt pad |= uint32(lower % 4) ok = true } } if ok { // If the pin is uneven, toggle the lowest bit of the pad number. if pin&1 != 0 { pad ^= 1 } } return } // SetInterrupt sets an interrupt to be executed when a particular pin changes // state. The pin should already be configured as an input, including a pull up // or down if no external pull is provided. // // This call will replace a previously set callback on this pin. You can pass a // nil func to unset the pin change interrupt. If you do so, the change // parameter is ignored and can be set to any value (such as 0). func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { // Most pins follow a common pattern where the EXTINT value is the pin // number modulo 16. However, there are a few exceptions, as you can see // below. extint := uint8(0) switch p { case PA08: // Connected to NMI. This is not currently supported. return ErrInvalidInputPin case PB26: extint = 12 case PB27: extint = 13 case PB28: extint = 14 case PB29: extint = 15 case PC07: extint = 9 case PD08: extint = 3 case PD09: extint = 4 case PD10: extint = 5 case PD11: extint = 6 case PD12: extint = 7 case PD20: extint = 10 case PD21: extint = 11 default: // All other pins follow a normal pattern. extint = uint8(p) % 16 } if callback == nil { // Disable this pin interrupt (if it was enabled). sam.EIC.INTENCLR.Set(1 << extint) if pinCallbacks[extint] != nil { pinCallbacks[extint] = nil } return nil } if pinCallbacks[extint] != nil { // The pin was already configured. // To properly re-configure a pin, unset it first and set a new // configuration. return ErrNoPinChangeChannel } pinCallbacks[extint] = callback interruptPins[extint] = p if !sam.EIC.CTRLA.HasBits(sam.EIC_CTRLA_ENABLE) { // EIC peripheral has not yet been initialized. Initialize it now. // The EIC needs two clocks: CLK_EIC_APB and GCLK_EIC. CLK_EIC_APB is // enabled by default, so doesn't have to be re-enabled. The other is // required for detecting edges and must be enabled manually. sam.GCLK.PCHCTRL[4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // should not be necessary (CLKCTRL is not synchronized) for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL_GCLK0 << sam.GCLK_SYNCBUSY_GENCTRL_Pos) { } } // CONFIG register is enable-protected, so disable EIC. sam.EIC.CTRLA.ClearBits(sam.EIC_CTRLA_ENABLE) // Configure this pin. Set the 4 bits of the EIC.CONFIGx register to the // sense value (filter bit set to 0, sense bits set to the change value). addr := &sam.EIC.CONFIG[0] if extint >= 8 { addr = &sam.EIC.CONFIG[1] } pos := (extint % 8) * 4 // bit position in register addr.ReplaceBits(uint32(change), 0xf, pos) // Enable external interrupt for this pin. sam.EIC.INTENSET.Set(1 << extint) sam.EIC.CTRLA.Set(sam.EIC_CTRLA_ENABLE) for sam.EIC.SYNCBUSY.HasBits(sam.EIC_SYNCBUSY_ENABLE) { } // Set the PMUXEN flag, while keeping the INEN and PULLEN flags (if they // were set before). This avoids clearing the pin pull mode while // configuring the pin interrupt. p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN | (p.getPinCfg() & (sam.PORT_GROUP_PINCFG_INEN | sam.PORT_GROUP_PINCFG_PULLEN))) if p&1 > 0 { // odd pin, so save the even pins val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk p.setPMux(val | (0 << sam.PORT_GROUP_PMUX_PMUXO_Pos)) } else { // even pin, so save the odd pins val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk p.setPMux(val | (0 << sam.PORT_GROUP_PMUX_PMUXE_Pos)) } handleEICInterrupt := func(interrupt.Interrupt) { flags := sam.EIC.INTFLAG.Get() sam.EIC.INTFLAG.Set(flags) // clear interrupt for i := uint(0); i < 16; i++ { // there are 16 channels if flags&(1<>pin_in_group)&1 > 0 } // Toggle switches an output pin from low to high or from high to low. // Warning: only use this on an output pin! func (p Pin) Toggle() { group, pin_in_group := p.getPinGrouping() sam.PORT.GROUP[group].OUTTGL.Set(1 << pin_in_group) } // Configure this pin with the given configuration. func (p Pin) Configure(config PinConfig) { group, pin_in_group := p.getPinGrouping() switch config.Mode { case PinOutput: sam.PORT.GROUP[group].DIRSET.Set(1 << pin_in_group) // output is also set to input enable so pin can read back its own value p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN) case PinInput: sam.PORT.GROUP[group].DIRCLR.Set(1 << pin_in_group) p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN) case PinInputPulldown: sam.PORT.GROUP[group].DIRCLR.Set(1 << pin_in_group) sam.PORT.GROUP[group].OUTCLR.Set(1 << pin_in_group) p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN | sam.PORT_GROUP_PINCFG_PULLEN) case PinInputPullup: sam.PORT.GROUP[group].DIRCLR.Set(1 << pin_in_group) sam.PORT.GROUP[group].OUTSET.Set(1 << pin_in_group) p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN | sam.PORT_GROUP_PINCFG_PULLEN) case PinSERCOM: if p&1 > 0 { // odd pin, so save the even pins val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk p.setPMux(val | (uint8(PinSERCOM) << sam.PORT_GROUP_PMUX_PMUXO_Pos)) } else { // even pin, so save the odd pins val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk p.setPMux(val | (uint8(PinSERCOM) << sam.PORT_GROUP_PMUX_PMUXE_Pos)) } // enable port config p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN | sam.PORT_GROUP_PINCFG_DRVSTR | sam.PORT_GROUP_PINCFG_INEN) case PinSERCOMAlt: if p&1 > 0 { // odd pin, so save the even pins val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk p.setPMux(val | (uint8(PinSERCOMAlt) << sam.PORT_GROUP_PMUX_PMUXO_Pos)) } else { // even pin, so save the odd pins val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk p.setPMux(val | (uint8(PinSERCOMAlt) << sam.PORT_GROUP_PMUX_PMUXE_Pos)) } // enable port config p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN | sam.PORT_GROUP_PINCFG_DRVSTR) case PinCom: if p&1 > 0 { // odd pin, so save the even pins val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk p.setPMux(val | (uint8(PinCom) << sam.PORT_GROUP_PMUX_PMUXO_Pos)) } else { // even pin, so save the odd pins val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk p.setPMux(val | (uint8(PinCom) << sam.PORT_GROUP_PMUX_PMUXE_Pos)) } // enable port config p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN) case PinAnalog: if p&1 > 0 { // odd pin, so save the even pins val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk p.setPMux(val | (uint8(PinAnalog) << sam.PORT_GROUP_PMUX_PMUXO_Pos)) } else { // even pin, so save the odd pins val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk p.setPMux(val | (uint8(PinAnalog) << sam.PORT_GROUP_PMUX_PMUXE_Pos)) } // enable port config p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN | sam.PORT_GROUP_PINCFG_DRVSTR) case PinSDHC: if p&1 > 0 { // odd pin, so save the even pins val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk p.setPMux(val | (uint8(PinSDHC) << sam.PORT_GROUP_PMUX_PMUXO_Pos)) } else { // even pin, so save the odd pins val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk p.setPMux(val | (uint8(PinSDHC) << sam.PORT_GROUP_PMUX_PMUXE_Pos)) } // enable port config p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN) } } // getPMux returns the value for the correct PMUX register for this pin. func (p Pin) getPMux() uint8 { group, pin_in_group := p.getPinGrouping() return sam.PORT.GROUP[group].PMUX[pin_in_group>>1].Get() } // setPMux sets the value for the correct PMUX register for this pin. func (p Pin) setPMux(val uint8) { group, pin_in_group := p.getPinGrouping() sam.PORT.GROUP[group].PMUX[pin_in_group>>1].Set(val) } // getPinCfg returns the value for the correct PINCFG register for this pin. func (p Pin) getPinCfg() uint8 { group, pin_in_group := p.getPinGrouping() return sam.PORT.GROUP[group].PINCFG[pin_in_group].Get() } // setPinCfg sets the value for the correct PINCFG register for this pin. func (p Pin) setPinCfg(val uint8) { group, pin_in_group := p.getPinGrouping() sam.PORT.GROUP[group].PINCFG[pin_in_group].Set(val) } // getPinGrouping calculates the gpio group and pin id from the pin number. // Pins are split into groups of 32, and each group has its own set of // control registers. func (p Pin) getPinGrouping() (uint8, uint8) { group := uint8(p) >> 5 pin_in_group := uint8(p) & 0x1f return group, pin_in_group } // InitADC initializes the ADC. func InitADC() { // ADC Bias Calibration // NVMCTRL_SW0 0x00800080 // #define ADC0_FUSES_BIASCOMP_ADDR NVMCTRL_SW0 // #define ADC0_FUSES_BIASCOMP_Pos 2 /**< \brief (NVMCTRL_SW0) ADC Comparator Scaling */ // #define ADC0_FUSES_BIASCOMP_Msk (_Ul(0x7) << ADC0_FUSES_BIASCOMP_Pos) // #define ADC0_FUSES_BIASCOMP(value) (ADC0_FUSES_BIASCOMP_Msk & ((value) << ADC0_FUSES_BIASCOMP_Pos)) // #define ADC0_FUSES_BIASR2R_ADDR NVMCTRL_SW0 // #define ADC0_FUSES_BIASR2R_Pos 8 /**< \brief (NVMCTRL_SW0) ADC Bias R2R ampli scaling */ // #define ADC0_FUSES_BIASR2R_Msk (_Ul(0x7) << ADC0_FUSES_BIASR2R_Pos) // #define ADC0_FUSES_BIASR2R(value) (ADC0_FUSES_BIASR2R_Msk & ((value) << ADC0_FUSES_BIASR2R_Pos)) // #define ADC0_FUSES_BIASREFBUF_ADDR NVMCTRL_SW0 // #define ADC0_FUSES_BIASREFBUF_Pos 5 /**< \brief (NVMCTRL_SW0) ADC Bias Reference Buffer Scaling */ // #define ADC0_FUSES_BIASREFBUF_Msk (_Ul(0x7) << ADC0_FUSES_BIASREFBUF_Pos) // #define ADC0_FUSES_BIASREFBUF(value) (ADC0_FUSES_BIASREFBUF_Msk & ((value) << ADC0_FUSES_BIASREFBUF_Pos)) // #define ADC1_FUSES_BIASCOMP_ADDR NVMCTRL_SW0 // #define ADC1_FUSES_BIASCOMP_Pos 16 /**< \brief (NVMCTRL_SW0) ADC Comparator Scaling */ // #define ADC1_FUSES_BIASCOMP_Msk (_Ul(0x7) << ADC1_FUSES_BIASCOMP_Pos) // #define ADC1_FUSES_BIASCOMP(value) (ADC1_FUSES_BIASCOMP_Msk & ((value) << ADC1_FUSES_BIASCOMP_Pos)) // #define ADC1_FUSES_BIASR2R_ADDR NVMCTRL_SW0 // #define ADC1_FUSES_BIASR2R_Pos 22 /**< \brief (NVMCTRL_SW0) ADC Bias R2R ampli scaling */ // #define ADC1_FUSES_BIASR2R_Msk (_Ul(0x7) << ADC1_FUSES_BIASR2R_Pos) // #define ADC1_FUSES_BIASR2R(value) (ADC1_FUSES_BIASR2R_Msk & ((value) << ADC1_FUSES_BIASR2R_Pos)) // #define ADC1_FUSES_BIASREFBUF_ADDR NVMCTRL_SW0 // #define ADC1_FUSES_BIASREFBUF_Pos 19 /**< \brief (NVMCTRL_SW0) ADC Bias Reference Buffer Scaling */ // #define ADC1_FUSES_BIASREFBUF_Msk (_Ul(0x7) << ADC1_FUSES_BIASREFBUF_Pos) // #define ADC1_FUSES_BIASREFBUF(value) (ADC1_FUSES_BIASREFBUF_Msk & ((value) << ADC1_FUSES_BIASREFBUF_Pos)) adcFuse := *(*uint32)(unsafe.Pointer(uintptr(0x00800080))) // uint32_t biascomp = (*((uint32_t *)ADC0_FUSES_BIASCOMP_ADDR) & ADC0_FUSES_BIASCOMP_Msk) >> ADC0_FUSES_BIASCOMP_Pos; biascomp := (adcFuse & uint32(0x7<<2)) //>> 2 // uint32_t biasr2r = (*((uint32_t *)ADC0_FUSES_BIASR2R_ADDR) & ADC0_FUSES_BIASR2R_Msk) >> ADC0_FUSES_BIASR2R_Pos; biasr2r := (adcFuse & uint32(0x7<<8)) //>> 8 // uint32_t biasref = (*((uint32_t *)ADC0_FUSES_BIASREFBUF_ADDR) & ADC0_FUSES_BIASREFBUF_Msk) >> ADC0_FUSES_BIASREFBUF_Pos; biasref := (adcFuse & uint32(0x7<<5)) //>> 5 // calibrate ADC0 sam.ADC0.CALIB.Set(uint16(biascomp | biasr2r | biasref)) // biascomp = (*((uint32_t *)ADC1_FUSES_BIASCOMP_ADDR) & ADC1_FUSES_BIASCOMP_Msk) >> ADC1_FUSES_BIASCOMP_Pos; biascomp = (adcFuse & uint32(0x7<<16)) //>> 16 // biasr2r = (*((uint32_t *)ADC1_FUSES_BIASR2R_ADDR) & ADC1_FUSES_BIASR2R_Msk) >> ADC1_FUSES_BIASR2R_Pos; biasr2r = (adcFuse & uint32(0x7<<22)) //>> 22 // biasref = (*((uint32_t *)ADC1_FUSES_BIASREFBUF_ADDR) & ADC1_FUSES_BIASREFBUF_Msk) >> ADC1_FUSES_BIASREFBUF_Pos; biasref = (adcFuse & uint32(0x7<<19)) //>> 19 // calibrate ADC1 sam.ADC1.CALIB.Set(uint16((biascomp | biasr2r | biasref) >> 16)) } // Configure configures a ADCPin to be able to be used to read data. func (a ADC) Configure(config ADCConfig) { for _, adc := range []*sam.ADC_Type{sam.ADC0, sam.ADC1} { for adc.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_CTRLB) { } // wait for sync // Averaging (see datasheet table in AVGCTRL register description) var resolution uint32 = sam.ADC_CTRLB_RESSEL_16BIT var samples uint32 switch config.Samples { case 2: samples = sam.ADC_AVGCTRL_SAMPLENUM_2 case 4: samples = sam.ADC_AVGCTRL_SAMPLENUM_4 case 8: samples = sam.ADC_AVGCTRL_SAMPLENUM_8 case 16: samples = sam.ADC_AVGCTRL_SAMPLENUM_16 case 32: samples = sam.ADC_AVGCTRL_SAMPLENUM_32 case 64: samples = sam.ADC_AVGCTRL_SAMPLENUM_64 case 128: samples = sam.ADC_AVGCTRL_SAMPLENUM_128 case 256: samples = sam.ADC_AVGCTRL_SAMPLENUM_256 case 512: samples = sam.ADC_AVGCTRL_SAMPLENUM_512 case 1024: samples = sam.ADC_AVGCTRL_SAMPLENUM_1024 default: // 1 sample only (no oversampling nor averaging), adjusting result by 0 // Resolutions less than 16 bits only make sense when sampling only // once. Resulting ADC values become erratic when using both // multi-sampling and less than 16 bits of resolution. samples = sam.ADC_AVGCTRL_SAMPLENUM_1 switch config.Resolution { case 8: resolution = sam.ADC_CTRLB_RESSEL_8BIT case 10: resolution = sam.ADC_CTRLB_RESSEL_10BIT case 12: resolution = sam.ADC_CTRLB_RESSEL_12BIT case 16: resolution = sam.ADC_CTRLB_RESSEL_16BIT default: resolution = sam.ADC_CTRLB_RESSEL_12BIT } } adc.AVGCTRL.Set(uint8(samples<> sam.ADC_CTRLB_RESSEL_Pos { case sam.ADC_CTRLB_RESSEL_8BIT: val = val << 8 case sam.ADC_CTRLB_RESSEL_10BIT: val = val << 6 case sam.ADC_CTRLB_RESSEL_12BIT: val = val << 4 case sam.ADC_CTRLB_RESSEL_16BIT: // Adjust for multiple samples. This is only configured when the // resolution is 16 bits. switch (bus.AVGCTRL.Get() & sam.ADC_AVGCTRL_SAMPLENUM_Msk) >> sam.ADC_AVGCTRL_SAMPLENUM_Pos { case sam.ADC_AVGCTRL_SAMPLENUM_1: val <<= 4 case sam.ADC_AVGCTRL_SAMPLENUM_2: val <<= 3 case sam.ADC_AVGCTRL_SAMPLENUM_4: val <<= 2 case sam.ADC_AVGCTRL_SAMPLENUM_8: val <<= 1 default: // These values are all shifted by the hardware so they fit exactly // in a 16-bit integer, so they don't need to be shifted here. } } return val } func (a ADC) getADCBus() *sam.ADC_Type { if (a.Pin >= PB04 && a.Pin <= PB07) || (a.Pin >= PC00) { return sam.ADC1 } return sam.ADC0 } func (a ADC) getADCChannel() uint8 { switch a.Pin { case PA02: return 0 case PB08: return 2 case PB09: return 3 case PA04: return 4 case PA05: return 5 case PA06: return 6 case PA07: return 7 case PB00: return 12 case PB01: return 13 case PB02: return 14 case PB03: return 15 case PA09: return 17 case PA11: return 19 case PB04: return 6 case PB05: return 7 case PB06: return 8 case PB07: return 9 case PC00: return 10 case PC01: return 11 case PC02: return 4 case PC03: return 5 case PC30: return 12 case PC31: return 13 case PD00: return 14 case PD01: return 15 default: panic("Invalid ADC pin") } } // UART on the SAMD51. type UART struct { Buffer *RingBuffer Bus *sam.SERCOM_USART_INT_Type SERCOM uint8 Interrupt interrupt.Interrupt // RXC interrupt } var ( sercomUSART0 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM0_USART_INT, SERCOM: 0} sercomUSART1 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM1_USART_INT, SERCOM: 1} sercomUSART2 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM2_USART_INT, SERCOM: 2} sercomUSART3 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM3_USART_INT, SERCOM: 3} sercomUSART4 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM4_USART_INT, SERCOM: 4} sercomUSART5 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM5_USART_INT, SERCOM: 5} ) func init() { sercomUSART0.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, sercomUSART0.handleInterrupt) sercomUSART1.Interrupt = interrupt.New(sam.IRQ_SERCOM1_2, sercomUSART1.handleInterrupt) sercomUSART2.Interrupt = interrupt.New(sam.IRQ_SERCOM2_2, sercomUSART2.handleInterrupt) sercomUSART3.Interrupt = interrupt.New(sam.IRQ_SERCOM3_2, sercomUSART3.handleInterrupt) sercomUSART4.Interrupt = interrupt.New(sam.IRQ_SERCOM4_2, sercomUSART4.handleInterrupt) sercomUSART5.Interrupt = interrupt.New(sam.IRQ_SERCOM5_2, sercomUSART5.handleInterrupt) } const ( sampleRate16X = 16 lsbFirst = 1 ) // Configure the UART. func (uart *UART) Configure(config UARTConfig) error { // Default baud rate to 115200. if config.BaudRate == 0 { config.BaudRate = 115200 } // determine pins if config.TX == 0 && config.RX == 0 { // use default pins config.TX = UART_TX_PIN config.RX = UART_RX_PIN } // Determine transmit pinout. txPinMode, txPad, ok := findPinPadMapping(uart.SERCOM, config.TX) if !ok { return ErrInvalidOutputPin } var txPadOut uint32 // See CTRLA.RXPO bits of the SERCOM USART peripheral (page 945-946) for how // pads are mapped to pinout values. switch txPad { case 0: txPadOut = 0 default: // should be flow control (RTS/CTS) pin return ErrInvalidOutputPin } // Determine receive pinout. rxPinMode, rxPad, ok := findPinPadMapping(uart.SERCOM, config.RX) if !ok { return ErrInvalidInputPin } // As you can see in the CTRLA.RXPO bits of the SERCOM USART peripheral // (page 945), input pins are mapped directly. rxPadOut := rxPad // configure pins config.TX.Configure(PinConfig{Mode: txPinMode}) config.RX.Configure(PinConfig{Mode: rxPinMode}) // configure RTS/CTS pins if provided if config.RTS != 0 && config.CTS != 0 { rtsPinMode, _, ok := findPinPadMapping(uart.SERCOM, config.RTS) if !ok { return ErrInvalidOutputPin } ctsPinMode, _, ok := findPinPadMapping(uart.SERCOM, config.CTS) if !ok { return ErrInvalidInputPin } // See CTRLA.RXPO bits of the SERCOM USART peripheral (page 945-946) for how // pads are mapped to pinout values. txPadOut = 2 config.RTS.Configure(PinConfig{Mode: rtsPinMode}) config.CTS.Configure(PinConfig{Mode: ctsPinMode}) } // reset SERCOM uart.Bus.CTRLA.SetBits(sam.SERCOM_USART_INT_CTRLA_SWRST) for uart.Bus.CTRLA.HasBits(sam.SERCOM_USART_INT_CTRLA_SWRST) || uart.Bus.SYNCBUSY.HasBits(sam.SERCOM_USART_INT_SYNCBUSY_SWRST) { } // set UART mode/sample rate // SERCOM_USART_CTRLA_MODE(mode) | // SERCOM_USART_CTRLA_SAMPR(sampleRate); // sam.SERCOM_USART_CTRLA_MODE_USART_INT_CLK = 1? uart.Bus.CTRLA.Set((1 << sam.SERCOM_USART_INT_CTRLA_MODE_Pos) | (1 << sam.SERCOM_USART_INT_CTRLA_SAMPR_Pos)) // sample rate of 16x // set clock setSERCOMClockGenerator(uart.SERCOM, sam.GCLK_PCHCTRL_GEN_GCLK1) // Set baud rate uart.SetBaudRate(config.BaudRate) // setup UART frame // SERCOM_USART_CTRLA_FORM( (parityMode == SERCOM_NO_PARITY ? 0 : 1) ) | // dataOrder << SERCOM_USART_CTRLA_DORD_Pos; uart.Bus.CTRLA.SetBits((0 << sam.SERCOM_USART_INT_CTRLA_FORM_Pos) | // no parity (lsbFirst << sam.SERCOM_USART_INT_CTRLA_DORD_Pos)) // data order // set UART stop bits/parity // SERCOM_USART_CTRLB_CHSIZE(charSize) | // nbStopBits << SERCOM_USART_CTRLB_SBMODE_Pos | // (parityMode == SERCOM_NO_PARITY ? 0 : parityMode) << SERCOM_USART_CTRLB_PMODE_Pos; //If no parity use default value uart.Bus.CTRLB.SetBits((0 << sam.SERCOM_USART_INT_CTRLB_CHSIZE_Pos) | // 8 bits is 0 (0 << sam.SERCOM_USART_INT_CTRLB_SBMODE_Pos) | // 1 stop bit is zero (0 << sam.SERCOM_USART_INT_CTRLB_PMODE_Pos)) // no parity // set UART pads. This is not same as pins... // SERCOM_USART_CTRLA_TXPO(txPad) | // SERCOM_USART_CTRLA_RXPO(rxPad); uart.Bus.CTRLA.SetBits((txPadOut << sam.SERCOM_USART_INT_CTRLA_TXPO_Pos) | (rxPadOut << sam.SERCOM_USART_INT_CTRLA_RXPO_Pos)) // Enable Transceiver and Receiver //sercom->USART.CTRLB.reg |= SERCOM_USART_CTRLB_TXEN | SERCOM_USART_CTRLB_RXEN ; uart.Bus.CTRLB.SetBits(sam.SERCOM_USART_INT_CTRLB_TXEN | sam.SERCOM_USART_INT_CTRLB_RXEN) // Enable USART1 port. // sercom->USART.CTRLA.bit.ENABLE = 0x1u; uart.Bus.CTRLA.SetBits(sam.SERCOM_USART_INT_CTRLA_ENABLE) for uart.Bus.SYNCBUSY.HasBits(sam.SERCOM_USART_INT_SYNCBUSY_ENABLE) { } // setup interrupt on receive uart.Bus.INTENSET.Set(sam.SERCOM_USART_INT_INTENSET_RXC) // Enable RX IRQ. // This is a small note at the bottom of the NVIC section of the datasheet: // > The integer number specified in the source refers to the respective bit // > position in the INTFLAG register of respective peripheral. // Therefore, if we only need to listen to the RXC interrupt source (in bit // position 2), we only need interrupt source 2 for this SERCOM device. uart.Interrupt.Enable() return nil } // SetBaudRate sets the communication speed for the UART. func (uart *UART) SetBaudRate(br uint32) { // Asynchronous fractional mode (Table 24-2 in datasheet) // BAUD = fref / (sampleRateValue * fbaud) // (multiply by 8, to calculate fractional piece) // uint32_t baudTimes8 = (SystemCoreClock * 8) / (16 * baudrate); baud := (SERCOM_FREQ_REF * 8) / (sampleRate16X * br) // sercom->USART.BAUD.FRAC.FP = (baudTimes8 % 8); // sercom->USART.BAUD.FRAC.BAUD = (baudTimes8 / 8); uart.Bus.BAUD.Set(uint16(((baud % 8) << sam.SERCOM_USART_INT_BAUD_FRAC_MODE_FP_Pos) | ((baud / 8) << sam.SERCOM_USART_INT_BAUD_FRAC_MODE_BAUD_Pos))) } // WriteByte writes a byte of data to the UART. func (uart *UART) writeByte(c byte) error { // wait until ready to receive for !uart.Bus.INTFLAG.HasBits(sam.SERCOM_USART_INT_INTFLAG_DRE) { } uart.Bus.DATA.Set(uint32(c)) return nil } func (uart *UART) flush() {} func (uart *UART) handleInterrupt(interrupt.Interrupt) { // should reset IRQ uart.Receive(byte((uart.Bus.DATA.Get() & 0xFF))) uart.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INT_INTFLAG_RXC) } // I2C on the SAMD51. type I2C struct { Bus *sam.SERCOM_I2CM_Type SERCOM uint8 } // I2CConfig is used to store config info for I2C. type I2CConfig struct { Frequency uint32 SCL Pin SDA Pin } const ( // SERCOM_FREQ_REF is always reference frequency on SAMD51 regardless of CPU speed. SERCOM_FREQ_REF = 48000000 SERCOM_FREQ_REF_GCLK0 = 120000000 // Default rise time in nanoseconds, based on 4.7K ohm pull up resistors riseTimeNanoseconds = 125 // wire bus states wireUnknownState = 0 wireIdleState = 1 wireOwnerState = 2 wireBusyState = 3 // wire commands wireCmdNoAction = 0 wireCmdRepeatStart = 1 wireCmdRead = 2 wireCmdStop = 3 ) const i2cTimeout = 28000 // about 210us // Configure is intended to setup the I2C interface. func (i2c *I2C) Configure(config I2CConfig) error { // Default I2C bus speed is 100 kHz. if config.Frequency == 0 { config.Frequency = 100 * KHz } // Use default I2C pins if not set. if config.SDA == 0 && config.SCL == 0 { config.SDA = SDA_PIN config.SCL = SCL_PIN } sclPinMode, sclPad, ok := findPinPadMapping(i2c.SERCOM, config.SCL) if !ok || sclPad != 1 { // SCL must be on pad 1, according to section 36.4 of the datasheet. // Note: this is not an exhaustive test for I2C support on the pin: not // all pins support I2C. return ErrInvalidClockPin } sdaPinMode, sdaPad, ok := findPinPadMapping(i2c.SERCOM, config.SDA) if !ok || sdaPad != 0 { // SDA must be on pad 0, according to section 36.4 of the datasheet. // Note: this is not an exhaustive test for I2C support on the pin: not // all pins support I2C. return ErrInvalidDataPin } // reset SERCOM i2c.Bus.CTRLA.SetBits(sam.SERCOM_I2CM_CTRLA_SWRST) for i2c.Bus.CTRLA.HasBits(sam.SERCOM_I2CM_CTRLA_SWRST) || i2c.Bus.SYNCBUSY.HasBits(sam.SERCOM_I2CM_SYNCBUSY_SWRST) { } // set clock setSERCOMClockGenerator(i2c.SERCOM, sam.GCLK_PCHCTRL_GEN_GCLK1) // Set i2c controller mode //SERCOM_I2CM_CTRLA_MODE( I2C_MASTER_OPERATION ) // sam.SERCOM_I2CM_CTRLA_MODE_I2C_MASTER = 5? i2c.Bus.CTRLA.Set(5 << sam.SERCOM_I2CM_CTRLA_MODE_Pos) // | i2c.SetBaudRate(config.Frequency) // Enable I2CM port. // sercom->USART.CTRLA.bit.ENABLE = 0x1u; i2c.Bus.CTRLA.SetBits(sam.SERCOM_I2CM_CTRLA_ENABLE) for i2c.Bus.SYNCBUSY.HasBits(sam.SERCOM_I2CM_SYNCBUSY_ENABLE) { } // set bus idle mode i2c.Bus.STATUS.SetBits(wireIdleState << sam.SERCOM_I2CM_STATUS_BUSSTATE_Pos) for i2c.Bus.SYNCBUSY.HasBits(sam.SERCOM_I2CM_SYNCBUSY_SYSOP) { } // enable pins config.SDA.Configure(PinConfig{Mode: sdaPinMode}) config.SCL.Configure(PinConfig{Mode: sclPinMode}) return nil } // SetBaudRate sets the communication speed for I2C. func (i2c *I2C) SetBaudRate(br uint32) error { // Synchronous arithmetic baudrate, via Adafruit SAMD51 implementation: // sercom->I2CM.BAUD.bit.BAUD = SERCOM_FREQ_REF / ( 2 * baudrate) - 1 ; baud := SERCOM_FREQ_REF/(2*br) - 1 i2c.Bus.BAUD.Set(baud) return nil } // Tx does a single I2C transaction at the specified address. // It clocks out the given address, writes the bytes in w, reads back len(r) // bytes and stores them in r, and generates a stop condition on the bus. func (i2c *I2C) Tx(addr uint16, w, r []byte) error { var err error if len(w) != 0 { // send start/address for write i2c.sendAddress(addr, true) // wait until transmission complete timeout := i2cTimeout for !i2c.Bus.INTFLAG.HasBits(sam.SERCOM_I2CM_INTFLAG_MB) { timeout-- if timeout == 0 { return errI2CWriteTimeout } } // ACK received (0: ACK, 1: NACK) if i2c.Bus.STATUS.HasBits(sam.SERCOM_I2CM_STATUS_RXNACK) { return errI2CAckExpected } // write data for _, b := range w { err = i2c.WriteByte(b) if err != nil { return err } } err = i2c.signalStop() if err != nil { return err } } if len(r) != 0 { // send start/address for read i2c.sendAddress(addr, false) // wait transmission complete for !i2c.Bus.INTFLAG.HasBits(sam.SERCOM_I2CM_INTFLAG_SB) { // If the peripheral NACKS the address, the MB bit will be set. // In that case, send a stop condition and return error. if i2c.Bus.INTFLAG.HasBits(sam.SERCOM_I2CM_INTFLAG_MB) { i2c.Bus.CTRLB.SetBits(wireCmdStop << sam.SERCOM_I2CM_CTRLB_CMD_Pos) // Stop condition return errI2CAckExpected } } // ACK received (0: ACK, 1: NACK) if i2c.Bus.STATUS.HasBits(sam.SERCOM_I2CM_STATUS_RXNACK) { return errI2CAckExpected } // read first byte r[0] = i2c.readByte() for i := 1; i < len(r); i++ { // Send an ACK i2c.Bus.CTRLB.ClearBits(sam.SERCOM_I2CM_CTRLB_ACKACT) i2c.signalRead() // Read data and send the ACK r[i] = i2c.readByte() } // Send NACK to end transmission i2c.Bus.CTRLB.SetBits(sam.SERCOM_I2CM_CTRLB_ACKACT) err = i2c.signalStop() if err != nil { return err } } return nil } // WriteByte writes a single byte to the I2C bus. func (i2c *I2C) WriteByte(data byte) error { // Send data byte i2c.Bus.DATA.Set(data) // wait until transmission successful timeout := i2cTimeout for !i2c.Bus.INTFLAG.HasBits(sam.SERCOM_I2CM_INTFLAG_MB) { // check for bus error if i2c.Bus.STATUS.HasBits(sam.SERCOM_I2CM_STATUS_BUSERR) { return errI2CBusError } timeout-- if timeout == 0 { return errI2CWriteTimeout } } if i2c.Bus.STATUS.HasBits(sam.SERCOM_I2CM_STATUS_RXNACK) { return errI2CAckExpected } return nil } // sendAddress sends the address and start signal func (i2c *I2C) sendAddress(address uint16, write bool) error { data := (address << 1) if !write { data |= 1 // set read flag } // wait until bus ready timeout := i2cTimeout for !i2c.Bus.STATUS.HasBits(wireIdleState< freqGCLK1 && uint32(uint8(baudRateGCLK0-1))+1 == baudRateGCLK0 { // Pick this 120MHz clock if it results in a better frequency after // division, and the baudRate value fits in the BAUD register. setSERCOMClockGenerator(spi.SERCOM, sam.GCLK_PCHCTRL_GEN_GCLK0) spi.Bus.BAUD.Set(uint8(baudRateGCLK0 - 1)) } else { // Use the 48MHz clock in other cases. setSERCOMClockGenerator(spi.SERCOM, sam.GCLK_PCHCTRL_GEN_GCLK1) spi.Bus.BAUD.Set(uint8(baudRateGCLK1 - 1)) } // Enable SPI port. spi.Bus.CTRLA.SetBits(sam.SERCOM_SPIM_CTRLA_ENABLE) for spi.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPIM_SYNCBUSY_ENABLE) { } return nil } // Transfer writes/reads a single byte using the SPI interface. func (spi *SPI) Transfer(w byte) (byte, error) { // write data spi.Bus.DATA.Set(uint32(w)) // wait for receive for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_RXC) { } // return data return byte(spi.Bus.DATA.Get()), nil } // Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read // interface, there must always be the same number of bytes written as bytes read. // The Tx method knows about this, and offers a few different ways of calling it. // // This form sends the bytes in tx buffer, putting the resulting bytes read into the rx buffer. // Note that the tx and rx buffers must be the same size: // // spi.Tx(tx, rx) // // This form sends the tx buffer, ignoring the result. Useful for sending "commands" that return zeros // until all the bytes in the command packet have been received: // // spi.Tx(tx, nil) // // This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": // // spi.Tx(nil, rx) func (spi *SPI) Tx(w, r []byte) error { switch { case w == nil: // read only, so write zero and read a result. spi.rx(r) case r == nil: // write only spi.tx(w) default: // write/read if len(w) != len(r) { return ErrTxInvalidSliceSize } spi.txrx(w, r) } return nil } func (spi *SPI) tx(tx []byte) { for i := 0; i < len(tx); i++ { for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) { } spi.Bus.DATA.Set(uint32(tx[i])) } for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_TXC) { } // read to clear RXC register for spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_RXC) { spi.Bus.DATA.Get() } } func (spi *SPI) rx(rx []byte) { spi.Bus.DATA.Set(0) for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) { } for i := 1; i < len(rx); i++ { spi.Bus.DATA.Set(0) for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_RXC) { } rx[i-1] = byte(spi.Bus.DATA.Get()) } for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_RXC) { } rx[len(rx)-1] = byte(spi.Bus.DATA.Get()) } func (spi *SPI) txrx(tx, rx []byte) { spi.Bus.DATA.Set(uint32(tx[0])) for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) { } for i := 1; i < len(rx); i++ { spi.Bus.DATA.Set(uint32(tx[i])) for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_RXC) { } rx[i-1] = byte(spi.Bus.DATA.Get()) } for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_RXC) { } rx[len(rx)-1] = byte(spi.Bus.DATA.Get()) } // The QSPI peripheral on ATSAMD51 is only available on the following pins const ( QSPI_SCK = PB10 QSPI_CS = PB11 QSPI_DATA0 = PA08 QSPI_DATA1 = PA09 QSPI_DATA2 = PA10 QSPI_DATA3 = PA11 ) // TCC is one timer peripheral, which consists of a counter and multiple output // channels (that can be connected to actual pins). You can set the frequency // using SetPeriod, but only for all the channels in this timer peripheral at // once. type TCC sam.TCC_Type //go:inline func (tcc *TCC) timer() *sam.TCC_Type { return (*sam.TCC_Type)(tcc) } // Configure enables and configures this TCC. func (tcc *TCC) Configure(config PWMConfig) error { // Enable the TCC clock to be able to use the TCC. tcc.configureClock() // Disable timer (if it was enabled). This is necessary because // tcc.setPeriod may want to change the prescaler bits in CTRLA, which is // only allowed when the TCC is disabled. tcc.timer().CTRLA.ClearBits(sam.TCC_CTRLA_ENABLE) // Use "Normal PWM" (single-slope PWM) tcc.timer().WAVE.Set(sam.TCC_WAVE_WAVEGEN_NPWM) // Wait for synchronization of all changed registers. for tcc.timer().SYNCBUSY.Get() != 0 { } // Set the period and prescaler. err := tcc.setPeriod(config.Period, true) // Enable the timer. tcc.timer().CTRLA.SetBits(sam.TCC_CTRLA_ENABLE) // Wait for synchronization of all changed registers. for tcc.timer().SYNCBUSY.Get() != 0 { } // Return any error that might have occurred in the tcc.setPeriod call. return err } // SetPeriod updates the period of this TCC peripheral. // To set a particular frequency, use the following formula: // // period = 1e9 / frequency // // If you use a period of 0, a period that works well for LEDs will be picked. // // SetPeriod will not change the prescaler, but also won't change the current // value in any of the channels. This means that you may need to update the // value for the particular channel. // // Note that you cannot pick any arbitrary period after the TCC peripheral has // been configured. If you want to switch between frequencies, pick the lowest // frequency (longest period) once when calling Configure and adjust the // frequency here as needed. func (tcc *TCC) SetPeriod(period uint64) error { return tcc.setPeriod(period, false) } // setPeriod sets the period of this TCC, possibly updating the prescaler as // well. The prescaler can only modified when the TCC is disabled, that is, in // the Configure function. func (tcc *TCC) setPeriod(period uint64, updatePrescaler bool) error { var top uint64 if period == 0 { // Make sure the TOP value is at 0xffff (enough for a 16-bit timer). top = 0xffff } else { // The formula below calculates the following formula, optimized: // period * (120e6 / 1e9) // This assumes that the chip is running from generic clock generator 0 // at 120MHz. top = period * 3 / 25 } maxTop := uint64(0xffff) if tcc.timer() == sam.TCC0 || tcc.timer() == sam.TCC1 { // Only TCC0 and TCC1 are 24-bit timers, the rest are 16-bit. maxTop = 0xffffff } if updatePrescaler { // This function was called during Configure(), with the timer disabled. // Note that updating the prescaler can only happen while the peripheral // is disabled. var prescaler uint32 switch { case top <= maxTop: prescaler = sam.TCC_CTRLA_PRESCALER_DIV1 case top/2 <= maxTop: prescaler = sam.TCC_CTRLA_PRESCALER_DIV2 top = top / 2 case top/4 <= maxTop: prescaler = sam.TCC_CTRLA_PRESCALER_DIV4 top = top / 4 case top/8 <= maxTop: prescaler = sam.TCC_CTRLA_PRESCALER_DIV8 top = top / 8 case top/16 <= maxTop: prescaler = sam.TCC_CTRLA_PRESCALER_DIV16 top = top / 16 case top/64 <= maxTop: prescaler = sam.TCC_CTRLA_PRESCALER_DIV64 top = top / 64 case top/256 <= maxTop: prescaler = sam.TCC_CTRLA_PRESCALER_DIV256 top = top / 256 case top/1024 <= maxTop: prescaler = sam.TCC_CTRLA_PRESCALER_DIV1024 top = top / 1024 default: return ErrPWMPeriodTooLong } tcc.timer().CTRLA.Set((tcc.timer().CTRLA.Get() &^ sam.TCC_CTRLA_PRESCALER_Msk) | (prescaler << sam.TCC_CTRLA_PRESCALER_Pos)) } else { // Do not update the prescaler, but use the already-configured // prescaler. This is the normal SetPeriod case, where the prescaler // must not be changed. prescaler := (tcc.timer().CTRLA.Get() & sam.TCC_CTRLA_PRESCALER_Msk) >> sam.TCC_CTRLA_PRESCALER_Pos switch prescaler { case sam.TCC_CTRLA_PRESCALER_DIV1: top /= 1 // no-op case sam.TCC_CTRLA_PRESCALER_DIV2: top /= 2 case sam.TCC_CTRLA_PRESCALER_DIV4: top /= 4 case sam.TCC_CTRLA_PRESCALER_DIV8: top /= 8 case sam.TCC_CTRLA_PRESCALER_DIV16: top /= 16 case sam.TCC_CTRLA_PRESCALER_DIV64: top /= 64 case sam.TCC_CTRLA_PRESCALER_DIV256: top /= 256 case sam.TCC_CTRLA_PRESCALER_DIV1024: top /= 1024 default: // unreachable } if top > maxTop { return ErrPWMPeriodTooLong } } // Set the period (the counter top). tcc.timer().PER.Set(uint32(top) - 1) // Wait for synchronization of CTRLA.PRESCALER and PER registers. for tcc.timer().SYNCBUSY.Get() != 0 { } return nil } // Top returns the current counter top, for use in duty cycle calculation. It // will only change with a call to Configure or SetPeriod, otherwise it is // constant. // // The value returned here is hardware dependent. In general, it's best to treat // it as an opaque value that can be divided by some number and passed to // tcc.Set (see tcc.Set for more information). func (tcc *TCC) Top() uint32 { return tcc.timer().PER.Get() + 1 } // Counter returns the current counter value of the timer in this TCC // peripheral. It may be useful for debugging. func (tcc *TCC) Counter() uint32 { tcc.timer().CTRLBSET.Set(sam.TCC_CTRLBSET_CMD_READSYNC << sam.TCC_CTRLBSET_CMD_Pos) for tcc.timer().SYNCBUSY.Get() != 0 { } return tcc.timer().COUNT.Get() } // Constants that encode a TCC number and WO number together in a single byte. const ( pinTCC0 = 1 << 4 // keep the value 0 usable as "no value" pinTCC1 = 2 << 4 pinTCC2 = 3 << 4 pinTCC3 = 4 << 4 pinTCC4 = 5 << 4 pinTCC0_0 = pinTCC0 | 0 pinTCC0_1 = pinTCC0 | 1 pinTCC0_2 = pinTCC0 | 2 pinTCC0_3 = pinTCC0 | 3 pinTCC0_4 = pinTCC0 | 4 pinTCC0_5 = pinTCC0 | 5 pinTCC0_6 = pinTCC0 | 6 pinTCC1_0 = pinTCC1 | 0 pinTCC1_2 = pinTCC1 | 2 pinTCC1_4 = pinTCC1 | 4 pinTCC1_6 = pinTCC1 | 6 pinTCC2_0 = pinTCC2 | 0 pinTCC2_2 = pinTCC2 | 2 pinTCC3_0 = pinTCC3 | 0 pinTCC4_0 = pinTCC4 | 0 ) // This is a copy of columns F and G (the TCC columns) of table 6-1 in the // datasheet: // http://ww1.microchip.com/downloads/en/DeviceDoc/60001507E.pdf // For example, "TCC0/WO[2]" is converted to pinTCC0_2. // Only the even pin numbers are stored here. The odd pin numbers are left out, // because their PWM output can be determined from the even number: just add one // to the wave output (WO) number. var pinTimerMapping = [...]struct{ F, G uint8 }{ // page 33 PC04 / 2: {pinTCC0_0, 0}, PA08 / 2: {pinTCC0_0, pinTCC1_4}, PA10 / 2: {pinTCC0_2, pinTCC1_6}, PB10 / 2: {pinTCC0_4, pinTCC1_0}, PB12 / 2: {pinTCC3_0, pinTCC0_0}, PB14 / 2: {pinTCC4_0, pinTCC0_2}, PD08 / 2: {pinTCC0_1, 0}, PD10 / 2: {pinTCC0_3, 0}, PD12 / 2: {pinTCC0_5, 0}, PC10 / 2: {pinTCC0_0, pinTCC1_4}, // page 34 PC12 / 2: {pinTCC0_2, pinTCC1_6}, PC14 / 2: {pinTCC0_4, pinTCC1_0}, PA12 / 2: {pinTCC0_6, pinTCC1_2}, PA14 / 2: {pinTCC2_0, pinTCC1_2}, PA16 / 2: {pinTCC1_0, pinTCC0_4}, PA18 / 2: {pinTCC1_2, pinTCC0_6}, PC16 / 2: {pinTCC0_0, 0}, PC18 / 2: {pinTCC0_2, 0}, PC20 / 2: {pinTCC0_4, 0}, PC22 / 2: {pinTCC0_6, 0}, PD20 / 2: {pinTCC1_0, 0}, PB16 / 2: {pinTCC3_0, pinTCC0_4}, PB18 / 2: {pinTCC1_0, 0}, // page 35 PB20 / 2: {pinTCC1_2, 0}, PA20 / 2: {pinTCC1_4, pinTCC0_0}, PA22 / 2: {pinTCC1_6, pinTCC0_2}, PA24 / 2: {pinTCC2_2, 0}, PB26 / 2: {pinTCC1_2, 0}, PB28 / 2: {pinTCC1_4, 0}, PA30 / 2: {pinTCC2_0, 0}, // page 36 PB30 / 2: {pinTCC4_0, pinTCC0_6}, PB02 / 2: {pinTCC2_2, 0}, } // findPinTimerMapping returns the pin mode (PinTCCF or PinTCCG) and the channel // number for a given timer and pin. A zero PinMode is returned if no mapping // could be found. func findPinTimerMapping(timer uint8, pin Pin) (PinMode, uint8) { if int(pin/2) >= len(pinTimerMapping) { return 0, 0 // invalid pin number } mapping := pinTimerMapping[pin/2] // Check for column F in the datasheet. if mapping.F>>4-1 == timer { return PinTCCF, mapping.F&0x0f + uint8(pin)&1 } // Check for column G in the datasheet. if mapping.G>>4-1 == timer { return PinTCCG, mapping.G&0x0f + uint8(pin)&1 } // Nothing found. return 0, 0 } // Channel returns a PWM channel for the given pin. Note that one channel may be // shared between multiple pins, and so will have the same duty cycle. If this // is not desirable, look for a different TCC or consider using a different pin. func (tcc *TCC) Channel(pin Pin) (uint8, error) { pinMode, woOutput := findPinTimerMapping(tcc.timerNum(), pin) if pinMode == 0 { // No pin could be found. return 0, ErrInvalidOutputPin } // Convert from waveform output to channel, assuming WEXCTRL.OTMX equals 0. // See table 49-4 "Output Matrix Channel Pin Routing Configuration" on page // 1829 of the datasheet. // The number of channels varies by TCC instance, hence the need to switch // over them. For TCC2-4 the number of channels is equal to the number of // waveform outputs, so the WO number maps directly to the channel number. // For TCC0 and TCC1 this is not the case so they will need some special // handling. channel := woOutput switch tcc.timer() { case sam.TCC0: channel = woOutput % 6 case sam.TCC1: channel = woOutput % 4 } // Enable the port multiplexer for pin pin.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN) // Connect timer/mux to pin. if pin&1 > 0 { // odd pin, so save the even pins val := pin.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk pin.setPMux(val | uint8(pinMode<> 4) dac.syncDAC() return nil } func (dac DAC) syncDAC() { switch dac.Channel { case 0: for !sam.DAC.STATUS.HasBits(sam.DAC_STATUS_EOC0) { } for sam.DAC.SYNCBUSY.HasBits(sam.DAC_SYNCBUSY_DATA0) { } default: for !sam.DAC.STATUS.HasBits(sam.DAC_STATUS_EOC1) { } for sam.DAC.SYNCBUSY.HasBits(sam.DAC_SYNCBUSY_DATA1) { } } } // GetRNG returns 32 bits of cryptographically secure random data func GetRNG() (uint32, error) { if !sam.MCLK.APBCMASK.HasBits(sam.MCLK_APBCMASK_TRNG_) { // Turn on clock for TRNG sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TRNG_) // enable sam.TRNG.CTRLA.Set(sam.TRNG_CTRLA_ENABLE) } for !sam.TRNG.INTFLAG.HasBits(sam.TRNG_INTFLAG_DATARDY) { } ret := sam.TRNG.DATA.Get() return ret, nil } // Flash related code const memoryStart = 0x0 // compile-time check for ensuring we fulfill BlockDevice interface var _ BlockDevice = flashBlockDevice{} var Flash flashBlockDevice type flashBlockDevice struct { initComplete bool } // ReadAt reads the given number of bytes from the block device. func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) { if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() { return 0, errFlashCannotReadPastEOF } waitWhileFlashBusy() data := unsafe.Slice((*byte)(unsafe.Add(unsafe.Pointer(FlashDataStart()), uintptr(off))), len(p)) copy(p, data) return len(p), nil } // WriteAt writes the given number of bytes to the block device. // Data is written to the page buffer in 4-byte chunks, then saved to flash memory. // See SAM-D5x-E5x-Family-Data-Sheet-DS60001507.pdf page 591-592. // If the length of p is not long enough it will be padded with 0xFF bytes. // This method assumes that the destination is already erased. func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() { return 0, errFlashCannotWritePastEOF } address := FlashDataStart() + uintptr(off) padded := flashPad(p, int(f.WriteBlockSize())) settings := disableFlashCache() defer restoreFlashCache(settings) waitWhileFlashBusy() sam.NVMCTRL.CTRLB.Set(sam.NVMCTRL_CTRLB_CMD_PBC | (sam.NVMCTRL_CTRLB_CMDEX_KEY << sam.NVMCTRL_CTRLB_CMDEX_Pos)) waitWhileFlashBusy() for j := 0; j < len(padded); j += int(f.WriteBlockSize()) { // page buffer is 512 bytes long, but only 4 bytes can be written at once for k := 0; k < int(f.WriteBlockSize()); k += 4 { *(*uint32)(unsafe.Pointer(address + uintptr(k))) = binary.LittleEndian.Uint32(padded[j+k : j+k+4]) } sam.NVMCTRL.SetADDR(uint32(address)) sam.NVMCTRL.CTRLB.Set(sam.NVMCTRL_CTRLB_CMD_WP | (sam.NVMCTRL_CTRLB_CMDEX_KEY << sam.NVMCTRL_CTRLB_CMDEX_Pos)) waitWhileFlashBusy() if err := checkFlashError(); err != nil { return j, err } address += uintptr(f.WriteBlockSize()) } return len(padded), nil } // Size returns the number of bytes in this block device. func (f flashBlockDevice) Size() int64 { return int64(FlashDataEnd() - FlashDataStart()) } const writeBlockSize = 512 // WriteBlockSize returns the block size in which data can be written to // memory. It can be used by a client to optimize writes, non-aligned writes // should always work correctly. func (f flashBlockDevice) WriteBlockSize() int64 { return writeBlockSize } const eraseBlockSizeValue = 8192 func eraseBlockSize() int64 { return eraseBlockSizeValue } // EraseBlockSize returns the smallest erasable area on this particular chip // in bytes. This is used for the block size in EraseBlocks. func (f flashBlockDevice) EraseBlockSize() int64 { return eraseBlockSize() } // EraseBlocks erases the given number of blocks. An implementation may // transparently coalesce ranges of blocks into larger bundles if the chip // supports this. The start and len parameters are in block numbers, use // EraseBlockSize to map addresses to blocks. func (f flashBlockDevice) EraseBlocks(start, len int64) error { address := FlashDataStart() + uintptr(start*f.EraseBlockSize()) settings := disableFlashCache() defer restoreFlashCache(settings) waitWhileFlashBusy() for i := start; i < start+len; i++ { sam.NVMCTRL.SetADDR(uint32(address)) sam.NVMCTRL.CTRLB.Set(sam.NVMCTRL_CTRLB_CMD_EB | (sam.NVMCTRL_CTRLB_CMDEX_KEY << sam.NVMCTRL_CTRLB_CMDEX_Pos)) waitWhileFlashBusy() if err := checkFlashError(); err != nil { return err } address += uintptr(f.EraseBlockSize()) } return nil } func disableFlashCache() uint16 { settings := sam.NVMCTRL.CTRLA.Get() // disable caches sam.NVMCTRL.SetCTRLA_CACHEDIS0(1) sam.NVMCTRL.SetCTRLA_CACHEDIS1(1) waitWhileFlashBusy() return settings } func restoreFlashCache(settings uint16) { sam.NVMCTRL.CTRLA.Set(settings) waitWhileFlashBusy() } func waitWhileFlashBusy() { for sam.NVMCTRL.GetSTATUS_READY() != sam.NVMCTRL_STATUS_READY { } } var ( errFlashADDRE = errors.New("errFlashADDRE") errFlashPROGE = errors.New("errFlashPROGE") errFlashLOCKE = errors.New("errFlashLOCKE") errFlashECCSE = errors.New("errFlashECCSE") errFlashNVME = errors.New("errFlashNVME") errFlashSEESOVF = errors.New("errFlashSEESOVF") ) func checkFlashError() error { switch { case sam.NVMCTRL.GetINTENSET_ADDRE() != 0: return errFlashADDRE case sam.NVMCTRL.GetINTENSET_PROGE() != 0: return errFlashPROGE case sam.NVMCTRL.GetINTENSET_LOCKE() != 0: return errFlashLOCKE case sam.NVMCTRL.GetINTENSET_ECCSE() != 0: return errFlashECCSE case sam.NVMCTRL.GetINTENSET_NVME() != 0: return errFlashNVME case sam.NVMCTRL.GetINTENSET_SEESOVF() != 0: return errFlashSEESOVF } return nil } // Watchdog provides access to the hardware watchdog available // in the SAMD51. var Watchdog = &watchdogImpl{} const ( // WatchdogMaxTimeout in milliseconds (16s) WatchdogMaxTimeout = (16384 * 1000) / 1024 // CYC16384/1024kHz ) type watchdogImpl struct{} // Configure the watchdog. // // This method should not be called after the watchdog is started and on // some platforms attempting to reconfigure after starting the watchdog // is explicitly forbidden / will not work. func (wd *watchdogImpl) Configure(config WatchdogConfig) error { // 1.024kHz clock cycles := int((int64(config.TimeoutMillis) * 1024) / 1000) // period is expressed as a power-of-two, starting at 8 / 1024ths of a second period := uint8(0) cfgCycles := 8 for cfgCycles < cycles { period++ cfgCycles <<= 1 if period >= 0xB { break } } sam.WDT.CONFIG.Set(period << sam.WDT_CONFIG_PER_Pos) return nil } // Starts the watchdog. func (wd *watchdogImpl) Start() error { sam.WDT.CTRLA.SetBits(sam.WDT_CTRLA_ENABLE) return nil } // Update the watchdog, indicating that `source` is healthy. func (wd *watchdogImpl) Update() { sam.WDT.CLEAR.Set(sam.WDT_CLEAR_CLEAR_KEY) } ================================================ FILE: src/machine/machine_atsamd51_usb.go ================================================ //go:build (sam && atsamd51) || (sam && atsame5x) package machine import ( "device/sam" "machine/usb" "runtime/interrupt" "unsafe" ) const ( // these are SAMD51 specific. usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos = 0 usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask = 0x3FFF usb_DEVICE_PCKSIZE_SIZE_Pos = 28 usb_DEVICE_PCKSIZE_SIZE_Mask = 0x7 usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos = 14 usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Mask = 0x3FFF NumberOfUSBEndpoints = 8 ) var ( endPoints = []uint32{ usb.CONTROL_ENDPOINT: usb.ENDPOINT_TYPE_CONTROL, usb.CDC_ENDPOINT_ACM: (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn), usb.CDC_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut), usb.CDC_ENDPOINT_IN: (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn), usb.HID_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt In usb.HID_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt Out usb.MIDI_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Bulk In usb.MIDI_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Bulk Out } ) // Configure the USB peripheral. The config is here for compatibility with the UART interface. func (dev *USBDevice) Configure(config UARTConfig) { if dev.initcomplete { return } // reset USB interface sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_SWRST) for sam.USB_DEVICE.SYNCBUSY.HasBits(sam.USB_DEVICE_SYNCBUSY_SWRST) || sam.USB_DEVICE.SYNCBUSY.HasBits(sam.USB_DEVICE_SYNCBUSY_ENABLE) { } sam.USB_DEVICE.DESCADD.Set(uint32(uintptr(unsafe.Pointer(&usbEndpointDescriptors)))) // configure pins USBCDC_DM_PIN.Configure(PinConfig{Mode: PinCom}) USBCDC_DP_PIN.Configure(PinConfig{Mode: PinCom}) // performs pad calibration from store fuses handlePadCalibration() // run in standby sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_RUNSTDBY) // set full speed sam.USB_DEVICE.CTRLB.SetBits(sam.USB_DEVICE_CTRLB_SPDCONF_FS << sam.USB_DEVICE_CTRLB_SPDCONF_Pos) // attach sam.USB_DEVICE.CTRLB.ClearBits(sam.USB_DEVICE_CTRLB_DETACH) // enable interrupt for end of reset sam.USB_DEVICE.INTENSET.SetBits(sam.USB_DEVICE_INTENSET_EORST) // enable interrupt for start of frame sam.USB_DEVICE.INTENSET.SetBits(sam.USB_DEVICE_INTENSET_SOF) // enable USB sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_ENABLE) // enable IRQ interrupt.New(sam.IRQ_USB_OTHER, handleUSBIRQ).Enable() interrupt.New(sam.IRQ_USB_SOF_HSOF, handleUSBIRQ).Enable() interrupt.New(sam.IRQ_USB_TRCPT0, handleUSBIRQ).Enable() interrupt.New(sam.IRQ_USB_TRCPT1, handleUSBIRQ).Enable() dev.initcomplete = true } func handlePadCalibration() { // Load Pad Calibration data from non-volatile memory // This requires registers that are not included in the SVD file. // Modeled after defines from samd21g18a.h and nvmctrl.h: // // #define NVMCTRL_OTP4 0x00806020 // // #define USB_FUSES_TRANSN_ADDR (NVMCTRL_OTP4 + 4) // #define USB_FUSES_TRANSN_Pos 13 /**< \brief (NVMCTRL_OTP4) USB pad Transn calibration */ // #define USB_FUSES_TRANSN_Msk (0x1Fu << USB_FUSES_TRANSN_Pos) // #define USB_FUSES_TRANSN(value) ((USB_FUSES_TRANSN_Msk & ((value) << USB_FUSES_TRANSN_Pos))) // #define USB_FUSES_TRANSP_ADDR (NVMCTRL_OTP4 + 4) // #define USB_FUSES_TRANSP_Pos 18 /**< \brief (NVMCTRL_OTP4) USB pad Transp calibration */ // #define USB_FUSES_TRANSP_Msk (0x1Fu << USB_FUSES_TRANSP_Pos) // #define USB_FUSES_TRANSP(value) ((USB_FUSES_TRANSP_Msk & ((value) << USB_FUSES_TRANSP_Pos))) // #define USB_FUSES_TRIM_ADDR (NVMCTRL_OTP4 + 4) // #define USB_FUSES_TRIM_Pos 23 /**< \brief (NVMCTRL_OTP4) USB pad Trim calibration */ // #define USB_FUSES_TRIM_Msk (0x7u << USB_FUSES_TRIM_Pos) // #define USB_FUSES_TRIM(value) ((USB_FUSES_TRIM_Msk & ((value) << USB_FUSES_TRIM_Pos))) // fuse := *(*uint32)(unsafe.Pointer(uintptr(0x00806020) + 4)) calibTransN := uint16(fuse>>13) & uint16(0x1f) calibTransP := uint16(fuse>>18) & uint16(0x1f) calibTrim := uint16(fuse>>23) & uint16(0x7) if calibTransN == 0x1f { calibTransN = 5 } sam.USB_DEVICE.PADCAL.SetBits(calibTransN << sam.USB_DEVICE_PADCAL_TRANSN_Pos) if calibTransP == 0x1f { calibTransP = 29 } sam.USB_DEVICE.PADCAL.SetBits(calibTransP << sam.USB_DEVICE_PADCAL_TRANSP_Pos) if calibTrim == 0x7 { calibTrim = 3 } sam.USB_DEVICE.PADCAL.SetBits(calibTrim << sam.USB_DEVICE_PADCAL_TRIM_Pos) } func handleUSBIRQ(intr interrupt.Interrupt) { // reset all interrupt flags flags := sam.USB_DEVICE.INTFLAG.Get() sam.USB_DEVICE.INTFLAG.Set(flags) // End of reset if (flags & sam.USB_DEVICE_INTFLAG_EORST) > 0 { // Configure control endpoint initEndpoint(0, usb.ENDPOINT_TYPE_CONTROL) usbConfiguration = 0 // ack the End-Of-Reset interrupt sam.USB_DEVICE.INTFLAG.Set(sam.USB_DEVICE_INTFLAG_EORST) } // Start of frame if (flags & sam.USB_DEVICE_INTFLAG_SOF) > 0 { // if you want to blink LED showing traffic, this would be the place... } // Endpoint 0 Setup interrupt if getEPINTFLAG(0)&sam.USB_DEVICE_ENDPOINT_EPINTFLAG_RXSTP > 0 { // ack setup received setEPINTFLAG(0, sam.USB_DEVICE_ENDPOINT_EPINTFLAG_RXSTP) // parse setup setup := usb.NewSetup(udd_ep_out_cache_buffer[0][:]) // Clear the Bank 0 ready flag on Control OUT usbEndpointDescriptors[0].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[0])))) usbEndpointDescriptors[0].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) setEPSTATUSCLR(0, sam.USB_DEVICE_ENDPOINT_EPSTATUSCLR_BK0RDY) ok := false if (setup.BmRequestType & usb.REQUEST_TYPE) == usb.REQUEST_STANDARD { // Standard Requests ok = handleStandardSetup(setup) } else { // Class Interface Requests if setup.WIndex < uint16(len(usbSetupHandler)) && usbSetupHandler[setup.WIndex] != nil { ok = usbSetupHandler[setup.WIndex](setup) } } if ok { // set Bank1 ready setEPSTATUSSET(0, sam.USB_DEVICE_ENDPOINT_EPSTATUSSET_BK1RDY) } else { // Stall endpoint setEPSTATUSSET(0, sam.USB_DEVICE_ENDPOINT_EPINTFLAG_STALL1) } if getEPINTFLAG(0)&sam.USB_DEVICE_ENDPOINT_EPINTFLAG_STALL1 > 0 { // ack the stall setEPINTFLAG(0, sam.USB_DEVICE_ENDPOINT_EPINTFLAG_STALL1) // clear stall request setEPINTENCLR(0, sam.USB_DEVICE_ENDPOINT_EPINTENCLR_STALL1) } } // Now the actual transfer handlers, ignore endpoint number 0 (setup) var i uint32 for i = 1; i < uint32(len(endPoints)); i++ { // Check if endpoint has a pending interrupt epFlags := getEPINTFLAG(i) setEPINTFLAG(i, epFlags) if (epFlags & sam.USB_DEVICE_ENDPOINT_EPINTFLAG_TRCPT0) > 0 { buf := handleEndpointRx(i) if usbRxHandler[i] == nil || usbRxHandler[i](buf) { AckUsbOutTransfer(i) } } else if (epFlags & sam.USB_DEVICE_ENDPOINT_EPINTFLAG_TRCPT1) > 0 { if usbTxHandler[i] != nil { usbTxHandler[i]() } } } } func initEndpoint(ep, config uint32) { switch config { case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn: // set packet size usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.SetBits(epPacketSize(64) << usb_DEVICE_PCKSIZE_SIZE_Pos) // set data buffer address usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep])))) // set endpoint type setEPCFG(ep, ((usb.ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE1_Pos)) setEPINTENSET(ep, sam.USB_DEVICE_ENDPOINT_EPINTENSET_TRCPT1) case usb.ENDPOINT_TYPE_BULK | usb.EndpointOut: // set packet size usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.SetBits(epPacketSize(64) << usb_DEVICE_PCKSIZE_SIZE_Pos) // set data buffer address usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep])))) // set endpoint type setEPCFG(ep, ((usb.ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE0_Pos)) // receive interrupts when current transfer complete setEPINTENSET(ep, sam.USB_DEVICE_ENDPOINT_EPINTENSET_TRCPT0) // set byte count to zero, we have not received anything yet usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) // ready for next transfer setEPSTATUSCLR(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSCLR_BK0RDY) case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut: // set packet size usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.SetBits(epPacketSize(64) << usb_DEVICE_PCKSIZE_SIZE_Pos) // set data buffer address usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep])))) // set endpoint type setEPCFG(ep, ((usb.ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE0_Pos)) // receive interrupts when current transfer complete setEPINTENSET(ep, sam.USB_DEVICE_ENDPOINT_EPINTENSET_TRCPT0) // set byte count to zero, we have not received anything yet usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) // ready for next transfer setEPSTATUSCLR(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSCLR_BK0RDY) case usb.ENDPOINT_TYPE_BULK | usb.EndpointIn: // set packet size usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.SetBits(epPacketSize(64) << usb_DEVICE_PCKSIZE_SIZE_Pos) // set data buffer address usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep])))) // set endpoint type setEPCFG(ep, ((usb.ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE1_Pos)) // NAK on endpoint IN, the bank is not yet filled in. setEPSTATUSCLR(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSCLR_BK1RDY) setEPINTENSET(ep, sam.USB_DEVICE_ENDPOINT_EPINTENSET_TRCPT1) case usb.ENDPOINT_TYPE_CONTROL: // Control OUT // set packet size usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.SetBits(epPacketSize(64) << usb_DEVICE_PCKSIZE_SIZE_Pos) // set data buffer address usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep])))) // set endpoint type setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_CONTROL+1)<> usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) if bytesread != cdcLineInfoSize { return b, ErrUSBBytesRead } copy(b[:7], udd_ep_out_cache_buffer[0][:7]) return b, nil } func handleEndpointRx(ep uint32) []byte { // get data count := int((usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.Get() >> usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) return udd_ep_out_cache_buffer[ep][:count] } // AckUsbOutTransfer is called to acknowledge the completion of a USB OUT transfer. func AckUsbOutTransfer(ep uint32) { // set byte count to zero usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) // set multi packet size to 64 usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.SetBits(64 << usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos) // set ready for next data setEPSTATUSCLR(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSCLR_BK0RDY) } func SendZlp() { usbEndpointDescriptors[0].DeviceDescBank[1].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) } func epPacketSize(size uint16) uint32 { switch size { case 8: return 0 case 16: return 1 case 32: return 2 case 64: return 3 case 128: return 4 case 256: return 5 case 512: return 6 case 1023: return 7 default: return 0 } } func getEPCFG(ep uint32) uint8 { return sam.USB_DEVICE.DEVICE_ENDPOINT[ep].EPCFG.Get() } func setEPCFG(ep uint32, val uint8) { sam.USB_DEVICE.DEVICE_ENDPOINT[ep].EPCFG.Set(val) } func setEPSTATUSCLR(ep uint32, val uint8) { sam.USB_DEVICE.DEVICE_ENDPOINT[ep].EPSTATUSCLR.Set(val) } func setEPSTATUSSET(ep uint32, val uint8) { sam.USB_DEVICE.DEVICE_ENDPOINT[ep].EPSTATUSSET.Set(val) } func getEPSTATUS(ep uint32) uint8 { return sam.USB_DEVICE.DEVICE_ENDPOINT[ep].EPSTATUS.Get() } func getEPINTFLAG(ep uint32) uint8 { return sam.USB_DEVICE.DEVICE_ENDPOINT[ep].EPINTFLAG.Get() } func setEPINTFLAG(ep uint32, val uint8) { sam.USB_DEVICE.DEVICE_ENDPOINT[ep].EPINTFLAG.Set(val) } func setEPINTENCLR(ep uint32, val uint8) { sam.USB_DEVICE.DEVICE_ENDPOINT[ep].EPINTENCLR.Set(val) } func setEPINTENSET(ep uint32, val uint8) { sam.USB_DEVICE.DEVICE_ENDPOINT[ep].EPINTENSET.Set(val) } ================================================ FILE: src/machine/machine_atsamd51g19.go ================================================ //go:build sam && atsamd51 && atsamd51g19 // Peripheral abstraction layer for the atsamd51. // // Datasheet: // http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf package machine import "device/sam" const HSRAM_SIZE = 0x00030000 var ( sercomI2CM0 = &I2C{Bus: sam.SERCOM0_I2CM, SERCOM: 0} sercomI2CM1 = &I2C{Bus: sam.SERCOM1_I2CM, SERCOM: 1} sercomI2CM2 = &I2C{Bus: sam.SERCOM2_I2CM, SERCOM: 2} sercomI2CM3 = &I2C{Bus: sam.SERCOM3_I2CM, SERCOM: 3} sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} ) // setSERCOMClockGenerator sets the GCLK for sercom func setSERCOMClockGenerator(sercom uint8, gclk uint32) { switch sercom { case 0: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 1: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 2: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 3: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 4: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 5: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } } // This chip has three TCC peripherals, which have PWM as one feature. var ( TCC0 = (*TCC)(sam.TCC0) TCC1 = (*TCC)(sam.TCC1) TCC2 = (*TCC)(sam.TCC2) ) func (tcc *TCC) configureClock() { // Turn on timer clocks used for TCC and use generic clock generator 0. switch tcc.timer() { case sam.TCC0: sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC1: sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC1_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC2: sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } } func (tcc *TCC) timerNum() uint8 { switch tcc.timer() { case sam.TCC0: return 0 case sam.TCC1: return 1 case sam.TCC2: return 2 default: return 0x0f // should not happen } } ================================================ FILE: src/machine/machine_atsamd51j19.go ================================================ //go:build sam && atsamd51 && atsamd51j19 // Peripheral abstraction layer for the atsamd51. // // Datasheet: // http://ww1.microchip.com/downloads/en/DeviceDoc/SAM_D5xE5x_Family_Data_Sheet_DS60001507F.pdf package machine import "device/sam" const HSRAM_SIZE = 0x00030000 var ( sercomI2CM0 = &I2C{Bus: sam.SERCOM0_I2CM, SERCOM: 0} sercomI2CM1 = &I2C{Bus: sam.SERCOM1_I2CM, SERCOM: 1} sercomI2CM2 = &I2C{Bus: sam.SERCOM2_I2CM, SERCOM: 2} sercomI2CM3 = &I2C{Bus: sam.SERCOM3_I2CM, SERCOM: 3} sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} ) // setSERCOMClockGenerator sets the GCLK for sercom func setSERCOMClockGenerator(sercom uint8, gclk uint32) { switch sercom { case 0: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 1: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 2: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 3: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 4: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 5: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } } // This chip has five TCC peripherals, which have PWM as one feature. var ( TCC0 = (*TCC)(sam.TCC0) TCC1 = (*TCC)(sam.TCC1) TCC2 = (*TCC)(sam.TCC2) TCC3 = (*TCC)(sam.TCC3) TCC4 = (*TCC)(sam.TCC4) ) func (tcc *TCC) configureClock() { // Turn on timer clocks used for the TCC and use generic clock generator 0. switch tcc.timer() { case sam.TCC0: sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC1: sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC1_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC2: sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC3: sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC3_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC3].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC4: sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } } func (tcc *TCC) timerNum() uint8 { switch tcc.timer() { case sam.TCC0: return 0 case sam.TCC1: return 1 case sam.TCC2: return 2 case sam.TCC3: return 3 case sam.TCC4: return 4 default: return 0x0f // should not happen } } ================================================ FILE: src/machine/machine_atsamd51j20.go ================================================ //go:build sam && atsamd51 && atsamd51j20 // Peripheral abstraction layer for the atsamd51. // // Datasheet: // http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf package machine import "device/sam" const HSRAM_SIZE = 0x00040000 var ( sercomI2CM0 = &I2C{Bus: sam.SERCOM0_I2CM, SERCOM: 0} sercomI2CM1 = &I2C{Bus: sam.SERCOM1_I2CM, SERCOM: 1} sercomI2CM2 = &I2C{Bus: sam.SERCOM2_I2CM, SERCOM: 2} sercomI2CM3 = &I2C{Bus: sam.SERCOM3_I2CM, SERCOM: 3} sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} ) // setSERCOMClockGenerator sets the GCLK for sercom func setSERCOMClockGenerator(sercom uint8, gclk uint32) { switch sercom { case 0: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 1: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 2: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 3: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 4: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 5: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } } // This chip has five TCC peripherals, which have PWM as one feature. var ( TCC0 = (*TCC)(sam.TCC0) TCC1 = (*TCC)(sam.TCC1) TCC2 = (*TCC)(sam.TCC2) TCC3 = (*TCC)(sam.TCC3) TCC4 = (*TCC)(sam.TCC4) ) func (tcc *TCC) configureClock() { // Turn on timer clocks used for TCC and use generic clock generator 0. switch tcc.timer() { case sam.TCC0: sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC1: sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC1_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC2: sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC3: sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC3_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC3].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC4: sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } } func (tcc *TCC) timerNum() uint8 { switch tcc.timer() { case sam.TCC0: return 0 case sam.TCC1: return 1 case sam.TCC2: return 2 case sam.TCC3: return 3 case sam.TCC4: return 4 default: return 0x0f // should not happen } } ================================================ FILE: src/machine/machine_atsamd51p19.go ================================================ //go:build sam && atsamd51 && atsamd51p19 // Peripheral abstraction layer for the atsamd51. // // Datasheet: // http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf package machine import "device/sam" const HSRAM_SIZE = 0x00030000 var ( sercomI2CM0 = &I2C{Bus: sam.SERCOM0_I2CM, SERCOM: 0} sercomI2CM1 = &I2C{Bus: sam.SERCOM1_I2CM, SERCOM: 1} sercomI2CM2 = &I2C{Bus: sam.SERCOM2_I2CM, SERCOM: 2} sercomI2CM3 = &I2C{Bus: sam.SERCOM3_I2CM, SERCOM: 3} sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} sercomI2CM6 = &I2C{Bus: sam.SERCOM6_I2CM, SERCOM: 6} sercomI2CM7 = &I2C{Bus: sam.SERCOM7_I2CM, SERCOM: 7} sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} sercomSPIM6 = &SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} sercomSPIM7 = &SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} ) // setSERCOMClockGenerator sets the GCLK for sercom func setSERCOMClockGenerator(sercom uint8, gclk uint32) { switch sercom { case 0: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 1: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 2: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 3: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 4: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 5: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 6: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM6_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM6_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM6_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 7: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM7_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM7_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM7_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } } // This chip has five TCC peripherals, which have PWM as one feature. var ( TCC0 = (*TCC)(sam.TCC0) TCC1 = (*TCC)(sam.TCC1) TCC2 = (*TCC)(sam.TCC2) TCC3 = (*TCC)(sam.TCC3) TCC4 = (*TCC)(sam.TCC4) ) func (tcc *TCC) configureClock() { // Turn on timer clocks used for TCC and use generic clock generator 0. switch tcc.timer() { case sam.TCC0: sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC1: sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC1_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC2: sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC3: sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC3_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC3].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC4: sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } } func (tcc *TCC) timerNum() uint8 { switch tcc.timer() { case sam.TCC0: return 0 case sam.TCC1: return 1 case sam.TCC2: return 2 case sam.TCC3: return 3 case sam.TCC4: return 4 default: return 0x0f // should not happen } } ================================================ FILE: src/machine/machine_atsamd51p20.go ================================================ //go:build sam && atsamd51 && atsamd51p20 // Peripheral abstraction layer for the atsamd51. // // Datasheet: // http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf package machine import "device/sam" const HSRAM_SIZE = 0x00040000 var ( sercomI2CM0 = &I2C{Bus: sam.SERCOM0_I2CM, SERCOM: 0} sercomI2CM1 = &I2C{Bus: sam.SERCOM1_I2CM, SERCOM: 1} sercomI2CM2 = &I2C{Bus: sam.SERCOM2_I2CM, SERCOM: 2} sercomI2CM3 = &I2C{Bus: sam.SERCOM3_I2CM, SERCOM: 3} sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} sercomI2CM6 = &I2C{Bus: sam.SERCOM6_I2CM, SERCOM: 6} sercomI2CM7 = &I2C{Bus: sam.SERCOM7_I2CM, SERCOM: 7} sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} sercomSPIM6 = &SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} sercomSPIM7 = &SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} ) // setSERCOMClockGenerator sets the GCLK for sercom func setSERCOMClockGenerator(sercom uint8, gclk uint32) { switch sercom { case 0: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 1: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 2: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 3: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 4: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 5: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 6: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM6_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM6_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM6_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 7: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM7_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM7_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM7_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } } // This chip has five TCC peripherals, which have PWM as one feature. var ( TCC0 = (*TCC)(sam.TCC0) TCC1 = (*TCC)(sam.TCC1) TCC2 = (*TCC)(sam.TCC2) TCC3 = (*TCC)(sam.TCC3) TCC4 = (*TCC)(sam.TCC4) ) func (tcc *TCC) configureClock() { // Turn on timer clocks used for TCC and use generic clock generator 0. switch tcc.timer() { case sam.TCC0: sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC1: sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC1_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC2: sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC3: sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC3_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC3].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC4: sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } } func (tcc *TCC) timerNum() uint8 { switch tcc.timer() { case sam.TCC0: return 0 case sam.TCC1: return 1 case sam.TCC2: return 2 case sam.TCC3: return 3 case sam.TCC4: return 4 default: return 0x0f // should not happen } } ================================================ FILE: src/machine/machine_atsame51j19.go ================================================ //go:build sam && atsame51 && atsame51j19 // Peripheral abstraction layer for the atsame51. // // Datasheet: // http://ww1.microchip.com/downloads/en/DeviceDoc/SAM_D5xE5x_Family_Data_Sheet_DS60001507F.pdf package machine import "device/sam" const HSRAM_SIZE = 0x00030000 var ( sercomI2CM0 = &I2C{Bus: sam.SERCOM0_I2CM, SERCOM: 0} sercomI2CM1 = &I2C{Bus: sam.SERCOM1_I2CM, SERCOM: 1} sercomI2CM2 = &I2C{Bus: sam.SERCOM2_I2CM, SERCOM: 2} sercomI2CM3 = &I2C{Bus: sam.SERCOM3_I2CM, SERCOM: 3} sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} ) // setSERCOMClockGenerator sets the GCLK for sercom func setSERCOMClockGenerator(sercom uint8, gclk uint32) { switch sercom { case 0: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 1: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 2: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 3: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 4: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 5: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } } // This chip has five TCC peripherals, which have PWM as one feature. var ( TCC0 = (*TCC)(sam.TCC0) TCC1 = (*TCC)(sam.TCC1) TCC2 = (*TCC)(sam.TCC2) TCC3 = (*TCC)(sam.TCC3) TCC4 = (*TCC)(sam.TCC4) ) func (tcc *TCC) configureClock() { // Turn on timer clocks used for the TCC and use generic clock generator 0. switch tcc.timer() { case sam.TCC0: sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC1: sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC1_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC2: sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC3: sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC3_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC3].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC4: sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } } func (tcc *TCC) timerNum() uint8 { switch tcc.timer() { case sam.TCC0: return 0 case sam.TCC1: return 1 case sam.TCC2: return 2 case sam.TCC3: return 3 case sam.TCC4: return 4 default: return 0x0f // should not happen } } ================================================ FILE: src/machine/machine_atsame54p20.go ================================================ //go:build sam && atsame5x && atsame54p20 // Peripheral abstraction layer for the atsame54. // // Datasheet: // http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf package machine import "device/sam" const HSRAM_SIZE = 0x00040000 var ( sercomI2CM0 = &I2C{Bus: sam.SERCOM0_I2CM, SERCOM: 0} sercomI2CM1 = &I2C{Bus: sam.SERCOM1_I2CM, SERCOM: 1} sercomI2CM2 = &I2C{Bus: sam.SERCOM2_I2CM, SERCOM: 2} sercomI2CM3 = &I2C{Bus: sam.SERCOM3_I2CM, SERCOM: 3} sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} sercomI2CM6 = &I2C{Bus: sam.SERCOM6_I2CM, SERCOM: 6} sercomI2CM7 = &I2C{Bus: sam.SERCOM7_I2CM, SERCOM: 7} sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} sercomSPIM6 = &SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} sercomSPIM7 = &SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} ) // setSERCOMClockGenerator sets the GCLK for sercom func setSERCOMClockGenerator(sercom uint8, gclk uint32) { switch sercom { case 0: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 1: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 2: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 3: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 4: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 5: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 6: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM6_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM6_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM6_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case 7: sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM7_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM7_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM7_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } } // This chip has five TCC peripherals, which have PWM as one feature. var ( TCC0 = (*TCC)(sam.TCC0) TCC1 = (*TCC)(sam.TCC1) TCC2 = (*TCC)(sam.TCC2) TCC3 = (*TCC)(sam.TCC3) TCC4 = (*TCC)(sam.TCC4) ) func (tcc *TCC) configureClock() { // Turn on timer clocks used for TCC and use generic clock generator 0. switch tcc.timer() { case sam.TCC0: sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC1: sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC1_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC2: sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC3: sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC3_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC3].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) case sam.TCC4: sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } } func (tcc *TCC) timerNum() uint8 { switch tcc.timer() { case sam.TCC0: return 0 case sam.TCC1: return 1 case sam.TCC2: return 2 case sam.TCC3: return 3 case sam.TCC4: return 4 default: return 0x0f // should not happen } } ================================================ FILE: src/machine/machine_atsame5x_can.go ================================================ //go:build (sam && atsame51) || (sam && atsame54) package machine import ( "device/sam" "errors" "runtime/interrupt" "unsafe" ) const ( CANRxFifoSize = 16 CANTxFifoSize = 16 CANEvFifoSize = 16 ) // Message RAM can only be located in the first 64 KB area of the system RAM. // TODO: when the go:section pragma is merged, add the section configuration //go:align 4 var CANRxFifo [2][(8 + 64) * CANRxFifoSize]byte //go:align 4 var CANTxFifo [2][(8 + 64) * CANTxFifoSize]byte //go:align 4 var CANEvFifo [2][(8) * CANEvFifoSize]byte type CAN struct { Bus *sam.CAN_Type } type CANTransferRate uint32 // CAN transfer rates for CANConfig const ( CANTransferRate125kbps CANTransferRate = 125000 CANTransferRate250kbps CANTransferRate = 250000 CANTransferRate500kbps CANTransferRate = 500000 CANTransferRate1000kbps CANTransferRate = 1000000 CANTransferRate2000kbps CANTransferRate = 2000000 CANTransferRate4000kbps CANTransferRate = 4000000 ) // CANConfig holds CAN configuration parameters. Tx and Rx need to be // specified with some pins. When the Standby Pin is specified, configure it // as an output pin and output Low in Configure(). If this operation is not // necessary, specify NoPin. type CANConfig struct { TransferRate CANTransferRate TransferRateFD CANTransferRate Tx Pin Rx Pin Standby Pin } var ( errCANInvalidTransferRate = errors.New("CAN: invalid TransferRate") errCANInvalidTransferRateFD = errors.New("CAN: invalid TransferRateFD") ) // Configure this CAN peripheral with the given configuration. func (can *CAN) Configure(config CANConfig) error { if config.Standby != NoPin { config.Standby.Configure(PinConfig{Mode: PinOutput}) config.Standby.Low() } mode := PinCAN0 if can.instance() == 1 { mode = PinCAN1 } config.Rx.Configure(PinConfig{Mode: mode}) config.Tx.Configure(PinConfig{Mode: mode}) can.Bus.CCCR.SetBits(sam.CAN_CCCR_INIT) for !can.Bus.CCCR.HasBits(sam.CAN_CCCR_INIT) { } can.Bus.CCCR.SetBits(sam.CAN_CCCR_CCE) can.Bus.CCCR.SetBits(sam.CAN_CCCR_BRSE | sam.CAN_CCCR_FDOE) can.Bus.MRCFG.Set(sam.CAN_MRCFG_QOS_MEDIUM) // base clock == 48 MHz if config.TransferRate == 0 { config.TransferRate = CANTransferRate500kbps } brp := uint32(6) switch config.TransferRate { case CANTransferRate125kbps: brp = 32 case CANTransferRate250kbps: brp = 16 case CANTransferRate500kbps: brp = 8 case CANTransferRate1000kbps: brp = 4 default: return errCANInvalidTransferRate } can.Bus.NBTP.Set(8<> sam.CAN_TXFQS_TFQPI_Pos f := CANTxFifo[can.instance()][putIndex*(8+64) : (putIndex+1)*(8+64)] id := e.ID if !e.XTD { // standard identifier is stored into ID[28:18] id <<= 18 } f[3] = byte(id>>24) & 0x1F if e.ESI { f[3] |= 0x80 } if e.XTD { f[3] |= 0x40 } if e.RTR { f[3] |= 0x20 } f[2] = byte(id >> 16) f[1] = byte(id >> 8) f[0] = byte(id) f[7] = e.MM f[6] = e.DLC if e.EFC { f[6] |= 0x80 } if e.FDF { f[6] |= 0x20 } if e.BRS { f[6] |= 0x10 } f[5] = 0x00 // reserved f[4] = 0x00 // reserved length := CANDlcToLength(e.DLC, e.FDF) for i := byte(0); i < length; i++ { f[8+i] = e.DB[i] } can.Bus.TXBAR.SetBits(1 << putIndex) } // The Tx transmits CAN frames. It is easier to use than TxRaw, but not as // flexible. func (can *CAN) Tx(id uint32, data []byte, isFD, isExtendedID bool) { length := byte(len(data)) dlc := CANLengthToDlc(length, true) e := CANTxBufferElement{ ESI: false, XTD: isExtendedID, RTR: false, ID: id, MM: 0x00, EFC: true, FDF: isFD, BRS: isFD, DLC: dlc, } if !isFD { if length > 8 { length = 8 } } for i := byte(0); i < length; i++ { e.DB[i] = data[i] } can.TxRaw(&e) } // RxFifoSize returns the number of CAN Frames currently stored in the RXFifo. func (can *CAN) RxFifoSize() int { sz := (can.Bus.RXF0S.Get() & sam.CAN_RXF0S_F0FL_Msk) >> sam.CAN_RXF0S_F0FL_Pos return int(sz) } // RxFifoIsFull returns whether RxFifo is full or not. func (can *CAN) RxFifoIsFull() bool { sz := (can.Bus.RXF0S.Get() & sam.CAN_RXF0S_F0FL_Msk) >> sam.CAN_RXF0S_F0FL_Pos return sz == CANRxFifoSize } // RxFifoIsEmpty returns whether RxFifo is empty or not. func (can *CAN) RxFifoIsEmpty() bool { sz := (can.Bus.RXF0S.Get() & sam.CAN_RXF0S_F0FL_Msk) >> sam.CAN_RXF0S_F0FL_Pos return sz == 0 } // RxRaw copies the received CAN frame to CANRxBufferElement. func (can *CAN) RxRaw(e *CANRxBufferElement) { idx := (can.Bus.RXF0S.Get() & sam.CAN_RXF0S_F0GI_Msk) >> sam.CAN_RXF0S_F0GI_Pos f := CANRxFifo[can.instance()][idx*(8+64):] e.ESI = false if (f[3] & 0x80) != 0x00 { e.ESI = true } e.XTD = false if (f[3] & 0x40) != 0x00 { e.XTD = true } e.RTR = false if (f[3] & 0x20) != 0x00 { e.RTR = true } id := ((uint32(f[3]) << 24) + (uint32(f[2]) << 16) + (uint32(f[1]) << 8) + uint32(f[0])) & 0x1FFFFFFF if !e.XTD { id >>= 18 id &= 0x000007FF } e.ID = id e.ANMF = false if (f[7] & 0x80) != 0x00 { e.ANMF = true } e.FIDX = f[7] & 0x7F e.FDF = false if (f[6] & 0x20) != 0x00 { e.FDF = true } e.BRS = false if (f[6] & 0x10) != 0x00 { e.BRS = true } e.DLC = f[6] & 0x0F e.RXTS = (uint16(f[5]) << 8) + uint16(f[4]) for i := byte(0); i < CANDlcToLength(e.DLC, e.FDF); i++ { e.DB[i] = f[i+8] } can.Bus.RXF0A.ReplaceBits(idx, sam.CAN_RXF0A_F0AI_Msk, sam.CAN_RXF0A_F0AI_Pos) } // Rx receives a CAN frame. It is easier to use than RxRaw, but not as // flexible. func (can *CAN) Rx() (id uint32, dlc byte, data []byte, isFd, isExtendedID bool) { e := CANRxBufferElement{} can.RxRaw(&e) length := CANDlcToLength(e.DLC, e.FDF) return e.ID, length, e.DB[:length], e.FDF, e.XTD } func (can *CAN) instance() byte { if can.Bus == sam.CAN0 { return 0 } else { return 1 } } // CANTxBufferElement is a struct that corresponds to the same5x' Tx Buffer // Element. type CANTxBufferElement struct { ESI bool XTD bool RTR bool ID uint32 MM uint8 EFC bool FDF bool BRS bool DLC uint8 DB [64]uint8 } // CANRxBufferElement is a struct that corresponds to the same5x Rx Buffer and // FIFO Element. type CANRxBufferElement struct { ESI bool XTD bool RTR bool ID uint32 ANMF bool FIDX uint8 FDF bool BRS bool DLC uint8 RXTS uint16 DB [64]uint8 } // Data returns the received data as a slice of the size according to dlc. func (e CANRxBufferElement) Data() []byte { return e.DB[:CANDlcToLength(e.DLC, e.FDF)] } // Length returns its actual length. func (e CANRxBufferElement) Length() byte { return CANDlcToLength(e.DLC, e.FDF) } // CANDlcToLength() converts a DLC value to its actual length. func CANDlcToLength(dlc byte, isFD bool) byte { length := dlc if dlc == 0x09 { length = 12 } else if dlc == 0x0A { length = 16 } else if dlc == 0x0B { length = 20 } else if dlc == 0x0C { length = 24 } else if dlc == 0x0D { length = 32 } else if dlc == 0x0E { length = 48 } else if dlc == 0x0F { length = 64 } return length } // CANLengthToDlc() converts its actual length to a DLC value. func CANLengthToDlc(length byte, isFD bool) byte { dlc := length if length <= 0x08 { } else if length <= 12 { dlc = 0x09 } else if length <= 16 { dlc = 0x0A } else if length <= 20 { dlc = 0x0B } else if length <= 24 { dlc = 0x0C } else if length <= 32 { dlc = 0x0D } else if length <= 48 { dlc = 0x0E } else if length <= 64 { dlc = 0x0F } return dlc } ================================================ FILE: src/machine/machine_attiny1616.go ================================================ //go:build attiny1616 package machine import ( "device/avr" ) const ( portA Pin = iota * 8 portB portC ) const ( PA0 = portA + 0 PA1 = portA + 1 PA2 = portA + 2 PA3 = portA + 3 PA4 = portA + 4 PA5 = portA + 5 PA6 = portA + 6 PA7 = portA + 7 PB0 = portB + 0 PB1 = portB + 1 PB2 = portB + 2 PB3 = portB + 3 PB4 = portB + 4 PB5 = portB + 5 PB6 = portB + 6 PB7 = portB + 7 PC0 = portC + 0 PC1 = portC + 1 PC2 = portC + 2 PC3 = portC + 3 PC4 = portC + 4 PC5 = portC + 5 PC6 = portC + 6 PC7 = portC + 7 ) // getPortMask returns the PORT peripheral and mask for the pin. func (p Pin) getPortMask() (*avr.PORT_Type, uint8) { switch { case p >= PA0 && p <= PA7: // port A return avr.PORTA, 1 << uint8(p-portA) case p >= PB0 && p <= PB7: // port B return avr.PORTB, 1 << uint8(p-portB) default: // port C return avr.PORTC, 1 << uint8(p-portC) } } ================================================ FILE: src/machine/machine_attiny85.go ================================================ //go:build attiny85 package machine import ( "device/avr" "runtime/volatile" ) const ( PB0 Pin = iota PB1 PB2 PB3 PB4 PB5 ) // getPortMask returns the PORTx register and mask for the pin. func (p Pin) getPortMask() (*volatile.Register8, uint8) { // Very simple for the attiny85, which only has a single port. return avr.PORTB, 1 << uint8(p) } ================================================ FILE: src/machine/machine_avr.go ================================================ //go:build avr && !avrtiny package machine import ( "device/avr" "runtime/volatile" "unsafe" ) const deviceName = avr.DEVICE const ( PinInput PinMode = iota PinInputPullup PinOutput ) // In all the AVRs I've looked at, the PIN/DDR/PORT registers followed a regular // pattern: PINx, DDRx, PORTx in this order without registers in between. // Therefore, if you know any of them, you can calculate the other two. // // For now, I've chosen to let the PORTx register be the one that is returned // for each specific chip and to calculate the others from that one. Setting an // output port (done using PORTx) is likely the most common operation and the // one that is the most time critical. For others, the PINx and DDRx register // can trivially be calculated using a subtraction. // Configure sets the pin to input or output. func (p Pin) Configure(config PinConfig) { port, mask := p.getPortMask() // The DDRx register can be found by subtracting one from the PORTx // register, as this appears to be the case for many (most? all?) AVR chips. ddr := (*volatile.Register8)(unsafe.Pointer(uintptr(unsafe.Pointer(port)) - 1)) if config.Mode == PinOutput { // set output bit ddr.SetBits(mask) // Note: if the pin was PinInputPullup before, it'll now be high. // Otherwise it will be low. } else { // configure input: clear output bit ddr.ClearBits(mask) if config.Mode == PinInput { // No pullup (floating). // The transition may be one of the following: // output high -> input pullup -> input (safe: output high and input pullup are similar) // output low -> input -> input (safe: no extra transition) port.ClearBits(mask) } else { // Pullup. // The transition may be one of the following: // output high -> input pullup -> input pullup (safe: no extra transition) // output low -> input -> input pullup (possibly problematic) // For the last transition (output low -> input -> input pullup), // the transition may be problematic in some cases because there is // an intermediate floating state (which may cause irratic // interrupts, for example). If this is a problem, the application // should set the pin high before configuring it as PinInputPullup. // We can't do that here because setting it to high as an // intermediate state may have other problems. port.SetBits(mask) } } } // Get returns the current value of a GPIO pin when the pin is configured as an // input or as an output. func (p Pin) Get() bool { port, mask := p.getPortMask() // As noted above, the PINx register is always two registers below the PORTx // register, so we can find it simply by subtracting two from the PORTx // register address. pin := (*volatile.Register8)(unsafe.Pointer(uintptr(unsafe.Pointer(port)) - 2)) // PINA, PINB, etc return (pin.Get() & mask) > 0 } // Set changes the value of the GPIO pin. The pin must be configured as output. func (p Pin) Set(value bool) { if value { // set bits port, mask := p.PortMaskSet() port.Set(mask) } else { // clear bits port, mask := p.PortMaskClear() port.Set(mask) } } // Return the register and mask to enable a given GPIO pin. This can be used to // implement bit-banged drivers. // // Warning: there are no separate pin set/clear registers on the AVR. The // returned mask is only valid as long as no other pin in the same port has been // changed. func (p Pin) PortMaskSet() (*volatile.Register8, uint8) { port, mask := p.getPortMask() return port, port.Get() | mask } // Return the register and mask to disable a given port. This can be used to // implement bit-banged drivers. // // Warning: there are no separate pin set/clear registers on the AVR. The // returned mask is only valid as long as no other pin in the same port has been // changed. func (p Pin) PortMaskClear() (*volatile.Register8, uint8) { port, mask := p.getPortMask() return port, port.Get() &^ mask } // InitADC initializes the registers needed for ADC. func InitADC() { // set a2d prescaler so we are inside the desired 50-200 KHz range at 16MHz. avr.ADCSRA.SetBits(avr.ADCSRA_ADPS2 | avr.ADCSRA_ADPS1 | avr.ADCSRA_ADPS0) // enable a2d conversions avr.ADCSRA.SetBits(avr.ADCSRA_ADEN) } // Configure configures a ADCPin to be able to be used to read data. func (a ADC) Configure(ADCConfig) { return // no pin specific setup on AVR machine. } // Get returns the current value of a ADC pin, in the range 0..0xffff. The AVR // has an ADC of 10 bits precision so the lower 6 bits will be zero. func (a ADC) Get() uint16 { // set the analog reference (high two bits of ADMUX) and select the // channel (low 4 bits), masked to only turn on one ADC at a time. // set the ADLAR bit (left-adjusted result) to get a value scaled to 16 // bits. This has the same effect as shifting the return value left by 6 // bits. avr.ADMUX.Set(avr.ADMUX_REFS0 | avr.ADMUX_ADLAR | (uint8(a.Pin) & 0x07)) // start the conversion avr.ADCSRA.SetBits(avr.ADCSRA_ADSC) // ADSC is cleared when the conversion finishes for ok := true; ok; ok = avr.ADCSRA.HasBits(avr.ADCSRA_ADSC) { } return uint16(avr.ADCL.Get()) | uint16(avr.ADCH.Get())<<8 } // linked from runtime.adjustMonotonicTimer func adjustMonotonicTimer() // linked from runtime.initMonotonicTimer func initMonotonicTimer() ================================================ FILE: src/machine/machine_avrtiny.go ================================================ //go:build avrtiny package machine import ( "device/avr" "runtime/volatile" "unsafe" ) const deviceName = avr.DEVICE const ( PinInput PinMode = iota PinInputPullup PinOutput ) // Configure sets the pin to input or output. func (p Pin) Configure(config PinConfig) { port, mask := p.getPortMask() if config.Mode == PinOutput { // set output bit port.DIRSET.Set(mask) // Note: the output state (high or low) is as it was before. } else { // Configure the pin as an input. // First set up the configuration that will be used when it is an input. pinctrl := uint8(0) if config.Mode == PinInputPullup { pinctrl |= avr.PORT_PIN0CTRL_PULLUPEN } // Find the PINxCTRL register for this pin. ctrlAddress := (*volatile.Register8)(unsafe.Add(unsafe.Pointer(&port.PIN0CTRL), p%8)) ctrlAddress.Set(pinctrl) // Configure the pin as input (if it wasn't an input pin before). port.DIRCLR.Set(mask) } } // Get returns the current value of a GPIO pin when the pin is configured as an // input or as an output. func (p Pin) Get() bool { port, mask := p.getPortMask() // As noted above, the PINx register is always two registers below the PORTx // register, so we can find it simply by subtracting two from the PORTx // register address. return (port.IN.Get() & mask) > 0 } // Set changes the value of the GPIO pin. The pin must be configured as output. func (p Pin) Set(high bool) { port, mask := p.getPortMask() if high { port.OUTSET.Set(mask) } else { port.OUTCLR.Set(mask) } } ================================================ FILE: src/machine/machine_cortexm.go ================================================ //go:build cortexm package machine import "device/arm" // CPUReset performs a hard system reset. func CPUReset() { arm.SystemReset() } ================================================ FILE: src/machine/machine_esp32.go ================================================ //go:build esp32 package machine import ( "device/esp" "errors" "runtime/volatile" "unsafe" ) const deviceName = esp.Device const peripheralClock = 80000000 // 80MHz // CPUFrequency returns the current CPU frequency of the chip. // Currently it is a fixed frequency but it may allow changing in the future. func CPUFrequency() uint32 { return 160e6 // 160MHz } var ( ErrInvalidSPIBus = errors.New("machine: invalid SPI bus") ) const ( PinOutput PinMode = iota PinInput PinInputPullup PinInputPulldown ) // Hardware pin numbers const ( GPIO0 Pin = 0 GPIO1 Pin = 1 GPIO2 Pin = 2 GPIO3 Pin = 3 GPIO4 Pin = 4 GPIO5 Pin = 5 GPIO6 Pin = 6 GPIO7 Pin = 7 GPIO8 Pin = 8 GPIO9 Pin = 9 GPIO10 Pin = 10 GPIO11 Pin = 11 GPIO12 Pin = 12 GPIO13 Pin = 13 GPIO14 Pin = 14 GPIO15 Pin = 15 GPIO16 Pin = 16 GPIO17 Pin = 17 GPIO18 Pin = 18 GPIO19 Pin = 19 GPIO21 Pin = 21 GPIO22 Pin = 22 GPIO23 Pin = 23 GPIO25 Pin = 25 GPIO26 Pin = 26 GPIO27 Pin = 27 GPIO32 Pin = 32 GPIO33 Pin = 33 GPIO34 Pin = 34 GPIO35 Pin = 35 GPIO36 Pin = 36 GPIO37 Pin = 37 GPIO38 Pin = 38 GPIO39 Pin = 39 ) // Configure this pin with the given configuration. func (p Pin) Configure(config PinConfig) { // Output function 256 is a special value reserved for use as a regular GPIO // pin. Peripherals (SPI etc) can set a custom output function by calling // lowercase configure() instead with a signal name. p.configure(config, 256) } // configure is the same as Configure, but allows for setting a specific input // or output signal. // Signals are always routed through the GPIO matrix for simplicity. Output // signals are configured in FUNCx_OUT_SEL_CFG which selects a particular signal // to output on a given pin. Input signals are configured in FUNCy_IN_SEL_CFG, // which sets the pin to use for a particular input signal. func (p Pin) configure(config PinConfig, signal uint32) { if p == NoPin { // This simplifies pin configuration in peripherals such as SPI. return } var muxConfig uint32 // The mux configuration. // Configure this pin as a GPIO pin. const function = 3 // function 3 is GPIO for every pin muxConfig |= (function - 1) << esp.IO_MUX_GPIO0_MCU_SEL_Pos // Make this pin an input pin (always). muxConfig |= esp.IO_MUX_GPIO0_FUN_IE // Set drive strength: 0 is lowest, 3 is highest. muxConfig |= 2 << esp.IO_MUX_GPIO0_FUN_DRV_Pos // Select pull mode. if config.Mode == PinInputPullup { muxConfig |= esp.IO_MUX_GPIO0_FUN_WPU } else if config.Mode == PinInputPulldown { muxConfig |= esp.IO_MUX_GPIO0_FUN_WPD } // Configure the pad with the given IO mux configuration. p.mux().Set(muxConfig) switch config.Mode { case PinOutput: // Set the 'output enable' bit. if p < 32 { esp.GPIO.ENABLE_W1TS.Set(1 << p) } else { esp.GPIO.ENABLE1_W1TS.Set(1 << (p - 32)) } // Set the signal to read the output value from. It can be a peripheral // output signal, or the special value 256 which indicates regular GPIO // usage. p.outFunc().Set(signal) case PinInput, PinInputPullup, PinInputPulldown: // Clear the 'output enable' bit. if p < 32 { esp.GPIO.ENABLE_W1TC.Set(1 << p) } else { esp.GPIO.ENABLE1_W1TC.Set(1 << (p - 32)) } if signal != 256 { // Signal is a peripheral function (not a simple GPIO). Connect this // signal to the pin. // Note that outFunc and inFunc work in the opposite direction. // outFunc configures a pin to use a given output signal, while // inFunc specifies a pin to use to read the signal from. inFunc(signal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(p)< pad mapping. // I couldn't find it. switch p { case 36: return &esp.IO_MUX.GPIO36 case 37: return &esp.IO_MUX.GPIO37 case 38: return &esp.IO_MUX.GPIO38 case 39: return &esp.IO_MUX.GPIO39 case 34: return &esp.IO_MUX.GPIO34 case 35: return &esp.IO_MUX.GPIO35 case 32: return &esp.IO_MUX.GPIO32 case 33: return &esp.IO_MUX.GPIO33 case 25: return &esp.IO_MUX.GPIO25 case 26: return &esp.IO_MUX.GPIO26 case 27: return &esp.IO_MUX.GPIO27 case 14: return &esp.IO_MUX.GPIO14 case 12: return &esp.IO_MUX.GPIO12 case 13: return &esp.IO_MUX.GPIO13 case 15: return &esp.IO_MUX.GPIO15 case 2: return &esp.IO_MUX.GPIO2 case 0: return &esp.IO_MUX.GPIO0 case 4: return &esp.IO_MUX.GPIO4 case 16: return &esp.IO_MUX.GPIO16 case 17: return &esp.IO_MUX.GPIO17 case 9: return &esp.IO_MUX.GPIO9 case 10: return &esp.IO_MUX.GPIO10 case 11: return &esp.IO_MUX.GPIO11 case 6: return &esp.IO_MUX.GPIO6 case 7: return &esp.IO_MUX.GPIO7 case 8: return &esp.IO_MUX.GPIO8 case 5: return &esp.IO_MUX.GPIO5 case 18: return &esp.IO_MUX.GPIO18 case 19: return &esp.IO_MUX.GPIO19 case 20: return &esp.IO_MUX.GPIO20 case 21: return &esp.IO_MUX.GPIO21 case 22: return &esp.IO_MUX.GPIO22 case 3: return &esp.IO_MUX.GPIO3 case 1: return &esp.IO_MUX.GPIO1 case 23: return &esp.IO_MUX.GPIO23 case 24: return &esp.IO_MUX.GPIO24 default: return nil } } var DefaultUART = UART0 var ( UART0 = &_UART0 _UART0 = UART{Bus: esp.UART0, Buffer: NewRingBuffer()} UART1 = &_UART1 _UART1 = UART{Bus: esp.UART1, Buffer: NewRingBuffer()} UART2 = &_UART2 _UART2 = UART{Bus: esp.UART2, Buffer: NewRingBuffer()} ) type UART struct { Bus *esp.UART_Type Buffer *RingBuffer } func (uart *UART) Configure(config UARTConfig) { if config.BaudRate == 0 { config.BaudRate = 115200 } uart.Bus.CLKDIV.Set(peripheralClock / config.BaudRate) } func (uart *UART) writeByte(b byte) error { for (uart.Bus.STATUS.Get()>>16)&0xff >= 128 { // Read UART_TXFIFO_CNT from the status register, which indicates how // many bytes there are in the transmit buffer. Wait until there are // less than 128 bytes in this buffer (the default buffer size). } // Write to the TX_FIFO register. (*volatile.Register8)(unsafe.Add(unsafe.Pointer(uart.Bus), 0x200C0000)).Set(b) return nil } func (uart *UART) flush() {} // Serial Peripheral Interface on the ESP32. type SPI struct { Bus *esp.SPI_Type } var ( // SPI0 and SPI1 are reserved for use by the caching system etc. SPI2 = &SPI{esp.SPI2} SPI3 = &SPI{esp.SPI3} ) // SPIConfig configures a SPI peripheral on the ESP32. Make sure to set at least // SCK, SDO and SDI (possibly to NoPin if not in use). The default for LSBFirst // (false) and Mode (0) are good for most applications. The frequency defaults // to 1MHz if not set but can be configured up to 40MHz. Possible values are // 40MHz and integer divisions from 40MHz such as 20MHz, 13.3MHz, 10MHz, 8MHz, // etc. type SPIConfig struct { Frequency uint32 SCK Pin SDO Pin SDI Pin LSBFirst bool Mode uint8 } // Configure and make the SPI peripheral ready to use. func (spi *SPI) Configure(config SPIConfig) error { if config.Frequency == 0 { config.Frequency = 4e6 // default to 4MHz } // Configure the SPI clock. This assumes a peripheral clock of 80MHz. var clockReg uint32 if config.Frequency > 40e6 { // Don't use a prescaler, but directly connect to the APB clock. This // results in a SPI clock frequency of 40MHz. clockReg |= esp.SPI_CLOCK_CLK_EQU_SYSCLK } else { // Use a prescaler for frequencies below 40MHz. They will get rounded // down to the next possible frequency (20MHz, 13.3MHz, 10MHz, 8MHz, // 6.7MHz, 5.7MHz, 5MHz, etc). // This code is much simpler than how ESP-IDF configures the frequency, // but should be just as accurate. The only exception is for frequencies // below 4883Hz, which will need special support. if config.Frequency < 4883 { // The current lower limit is 4883Hz. // The hardware supports lower frequencies by setting the h and n // variables, but that's not yet implemented. config.Frequency = 4883 } // The prescaler value is 40e6 / config.Frequency, but rounded up so // that the actual frequency is never higher than the frequency // requested in config.Frequency. var ( pre uint32 = (40e6 + config.Frequency - 1) / config.Frequency n uint32 = 2 // this value seems to equal the number of ticks per SPI clock tick h uint32 = 1 // must be half of n according to the formula in the reference manual l uint32 = n // must equal n according to the reference manual ) clockReg |= (pre - 1) << esp.SPI_CLOCK_CLKDIV_PRE_Pos clockReg |= (n - 1) << esp.SPI_CLOCK_CLKCNT_N_Pos clockReg |= (h - 1) << esp.SPI_CLOCK_CLKCNT_H_Pos clockReg |= (l - 1) << esp.SPI_CLOCK_CLKCNT_L_Pos } spi.Bus.CLOCK.Set(clockReg) // SPI_CTRL_REG controls bit order. var ctrlReg uint32 if config.LSBFirst { ctrlReg |= esp.SPI_CTRL_WR_BIT_ORDER ctrlReg |= esp.SPI_CTRL_RD_BIT_ORDER } spi.Bus.CTRL.Set(ctrlReg) // SPI_CTRL2_REG, SPI_USER_REG and SPI_PIN_REG control SPI clock polarity // (mode), among others. var ctrl2Reg, userReg, pinReg uint32 // For mode configuration, see table 29 in the reference manual (page 128). switch config.Mode { case 0: case 1: userReg |= esp.SPI_USER_CK_OUT_EDGE case 2: userReg |= esp.SPI_USER_CK_OUT_EDGE pinReg |= esp.SPI_PIN_CK_IDLE_EDGE case 3: pinReg |= esp.SPI_PIN_CK_IDLE_EDGE } // Enable full-duplex communication. userReg |= esp.SPI_USER_DOUTDIN userReg |= esp.SPI_USER_USR_MOSI // Write values to registers. spi.Bus.CTRL2.Set(ctrl2Reg) spi.Bus.USER.Set(userReg) spi.Bus.PIN.Set(pinReg) // Configure pins. // TODO: use direct output if possible, if the configured pins match the // possible direct configurations (e.g. for SPI2, when SCK is pin 14 etc). if spi.Bus == esp.SPI2 { config.SCK.configure(PinConfig{Mode: PinOutput}, 8) // HSPICLK config.SDI.configure(PinConfig{Mode: PinInput}, 9) // HSPIQ config.SDO.configure(PinConfig{Mode: PinOutput}, 10) // HSPID } else if spi.Bus == esp.SPI3 { config.SCK.configure(PinConfig{Mode: PinOutput}, 63) // VSPICLK config.SDI.configure(PinConfig{Mode: PinInput}, 64) // VSPIQ config.SDO.configure(PinConfig{Mode: PinOutput}, 65) // VSPID } else { // Don't know how to configure this bus. return ErrInvalidSPIBus } return nil } // Transfer writes/reads a single byte using the SPI interface. If you need to // transfer larger amounts of data, Tx will be faster. func (spi *SPI) Transfer(w byte) (byte, error) { spi.Bus.MISO_DLEN.Set(7 << esp.SPI_MISO_DLEN_USR_MISO_DBITLEN_Pos) spi.Bus.MOSI_DLEN.Set(7 << esp.SPI_MOSI_DLEN_USR_MOSI_DBITLEN_Pos) spi.Bus.W0.Set(uint32(w)) // Send/receive byte. spi.Bus.CMD.Set(esp.SPI_CMD_USR) for spi.Bus.CMD.Get() != 0 { } // The received byte is stored in W0. return byte(spi.Bus.W0.Get()), nil } // Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read // interface, there must always be the same number of bytes written as bytes read. // This is accomplished by sending zero bits if r is bigger than w or discarding // the incoming data if w is bigger than r. func (spi *SPI) Tx(w, r []byte) error { toTransfer := len(w) if len(r) > toTransfer { toTransfer = len(r) } for toTransfer != 0 { // Do only 64 bytes at a time. chunkSize := toTransfer if chunkSize > 64 { chunkSize = 64 } // Fill tx buffer. transferWords := (*[16]volatile.Register32)(unsafe.Pointer(uintptr(unsafe.Pointer(&spi.Bus.W0)))) if len(w) >= 64 { // We can fill the entire 64-byte transfer buffer with data. // This loop is slightly faster than the loop below. for i := 0; i < 16; i++ { word := uint32(w[i*4])<<0 | uint32(w[i*4+1])<<8 | uint32(w[i*4+2])<<16 | uint32(w[i*4+3])<<24 transferWords[i].Set(word) } } else { // We can't fill the entire transfer buffer, so we need to be a bit // more careful. // Note that parts of the transfer buffer that aren't used still // need to be set to zero, otherwise we might be transferring // garbage from a previous transmission if w is smaller than r. for i := 0; i < 16; i++ { var word uint32 if i*4+3 < len(w) { word |= uint32(w[i*4+3]) << 24 } if i*4+2 < len(w) { word |= uint32(w[i*4+2]) << 16 } if i*4+1 < len(w) { word |= uint32(w[i*4+1]) << 8 } if i*4+0 < len(w) { word |= uint32(w[i*4+0]) << 0 } transferWords[i].Set(word) } } // Do the transfer. spi.Bus.MISO_DLEN.Set((uint32(chunkSize)*8 - 1) << esp.SPI_MISO_DLEN_USR_MISO_DBITLEN_Pos) spi.Bus.MOSI_DLEN.Set((uint32(chunkSize)*8 - 1) << esp.SPI_MOSI_DLEN_USR_MOSI_DBITLEN_Pos) spi.Bus.CMD.Set(esp.SPI_CMD_USR) for spi.Bus.CMD.Get() != 0 { } // Read rx buffer. rxSize := 64 if rxSize > len(r) { rxSize = len(r) } for i := 0; i < rxSize; i++ { r[i] = byte(transferWords[i/4].Get() >> ((i % 4) * 8)) } // Cut off some part of the output buffer so the next iteration we will // only send the remaining bytes. if len(w) < chunkSize { w = nil } else { w = w[chunkSize:] } if len(r) < chunkSize { r = nil } else { r = r[chunkSize:] } toTransfer -= chunkSize } return nil } ================================================ FILE: src/machine/machine_esp32_i2c.go ================================================ //go:build esp32 package machine import ( "device/esp" "runtime/volatile" "unsafe" ) var ( I2C0 = &I2C{Bus: esp.I2C0, funcSCL: 29, funcSDA: 30} I2C1 = &I2C{Bus: esp.I2C1, funcSCL: 95, funcSDA: 96} ) type I2C struct { Bus *esp.I2C_Type funcSCL, funcSDA uint32 config I2CConfig } // I2CConfig is used to store config info for I2C. type I2CConfig struct { Frequency uint32 // in Hz SCL Pin SDA Pin } const ( i2cClkSourceFrequency = uint32(80 * MHz) ) func (i2c *I2C) Configure(config I2CConfig) error { if config.Frequency == 0 { config.Frequency = 400 * KHz } if config.SCL == 0 { config.SCL = SCL_PIN } if config.SDA == 0 { config.SDA = SDA_PIN } i2c.config = config i2c.initAll() return nil } func (i2c *I2C) initAll() { i2c.initClock() i2c.initNoiseFilter() i2c.initPins() i2c.initFrequency() i2c.startMaster() } //go:inline func (i2c *I2C) initClock() { // reset I2C clock if i2c.Bus == esp.I2C0 { esp.DPORT.SetPERIP_RST_EN_I2C0_EXT0_RST(1) esp.DPORT.SetPERIP_CLK_EN_I2C0_EXT0_CLK_EN(1) esp.DPORT.SetPERIP_RST_EN_I2C0_EXT0_RST(0) } else { esp.DPORT.SetPERIP_RST_EN_I2C_EXT1_RST(1) esp.DPORT.SetPERIP_CLK_EN_I2C_EXT1_CLK_EN(1) esp.DPORT.SetPERIP_RST_EN_I2C_EXT1_RST(0) } // disable interrupts i2c.Bus.INT_ENA.Set(0) i2c.Bus.INT_CLR.Set(0x3fff) i2c.Bus.SetCTR_CLK_EN(1) } //go:inline func (i2c *I2C) initNoiseFilter() { i2c.Bus.SCL_FILTER_CFG.Set(0xF) i2c.Bus.SDA_FILTER_CFG.Set(0xF) } //go:inline func (i2c *I2C) initPins() { var muxConfig uint32 const function = 2 // function 2 is just GPIO // SDA muxConfig = function << esp.IO_MUX_GPIO0_MCU_SEL_Pos // Make this pin an input pin (always). muxConfig |= esp.IO_MUX_GPIO0_FUN_IE // Set drive strength: 0 is lowest, 3 is highest. muxConfig |= 1 << esp.IO_MUX_GPIO0_FUN_DRV_Pos i2c.config.SDA.mux().Set(muxConfig) i2c.config.SDA.outFunc().Set(i2c.funcSDA) inFunc(i2c.funcSDA).Set(uint32(esp.GPIO_FUNC_IN_SEL_CFG_SEL | i2c.config.SDA)) i2c.config.SDA.Set(true) // Configure the pad with the given IO mux configuration. i2c.config.SDA.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER) esp.GPIO.ENABLE_W1TS.Set(1 << int(i2c.config.SDA)) i2c.Bus.SetCTR_SDA_FORCE_OUT(1) // SCL muxConfig = function << esp.IO_MUX_GPIO0_MCU_SEL_Pos // Make this pin an input pin (always). muxConfig |= esp.IO_MUX_GPIO0_FUN_IE // Set drive strength: 0 is lowest, 3 is highest. muxConfig |= 1 << esp.IO_MUX_GPIO0_FUN_DRV_Pos i2c.config.SCL.mux().Set(muxConfig) i2c.config.SCL.outFunc().Set(i2c.funcSCL) inFunc(i2c.funcSCL).Set(uint32(esp.GPIO_FUNC_IN_SEL_CFG_SEL | i2c.config.SCL)) i2c.config.SCL.Set(true) // Configure the pad with the given IO mux configuration. i2c.config.SCL.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER) esp.GPIO.ENABLE_W1TS.Set(1 << int(i2c.config.SCL)) i2c.Bus.SetCTR_SCL_FORCE_OUT(1) } //go:inline func (i2c *I2C) initFrequency() { clkmDiv := i2cClkSourceFrequency/(i2c.config.Frequency*1024) + 1 sclkFreq := i2cClkSourceFrequency / clkmDiv halfCycle := sclkFreq / i2c.config.Frequency / 2 //SCL sclLow := halfCycle sclWaitHigh := uint32(0) if i2c.config.Frequency > 50000 { sclWaitHigh = halfCycle / 8 // compensate the time when freq > 50K } sclHigh := halfCycle - sclWaitHigh // SDA sdaHold := halfCycle / 4 sda_sample := halfCycle / 2 setup := halfCycle hold := halfCycle i2c.Bus.SetSCL_LOW_PERIOD(sclLow - 1) i2c.Bus.SetSCL_HIGH_PERIOD(sclHigh) i2c.Bus.SetSCL_RSTART_SETUP_TIME(setup) i2c.Bus.SetSCL_STOP_SETUP_TIME(setup) i2c.Bus.SetSCL_START_HOLD_TIME(hold - 1) i2c.Bus.SetSCL_STOP_HOLD_TIME(hold - 1) i2c.Bus.SetSDA_SAMPLE_TIME(sda_sample) i2c.Bus.SetSDA_HOLD_TIME(sdaHold) // set timeout value i2c.Bus.SetTO_TIME_OUT(20 * halfCycle) } //go:inline func (i2c *I2C) startMaster() { // FIFO mode for data i2c.Bus.SetFIFO_CONF_NONFIFO_EN(0) // Reset TX & RX buffers i2c.Bus.SetFIFO_CONF_RX_FIFO_RST(1) i2c.Bus.SetFIFO_CONF_RX_FIFO_RST(0) i2c.Bus.SetFIFO_CONF_TX_FIFO_RST(1) i2c.Bus.SetFIFO_CONF_TX_FIFO_RST(0) // enable master mode i2c.Bus.SetCTR_MS_MODE(1) } func (i2c *I2C) resetBus() { // unlike esp32c3, the esp32 i2c modules do not have a reset fsm register, // so we need to: // 1. disconnect the pins // 2. generate a stop condition manually // 3. do a full reset // 4. redo all configuration i2c.config.SDA.mux().Set(2< 0 && c.head < len(c.data); count, c.head = count-1, c.head+1 { i2c.Bus.SetDATA_FIFO_RDATA(uint32(c.data[c.head])) } reg.Set(i2cCMD_WRITE | uint32(32-count)) reg = nextAddress(reg) if c.head < len(c.data) { reg.Set(i2cCMD_END) reg = nil } else { cmdIdx++ } needRestart = true case i2cCMD_READ: if needAddress { needAddress = false i2c.Bus.SetDATA_FIFO_RDATA((uint32(addr)&0x7f)<<1 | 1) i2c.Bus.SLAVE_ADDR.Set(uint32(addr)) reg.Set(i2cCMD_WRITE | 1) reg = nextAddress(reg) } if needRestart { // We need to send RESTART again after i2cCMD_WRITE. reg.Set(i2cCMD_RSTART) reg = nextAddress(reg) reg.Set(i2cCMD_WRITE | 1) reg = nextAddress(reg) i2c.Bus.SetDATA_FIFO_RDATA((uint32(addr)&0x7f)<<1 | 1) needRestart = false } count := 32 bytes := len(c.data) - c.head // Only last byte in sequence must be sent with ACK set to 1 to indicate end of data. split := bytes <= count if split { bytes-- } if bytes > 32 { bytes = 32 } if bytes > 0 { reg.Set(i2cCMD_READ | uint32(bytes)) reg = nextAddress(reg) } if split { readLast = true reg.Set(i2cCMD_READLAST | 1) reg = nextAddress(reg) readTo = c.data[c.head : c.head+bytes+1] // read bytes + 1 last byte cmdIdx++ } else { reg.Set(i2cCMD_END) readTo = c.data[c.head : c.head+bytes] reg = nil } case i2cCMD_STOP: reg.Set(i2cCMD_STOP) reg = nil cmdIdx++ } if reg == nil { // transmit now i2c.Bus.SetCTR_TRANS_START(1) end := nanotime() + timeoutNS var mask uint32 for mask = i2c.Bus.INT_STATUS.Get(); mask&intMask == 0; mask = i2c.Bus.INT_STATUS.Get() { if nanotime() > end { // timeout leaves the bus in an undefined state, reset i2c.resetBus() if readTo != nil { return errI2CReadTimeout } return errI2CWriteTimeout } } switch { case mask&esp.I2C_INT_STATUS_ACK_ERR_INT_ST_Msk != 0 && !readLast: return errI2CAckExpected case mask&esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk != 0: // timeout leaves the bus in an undefined state, reset i2c.resetBus() if readTo != nil { return errI2CReadTimeout } return errI2CWriteTimeout } i2c.Bus.INT_CLR.SetBits(intMask) for i := 0; i < len(readTo); i++ { readTo[i] = byte(i2c.Bus.GetDATA_FIFO_RDATA() & 0xff) c.head++ } readTo = nil reg = &i2c.Bus.COMD0 } } return nil } // Tx does a single I2C transaction at the specified address. // It clocks out the given address, writes the bytes in w, reads back len(r) // bytes and stores them in r, and generates a stop condition on the bus. func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) { // timeout in microseconds. const timeout = 40 // 40ms is a reasonable time for a real-time system. cmd := make([]i2cCommand, 0, 8) cmd = append(cmd, i2cCommand{cmd: i2cCMD_RSTART}) if len(w) > 0 { cmd = append(cmd, i2cCommand{cmd: i2cCMD_WRITE, data: w}) } if len(r) > 0 { cmd = append(cmd, i2cCommand{cmd: i2cCMD_READ, data: r}) } cmd = append(cmd, i2cCommand{cmd: i2cCMD_STOP}) return i2c.transmit(addr, cmd, timeout) } func (i2c *I2C) SetBaudRate(br uint32) error { return errI2CNotImplemented } func (p Pin) pinReg() *volatile.Register32 { return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.PIN0)) + uintptr(p)*4))) } func nextAddress(reg *volatile.Register32) *volatile.Register32 { return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(reg), 4)) } // CheckDevice does an empty I2C transaction at the specified address. // This can be used to find out if any device with that address is // connected, e.g. for enumerating all devices on the bus. func (i2c *I2C) CheckDevice(addr uint16) bool { // timeout in microseconds. const timeout = 40 // 40ms is a reasonable time for a real-time system. cmd := []i2cCommand{ {cmd: i2cCMD_RSTART}, {cmd: i2cCMD_WRITE}, {cmd: i2cCMD_STOP}, } return i2c.transmit(addr, cmd, timeout) == nil } ================================================ FILE: src/machine/machine_esp32c3.go ================================================ //go:build esp32c3 package machine import ( "device/esp" "device/riscv" "errors" "runtime/interrupt" "runtime/volatile" "sync" "unsafe" ) const deviceName = esp.Device const maxPin = 22 const cpuInterruptFromPin = 6 // CPUFrequency returns the current CPU frequency of the chip. // Currently it is a fixed frequency but it may allow changing in the future. func CPUFrequency() uint32 { return 160e6 // 160MHz } const ( PinOutput PinMode = iota PinInput PinInputPullup PinInputPulldown ) const ( GPIO0 Pin = 0 GPIO1 Pin = 1 GPIO2 Pin = 2 GPIO3 Pin = 3 GPIO4 Pin = 4 GPIO5 Pin = 5 GPIO6 Pin = 6 GPIO7 Pin = 7 GPIO8 Pin = 8 GPIO9 Pin = 9 GPIO10 Pin = 10 GPIO11 Pin = 11 GPIO12 Pin = 12 GPIO13 Pin = 13 GPIO14 Pin = 14 GPIO15 Pin = 15 GPIO16 Pin = 16 GPIO17 Pin = 17 GPIO18 Pin = 18 GPIO19 Pin = 19 GPIO20 Pin = 20 GPIO21 Pin = 21 ) type PinChange uint8 // Pin change interrupt constants for SetInterrupt. const ( PinRising PinChange = iota + 1 PinFalling PinToggle ) // Configure this pin with the given configuration. func (p Pin) Configure(config PinConfig) { if p == NoPin { // This simplifies pin configuration in peripherals such as SPI. return } var muxConfig uint32 // Configure this pin as a GPIO pin. const function = 1 // function 1 is GPIO for every pin muxConfig |= function << esp.IO_MUX_GPIO_MCU_SEL_Pos // Make this pin an input pin (always). muxConfig |= esp.IO_MUX_GPIO_FUN_IE // Set drive strength: 0 is lowest, 3 is highest. muxConfig |= 2 << esp.IO_MUX_GPIO_FUN_DRV_Pos // Select pull mode. if config.Mode == PinInputPullup { muxConfig |= esp.IO_MUX_GPIO_FUN_WPU } else if config.Mode == PinInputPulldown { muxConfig |= esp.IO_MUX_GPIO_FUN_WPD } // Configure the pad with the given IO mux configuration. p.mux().Set(muxConfig) // Set the output signal to the simple GPIO output. p.outFunc().Set(0x80) switch config.Mode { case PinOutput: // Set the 'output enable' bit. esp.GPIO.ENABLE_W1TS.Set(1 << p) case PinInput, PinInputPullup, PinInputPulldown: // Clear the 'output enable' bit. esp.GPIO.ENABLE_W1TC.Set(1 << p) } } // outFunc returns the FUNCx_OUT_SEL_CFG register used for configuring the // output function selection. func (p Pin) outFunc() *volatile.Register32 { return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.FUNC0_OUT_SEL_CFG), uintptr(p)*4)) } func (p Pin) pinReg() *volatile.Register32 { return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.PIN0)) + uintptr(p)*4))) } // inFunc returns the FUNCy_IN_SEL_CFG register used for configuring the input // function selection. func inFunc(signal uint32) *volatile.Register32 { return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.FUNC0_IN_SEL_CFG), uintptr(signal)*4)) } // mux returns the I/O mux configuration register corresponding to the given // GPIO pin. func (p Pin) mux() *volatile.Register32 { return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.IO_MUX.GPIO0), uintptr(p)*4)) } // pin returns the PIN register corresponding to the given GPIO pin. func (p Pin) pin() *volatile.Register32 { return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.PIN0), uintptr(p)*4)) } // Set the pin to high or low. // Warning: only use this on an output pin! func (p Pin) Set(value bool) { if value { reg, mask := p.portMaskSet() reg.Set(mask) } else { reg, mask := p.portMaskClear() reg.Set(mask) } } // Get returns the current value of a GPIO pin when configured as an input or as // an output. func (p Pin) Get() bool { reg := &esp.GPIO.IN return (reg.Get()>>p)&1 > 0 } // Return the register and mask to enable a given GPIO pin. This can be used to // implement bit-banged drivers. // // Warning: only use this on an output pin! func (p Pin) PortMaskSet() (*uint32, uint32) { reg, mask := p.portMaskSet() return ®.Reg, mask } // Return the register and mask to disable a given GPIO pin. This can be used to // implement bit-banged drivers. // // Warning: only use this on an output pin! func (p Pin) PortMaskClear() (*uint32, uint32) { reg, mask := p.portMaskClear() return ®.Reg, mask } func (p Pin) portMaskSet() (*volatile.Register32, uint32) { return &esp.GPIO.OUT_W1TS, 1 << p } func (p Pin) portMaskClear() (*volatile.Register32, uint32) { return &esp.GPIO.OUT_W1TC, 1 << p } // SetInterrupt sets an interrupt to be executed when a particular pin changes // state. The pin should already be configured as an input, including a pull up // or down if no external pull is provided. // // You can pass a nil func to unset the pin change interrupt. If you do so, // the change parameter is ignored and can be set to any value (such as 0). // If the pin is already configured with a callback, you must first unset // this pins interrupt before you can set a new callback. func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) (err error) { if p >= maxPin { return ErrInvalidInputPin } if callback == nil { // Disable this pin interrupt p.pin().ClearBits(esp.GPIO_PIN_INT_TYPE_Msk | esp.GPIO_PIN_INT_ENA_Msk) if pinCallbacks[p] != nil { pinCallbacks[p] = nil } return nil } if pinCallbacks[p] != nil { // The pin was already configured. // To properly re-configure a pin, unset it first and set a new // configuration. return ErrNoPinChangeChannel } pinCallbacks[p] = callback onceSetupPinInterrupt.Do(func() { err = setupPinInterrupt() }) if err != nil { return err } p.pin().Set( (p.pin().Get() & ^uint32(esp.GPIO_PIN_INT_TYPE_Msk|esp.GPIO_PIN_INT_ENA_Msk)) | uint32(change)< 1 { return errWrongStopBitSize } // - data length uart.Bus.SetCONF0_BIT_NUM(uint32(dataBits - 5)) // - stop bit uart.Bus.SetCONF0_STOP_BIT_NUM(uint32(stopBits)) // - parity check switch parity { case ParityNone: uart.Bus.SetCONF0_PARITY_EN(0) case ParityEven: uart.Bus.SetCONF0_PARITY_EN(1) uart.Bus.SetCONF0_PARITY(0) case ParityOdd: uart.Bus.SetCONF0_PARITY_EN(1) uart.Bus.SetCONF0_PARITY(1) } return nil } func initUARTClock(bus *esp.UART_Type, regs registerSet) { uartClock := &esp.SYSTEM.PERIP_CLK_EN0 uartClockReset := &esp.SYSTEM.PERIP_RST_EN0 // Initialize/reset URATn (Ref: Initializing URATn) // - enable the clock for UART RAM uartClock.SetBits(esp.SYSTEM_PERIP_CLK_EN0_UART_MEM_CLK_EN) // - enable APB_CLK for UARTn uartClock.SetBits(regs.uartClockBitMask) // - reset sequence uartClockReset.ClearBits(regs.uartClockBitMask) bus.SetCLK_CONF_RST_CORE(1) uartClockReset.SetBits(regs.uartClockBitMask) uartClockReset.ClearBits(regs.uartClockBitMask) bus.SetCLK_CONF_RST_CORE(0) // synchronize core register bus.SetID_REG_UPDATE(0) // enable RTC clock esp.RTC_CNTL.SetCLK_CONF_DIG_CLK8M_EN(1) // wait for Core Clock to ready for configuration for bus.GetID_REG_UPDATE() > 0 { riscv.Asm("nop") } } func (uart *UART) SetBaudRate(baudRate uint32) { // based on esp-idf max_div := uint32((1 << 12) - 1) sclk_div := (pplClockFreq + (max_div * baudRate) - 1) / (max_div * baudRate) clk_div := (pplClockFreq << 4) / (baudRate * sclk_div) uart.Bus.SetCLKDIV(clk_div >> 4) uart.Bus.SetCLKDIV_FRAG(clk_div & 0xf) uart.Bus.SetCLK_CONF_SCLK_DIV_NUM(sclk_div - 1) } func (uart *UART) setupPins(config UARTConfig, regs registerSet) { config.RX.Configure(PinConfig{Mode: PinInputPullup}) config.TX.Configure(PinConfig{Mode: PinInputPullup}) // link TX with GPIO signal X (technical reference manual 5.10) (this is not interrupt signal!) config.TX.outFunc().Set(regs.gpioMatrixSignal) // link RX with GPIO signal X and route signals via GPIO matrix (GPIO_SIGn_IN_SEL 0x40) inFunc(regs.gpioMatrixSignal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.RX)) } func (uart *UART) configureInterrupt(intrMapReg *volatile.Register32) { // Disable all UART interrupts // Disable all UART interrupts uart.Bus.INT_ENA.ClearBits(0x0ffff) intrMapReg.Set(7) onceUart.Do(func() { _ = interrupt.New(7, func(i interrupt.Interrupt) { UART0.serveInterrupt(0) UART1.serveInterrupt(1) }).Enable() }) } func (uart *UART) serveInterrupt(num int) { // get interrupt status interrutFlag := uart.Bus.INT_ST.Get() if (interrutFlag & uartInterrupts) == 0 { return } // block UART interrupts while processing uart.Bus.INT_ENA.ClearBits(uartInterrupts) if interrutFlag&esp.UART_INT_ENA_RXFIFO_FULL_INT_ENA > 0 { for uart.Bus.GetSTATUS_RXFIFO_CNT() > 0 { b := uart.Bus.GetFIFO_RXFIFO_RD_BYTE() if !uart.Buffer.Put(byte(b & 0xff)) { uart.DataOverflowDetected = true } } } if interrutFlag&esp.UART_INT_ENA_PARITY_ERR_INT_ENA > 0 { uart.ParityErrorDetected = true } if 0 != interrutFlag&esp.UART_INT_ENA_FRM_ERR_INT_ENA { uart.DataErrorDetected = true } if 0 != interrutFlag&esp.UART_INT_ENA_RXFIFO_OVF_INT_ENA { uart.DataOverflowDetected = true } if 0 != interrutFlag&esp.UART_INT_ENA_GLITCH_DET_INT_ENA { uart.DataErrorDetected = true } // Clear the UART interrupt status uart.Bus.INT_CLR.SetBits(interrutFlag) uart.Bus.INT_CLR.ClearBits(interrutFlag) // Enable interrupts uart.Bus.INT_ENA.Set(uartInterrupts) } const uart_empty_thresh_default = 10 func (uart *UART) enableTransmitter() { uart.Bus.SetCONF0_TXFIFO_RST(1) uart.Bus.SetCONF0_TXFIFO_RST(0) // TXINFO empty threshold is when txfifo_empty_int interrupt produced after the amount of data in Tx-FIFO is less than this register value. uart.Bus.SetCONF1_TXFIFO_EMPTY_THRHD(uart_empty_thresh_default) // we are not using interrupt on TX since write we are waiting for FIFO to have space. // uart.Bus.INT_ENA.SetBits(esp.UART_INT_ENA_TXFIFO_EMPTY_INT_ENA) } func (uart *UART) enableReceiver() { uart.Bus.SetCONF0_RXFIFO_RST(1) uart.Bus.SetCONF0_RXFIFO_RST(0) // using value 1 so that we can start populate ring buffer with data as we get it uart.Bus.SetCONF1_RXFIFO_FULL_THRHD(1) // enable interrupts for: uart.Bus.SetINT_ENA_RXFIFO_FULL_INT_ENA(1) uart.Bus.SetINT_ENA_FRM_ERR_INT_ENA(1) uart.Bus.SetINT_ENA_PARITY_ERR_INT_ENA(1) uart.Bus.SetINT_ENA_GLITCH_DET_INT_ENA(1) uart.Bus.SetINT_ENA_RXFIFO_OVF_INT_ENA(1) } func (uart *UART) writeByte(b byte) error { for (uart.Bus.STATUS.Get()&esp.UART_STATUS_TXFIFO_CNT_Msk)>>esp.UART_STATUS_TXFIFO_CNT_Pos >= 128 { // Read UART_TXFIFO_CNT from the status register, which indicates how // many bytes there are in the transmit buffer. Wait until there are // less than 128 bytes in this buffer (the default buffer size). } uart.Bus.FIFO.Set(uint32(b)) return nil } func (uart *UART) flush() {} type Serialer interface { WriteByte(c byte) error Write(data []byte) (n int, err error) Configure(config UARTConfig) error Buffered() int ReadByte() (byte, error) DTR() bool RTS() bool } func initUSB() { // nothing to do here } // USB Serial/JTAG Controller // See esp32-c3_technical_reference_manual_en.pdf // pg. 736 type USB_DEVICE struct { Bus *esp.USB_DEVICE_Type } var ( _USBCDC = &USB_DEVICE{ Bus: esp.USB_DEVICE, } USBCDC Serialer = _USBCDC ) var ( errUSBWrongSize = errors.New("USB: invalid write size") errUSBCouldNotWriteAllData = errors.New("USB: could not write all data") errUSBBufferEmpty = errors.New("USB: read buffer empty") ) func (usbdev *USB_DEVICE) Configure(config UARTConfig) error { return nil } func (usbdev *USB_DEVICE) WriteByte(c byte) error { if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { return errUSBCouldNotWriteAllData } usbdev.Bus.SetEP1_RDWR_BYTE(uint32(c)) usbdev.flush() return nil } func (usbdev *USB_DEVICE) Write(data []byte) (n int, err error) { if len(data) == 0 || len(data) > 64 { return 0, errUSBWrongSize } for i, c := range data { if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { if i > 0 { usbdev.flush() } return i, errUSBCouldNotWriteAllData } usbdev.Bus.SetEP1_RDWR_BYTE(uint32(c)) } usbdev.flush() return len(data), nil } func (usbdev *USB_DEVICE) Buffered() int { return int(usbdev.Bus.GetEP1_CONF_SERIAL_OUT_EP_DATA_AVAIL()) } func (usbdev *USB_DEVICE) ReadByte() (byte, error) { if usbdev.Bus.GetEP1_CONF_SERIAL_OUT_EP_DATA_AVAIL() != 0 { return byte(usbdev.Bus.GetEP1_RDWR_BYTE()), nil } return 0, nil } func (usbdev *USB_DEVICE) DTR() bool { return false } func (usbdev *USB_DEVICE) RTS() bool { return false } func (usbdev *USB_DEVICE) flush() { usbdev.Bus.SetEP1_CONF_WR_DONE(1) for usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { } } // GetRNG returns 32-bit random numbers using the ESP32-C3 true random number generator, // Random numbers are generated based on the thermal noise in the system and the // asynchronous clock mismatch. // For maximum entropy also make sure that the SAR_ADC is enabled. // See esp32-c3_technical_reference_manual_en.pdf p.524 func GetRNG() (ret uint32, err error) { // ensure ADC clock is initialized initADCClock() // ensure fast RTC clock is enabled if esp.RTC_CNTL.GetCLK_CONF_DIG_CLK8M_EN() == 0 { esp.RTC_CNTL.SetCLK_CONF_DIG_CLK8M_EN(1) } return esp.APB_CTRL.GetRND_DATA(), nil } func initADCClock() { if esp.APB_SARADC.GetCLKM_CONF_CLK_EN() == 1 { return } // only support ADC_CTRL_CLK set to 1 esp.APB_SARADC.SetCLKM_CONF_CLK_SEL(1) esp.APB_SARADC.SetCTRL_SARADC_SAR_CLK_GATED(1) esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_NUM(15) esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_B(1) esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_A(0) esp.APB_SARADC.SetCTRL_SARADC_SAR_CLK_DIV(1) esp.APB_SARADC.SetCLKM_CONF_CLK_EN(1) } ================================================ FILE: src/machine/machine_esp32c3_i2c.go ================================================ //go:build esp32c3 && !m5stamp_c3 package machine import ( "device/esp" "runtime/volatile" "unsafe" ) var ( I2C0 = &I2C{} ) type I2C struct{} // I2CConfig is used to store config info for I2C. type I2CConfig struct { Frequency uint32 // in Hz SCL Pin SDA Pin } const ( clkXTAL = 0 clkFOSC = 1 clkXTALFrequency = uint32(40e6) clkFOSCFrequency = uint32(17.5e6) i2cClkSourceFrequency = clkXTALFrequency i2cClkSource = clkXTAL ) func (i2c *I2C) Configure(config I2CConfig) error { if config.Frequency == 0 { config.Frequency = 400 * KHz } if config.SCL == 0 { config.SCL = SCL_PIN } if config.SDA == 0 { config.SDA = SDA_PIN } i2c.initClock(config) i2c.initNoiseFilter() i2c.initPins(config) i2c.initFrequency(config) i2c.startMaster() return nil } //go:inline func (i2c *I2C) initClock(config I2CConfig) { // reset I2C clock esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(1) esp.SYSTEM.SetPERIP_CLK_EN0_I2C_EXT0_CLK_EN(1) esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(0) // disable interrupts esp.I2C0.INT_ENA.ClearBits(0x3fff) esp.I2C0.INT_CLR.ClearBits(0x3fff) esp.I2C0.SetCLK_CONF_SCLK_SEL(i2cClkSource) esp.I2C0.SetCLK_CONF_SCLK_ACTIVE(1) esp.I2C0.SetCLK_CONF_SCLK_DIV_NUM(i2cClkSourceFrequency / (config.Frequency * 1024)) esp.I2C0.SetCTR_CLK_EN(1) } //go:inline func (i2c *I2C) initNoiseFilter() { esp.I2C0.FILTER_CFG.Set(0x377) } //go:inline func (i2c *I2C) initPins(config I2CConfig) { var muxConfig uint32 const function = 1 // function 1 is just GPIO // SDA muxConfig = function << esp.IO_MUX_GPIO_MCU_SEL_Pos // Make this pin an input pin (always). muxConfig |= esp.IO_MUX_GPIO_FUN_IE // Set drive strength: 0 is lowest, 3 is highest. muxConfig |= 1 << esp.IO_MUX_GPIO_FUN_DRV_Pos config.SDA.mux().Set(muxConfig) config.SDA.outFunc().Set(54) inFunc(54).Set(uint32(esp.GPIO_FUNC_IN_SEL_CFG_SEL | config.SDA)) config.SDA.Set(true) // Configure the pad with the given IO mux configuration. config.SDA.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER) esp.GPIO.ENABLE.SetBits(1 << int(config.SDA)) esp.I2C0.SetCTR_SDA_FORCE_OUT(1) // SCL muxConfig = function << esp.IO_MUX_GPIO_MCU_SEL_Pos // Make this pin an input pin (always). muxConfig |= esp.IO_MUX_GPIO_FUN_IE // Set drive strength: 0 is lowest, 3 is highest. muxConfig |= 1 << esp.IO_MUX_GPIO_FUN_DRV_Pos config.SCL.mux().Set(muxConfig) config.SCL.outFunc().Set(53) inFunc(53).Set(uint32(config.SCL)) config.SCL.Set(true) // Configure the pad with the given IO mux configuration. config.SCL.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER) esp.GPIO.ENABLE.SetBits(1 << int(config.SCL)) esp.I2C0.SetCTR_SCL_FORCE_OUT(1) } //go:inline func (i2c *I2C) initFrequency(config I2CConfig) { clkmDiv := i2cClkSourceFrequency/(config.Frequency*1024) + 1 sclkFreq := i2cClkSourceFrequency / clkmDiv halfCycle := sclkFreq / config.Frequency / 2 //SCL sclLow := halfCycle sclWaitHigh := uint32(0) if config.Frequency > 50000 { sclWaitHigh = halfCycle / 8 // compensate the time when freq > 50K } sclHigh := halfCycle - sclWaitHigh // SDA sdaHold := halfCycle / 4 sda_sample := halfCycle / 2 setup := halfCycle hold := halfCycle esp.I2C0.SetSCL_LOW_PERIOD(sclLow - 1) esp.I2C0.SetSCL_HIGH_PERIOD(sclHigh) esp.I2C0.SetSCL_HIGH_PERIOD_SCL_WAIT_HIGH_PERIOD(25) esp.I2C0.SetSCL_RSTART_SETUP_TIME(setup) esp.I2C0.SetSCL_STOP_SETUP_TIME(setup) esp.I2C0.SetSCL_START_HOLD_TIME(hold - 1) esp.I2C0.SetSCL_STOP_HOLD_TIME(hold - 1) esp.I2C0.SetSDA_SAMPLE_TIME(sda_sample) esp.I2C0.SetSDA_HOLD_TIME(sdaHold) } //go:inline func (i2c *I2C) startMaster() { // FIFO mode for data esp.I2C0.SetFIFO_CONF_NONFIFO_EN(0) // Reset TX & RX buffers esp.I2C0.SetFIFO_CONF_RX_FIFO_RST(1) esp.I2C0.SetFIFO_CONF_RX_FIFO_RST(0) esp.I2C0.SetFIFO_CONF_TX_FIFO_RST(1) esp.I2C0.SetFIFO_CONF_TX_FIFO_RST(0) // set timeout value esp.I2C0.TO.Set(0x10) // enable master mode esp.I2C0.CTR.Set(0x113) esp.I2C0.SetCTR_CONF_UPGATE(1) resetMaster() } //go:inline func resetMaster() { // reset FSM esp.I2C0.SetCTR_FSM_RST(1) // clear the bus esp.I2C0.SetSCL_SP_CONF_SCL_RST_SLV_NUM(9) esp.I2C0.SetSCL_SP_CONF_SCL_RST_SLV_EN(1) esp.I2C0.SetSCL_STRETCH_CONF_SLAVE_SCL_STRETCH_EN(1) esp.I2C0.SetCTR_CONF_UPGATE(1) esp.I2C0.FILTER_CFG.Set(0x377) // wait for SCL_RST_SLV_EN for esp.I2C0.GetSCL_SP_CONF_SCL_RST_SLV_EN() != 0 { } esp.I2C0.SetSCL_SP_CONF_SCL_RST_SLV_NUM(0) } type i2cCommandType = uint32 type i2cAck = uint32 const ( i2cCMD_RSTART i2cCommandType = 6 << 11 i2cCMD_WRITE i2cCommandType = 1<<11 | 1<<8 // WRITE + ack_check_en i2cCMD_READ i2cCommandType = 3<<11 | 1<<8 // READ + ack_check_en i2cCMD_READLAST i2cCommandType = 3<<11 | 5<<8 // READ + ack_check_en + NACK i2cCMD_STOP i2cCommandType = 2 << 11 i2cCMD_END i2cCommandType = 4 << 11 ) type i2cCommand struct { cmd i2cCommandType data []byte head int } //go:linkname nanotime runtime.nanotime func nanotime() int64 func (i2c *I2C) transmit(addr uint16, cmd []i2cCommand, timeoutMS int) error { const intMask = esp.I2C_INT_STATUS_END_DETECT_INT_ST_Msk | esp.I2C_INT_STATUS_TRANS_COMPLETE_INT_ST_Msk | esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk | esp.I2C_INT_STATUS_NACK_INT_ST_Msk esp.I2C0.INT_CLR.SetBits(intMask) esp.I2C0.INT_ENA.SetBits(intMask) esp.I2C0.SetCTR_CONF_UPGATE(1) defer func() { esp.I2C0.INT_CLR.SetBits(intMask) esp.I2C0.INT_ENA.ClearBits(intMask) }() timeoutNS := int64(timeoutMS) * 1000000 needAddress := true needRestart := false readLast := false var readTo []byte for cmdIdx, reg := 0, &esp.I2C0.COMD0; cmdIdx < len(cmd); { c := &cmd[cmdIdx] switch c.cmd { case i2cCMD_RSTART: reg.Set(i2cCMD_RSTART) reg = nextAddress(reg) cmdIdx++ case i2cCMD_WRITE: count := 32 if needAddress { needAddress = false esp.I2C0.SetDATA_FIFO_RDATA((uint32(addr) & 0x7f) << 1) count-- esp.I2C0.SLAVE_ADDR.Set(uint32(addr)) esp.I2C0.SetCTR_CONF_UPGATE(1) } for ; count > 0 && c.head < len(c.data); count, c.head = count-1, c.head+1 { esp.I2C0.SetDATA_FIFO_RDATA(uint32(c.data[c.head])) } reg.Set(i2cCMD_WRITE | uint32(32-count)) reg = nextAddress(reg) if c.head < len(c.data) { reg.Set(i2cCMD_END) reg = nil } else { cmdIdx++ } needRestart = true case i2cCMD_READ: if needAddress { needAddress = false esp.I2C0.SetDATA_FIFO_RDATA((uint32(addr)&0x7f)<<1 | 1) esp.I2C0.SLAVE_ADDR.Set(uint32(addr)) reg.Set(i2cCMD_WRITE | 1) reg = nextAddress(reg) } if needRestart { // We need to send RESTART again after i2cCMD_WRITE. reg.Set(i2cCMD_RSTART) reg = nextAddress(reg) reg.Set(i2cCMD_WRITE | 1) reg = nextAddress(reg) esp.I2C0.SetDATA_FIFO_RDATA((uint32(addr)&0x7f)<<1 | 1) needRestart = false } count := 32 bytes := len(c.data) - c.head // Only last byte in sequence must be sent with ACK set to 1 to indicate end of data. split := bytes <= count if split { bytes-- } if bytes > 32 { bytes = 32 } reg.Set(i2cCMD_READ | uint32(bytes)) reg = nextAddress(reg) if split { readLast = true reg.Set(i2cCMD_READLAST | 1) reg = nextAddress(reg) readTo = c.data[c.head : c.head+bytes+1] // read bytes + 1 last byte cmdIdx++ } else { reg.Set(i2cCMD_END) readTo = c.data[c.head : c.head+bytes] reg = nil } case i2cCMD_STOP: reg.Set(i2cCMD_STOP) reg = nil cmdIdx++ } if reg == nil { // transmit now esp.I2C0.SetCTR_CONF_UPGATE(1) esp.I2C0.SetCTR_TRANS_START(1) end := nanotime() + timeoutNS var mask uint32 for mask = esp.I2C0.INT_STATUS.Get(); mask&intMask == 0; mask = esp.I2C0.INT_STATUS.Get() { if nanotime() > end { if readTo != nil { return errI2CReadTimeout } return errI2CWriteTimeout } } switch { case mask&esp.I2C_INT_STATUS_NACK_INT_ST_Msk != 0 && !readLast: return errI2CAckExpected case mask&esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk != 0: if readTo != nil { return errI2CReadTimeout } return errI2CWriteTimeout } esp.I2C0.INT_CLR.SetBits(intMask) for i := 0; i < len(readTo); i++ { readTo[i] = byte(esp.I2C0.GetDATA_FIFO_RDATA() & 0xff) c.head++ } readTo = nil reg = &esp.I2C0.COMD0 } } return nil } // Tx does a single I2C transaction at the specified address. // It clocks out the given address, writes the bytes in w, reads back len(r) // bytes and stores them in r, and generates a stop condition on the bus. func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) { // timeout in microseconds. const timeout = 40 // 40ms is a reasonable time for a real-time system. cmd := make([]i2cCommand, 0, 8) cmd = append(cmd, i2cCommand{cmd: i2cCMD_RSTART}) if len(w) > 0 { cmd = append(cmd, i2cCommand{cmd: i2cCMD_WRITE, data: w}) } if len(r) > 0 { cmd = append(cmd, i2cCommand{cmd: i2cCMD_READ, data: r}) } cmd = append(cmd, i2cCommand{cmd: i2cCMD_STOP}) return i2c.transmit(addr, cmd, timeout) } func (i2c *I2C) SetBaudRate(br uint32) error { return nil } func nextAddress(reg *volatile.Register32) *volatile.Register32 { return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(reg), 4)) } ================================================ FILE: src/machine/machine_esp32c3_spi.go ================================================ //go:build esp32c3 package machine // On the C3 variant, SPI2 is a general purpose SPI controller. SPI0 and SPI1 // are used internally to access the ESP32-C3’s attached flash memory. Due to // different registers between SPI2 and the other SPI ports, this driver // currently supports only the the general purpose FSPI SPI2 controller. // https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/spi_master.html import ( "device/esp" "errors" "runtime/volatile" "unsafe" ) const ( SPI_MODE0 = uint8(0) SPI_MODE1 = uint8(1) SPI_MODE2 = uint8(2) SPI_MODE3 = uint8(3) FSPICLK_IN_IDX = uint32(63) FSPICLK_OUT_IDX = uint32(63) FSPIQ_IN_IDX = uint32(64) FSPIQ_OUT_IDX = uint32(64) FSPID_IN_IDX = uint32(65) FSPID_OUT_IDX = uint32(65) FSPIHD_IN_IDX = uint32(66) FSPIHD_OUT_IDX = uint32(66) FSPIWP_IN_IDX = uint32(67) FSPIWP_OUT_IDX = uint32(67) FSPICS0_IN_IDX = uint32(68) FSPICS0_OUT_IDX = uint32(68) FSPICS1_OUT_IDX = uint32(69) FSPICS2_OUT_IDX = uint32(70) FSPICS3_OUT_IDX = uint32(71) FSPICS4_OUT_IDX = uint32(72) FSPICS5_OUT_IDX = uint32(73) ) var ( ErrInvalidSPIBus = errors.New("machine: SPI bus is invalid") ErrInvalidSPIMode = errors.New("machine: SPI mode is invalid") ) // Serial Peripheral Interface on the ESP32-C3. type SPI struct { Bus *esp.SPI2_Type } var ( // SPI0 and SPI1 are reserved for use by the caching system etc. SPI2 = &SPI{esp.SPI2} ) // SPIConfig is used to store config info for SPI. type SPIConfig struct { Frequency uint32 SCK Pin // Serial Clock SDO Pin // Serial Data Out (MOSI) SDI Pin // Serial Data In (MISO) CS Pin // Chip Select (optional) LSBFirst bool // MSB is default Mode uint8 // SPI_MODE0 is default } // Compute the SPI bus frequency from the CPU frequency. func freqToClockDiv(hz uint32) uint32 { fcpu := CPUFrequency() if hz >= fcpu { // maximum frequency return 1 << 31 } if hz < (fcpu / (16 * 64)) { // minimum frequency return 15<<18 | 63<<12 | 31<<6 | 63 // pre=15, n=63 } // iterate looking for an exact match // or iterate all 16 prescaler options // looking for the smallest error var bestPre, bestN, bestErr uint32 bestN = 1 bestErr = 0xffffffff q := uint32(float32(pplClockFreq)/float32(hz) + float32(0.5)) for p := uint32(0); p < 16; p++ { n := q/(p+1) - 1 if n < 1 { // prescaler became too large, stop enum break } if n > 63 { // prescaler too small, skip to next continue } freq := fcpu / ((p + 1) * (n + 1)) if freq == hz { // exact match return p<<18 | n<<12 | (n/2)<<6 | n } var err uint32 if freq < hz { err = hz - freq } else { err = freq - hz } if err < bestErr { bestErr = err bestPre = p bestN = n } } return bestPre<<18 | bestN<<12 | (bestN/2)<<6 | bestN } // Configure and make the SPI peripheral ready to use. func (spi *SPI) Configure(config SPIConfig) error { // right now this is only setup to work for the esp32c3 spi2 bus if spi.Bus != esp.SPI2 { return ErrInvalidSPIBus } // periph module reset esp.SYSTEM.SetPERIP_RST_EN0_SPI2_RST(1) esp.SYSTEM.SetPERIP_RST_EN0_SPI2_RST(0) // periph module enable esp.SYSTEM.SetPERIP_CLK_EN0_SPI2_CLK_EN(1) esp.SYSTEM.SetPERIP_RST_EN0_SPI2_RST(0) // init the spi2 bus spi.Bus.SLAVE.Set(0) spi.Bus.MISC.Set(0) spi.Bus.USER.Set(0) spi.Bus.USER1.Set(0) spi.Bus.CTRL.Set(0) spi.Bus.CLK_GATE.Set(0) spi.Bus.DMA_CONF.Set(0) spi.Bus.SetDMA_CONF_RX_AFIFO_RST(1) spi.Bus.SetDMA_CONF_BUF_AFIFO_RST(1) spi.Bus.CLOCK.Set(0) // clear data buf spi.Bus.SetW0(0) spi.Bus.SetW1(0) spi.Bus.SetW2(0) spi.Bus.SetW3(0) spi.Bus.SetW4(0) spi.Bus.SetW5(0) spi.Bus.SetW6(0) spi.Bus.SetW7(0) spi.Bus.SetW8(0) spi.Bus.SetW9(0) spi.Bus.SetW10(0) spi.Bus.SetW11(0) spi.Bus.SetW12(0) spi.Bus.SetW13(0) spi.Bus.SetW14(0) spi.Bus.SetW15(0) // start the spi2 bus spi.Bus.SetCLK_GATE_CLK_EN(1) spi.Bus.SetCLK_GATE_MST_CLK_SEL(1) spi.Bus.SetCLK_GATE_MST_CLK_ACTIVE(1) spi.Bus.SetDMA_CONF_SLV_TX_SEG_TRANS_CLR_EN(1) spi.Bus.SetDMA_CONF_SLV_RX_SEG_TRANS_CLR_EN(1) spi.Bus.SetDMA_CONF_DMA_SLV_SEG_TRANS_EN(0) spi.Bus.SetUSER_USR_MOSI(1) spi.Bus.SetUSER_USR_MISO(1) spi.Bus.SetUSER_DOUTDIN(1) // set spi2 data mode switch config.Mode { case SPI_MODE0: spi.Bus.SetMISC_CK_IDLE_EDGE(0) spi.Bus.SetUSER_CK_OUT_EDGE(0) case SPI_MODE1: spi.Bus.SetMISC_CK_IDLE_EDGE(0) spi.Bus.SetUSER_CK_OUT_EDGE(1) case SPI_MODE2: spi.Bus.SetMISC_CK_IDLE_EDGE(1) spi.Bus.SetUSER_CK_OUT_EDGE(1) case SPI_MODE3: spi.Bus.SetMISC_CK_IDLE_EDGE(1) spi.Bus.SetUSER_CK_OUT_EDGE(0) default: return ErrInvalidSPIMode } // set spi2 bit order if config.LSBFirst { spi.Bus.SetCTRL_WR_BIT_ORDER(1) // LSB first spi.Bus.SetCTRL_RD_BIT_ORDER(1) } else { spi.Bus.SetCTRL_WR_BIT_ORDER(0) // MSB first spi.Bus.SetCTRL_RD_BIT_ORDER(0) } // configure SPI bus clock spi.Bus.CLOCK.Set(freqToClockDiv(config.Frequency)) // configure esp32c3 gpio pin matrix config.SDI.Configure(PinConfig{Mode: PinInput}) inFunc(FSPIQ_IN_IDX).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.SDI)) config.SDO.Configure(PinConfig{Mode: PinOutput}) config.SDO.outFunc().Set(FSPID_OUT_IDX) config.SCK.Configure(PinConfig{Mode: PinOutput}) config.SCK.outFunc().Set(FSPICLK_OUT_IDX) if config.CS != NoPin { config.CS.Configure(PinConfig{Mode: PinOutput}) config.CS.outFunc().Set(FSPICS0_OUT_IDX) } return nil } // Transfer writes/reads a single byte using the SPI interface. If you need to // transfer larger amounts of data, Tx will be faster. func (spi *SPI) Transfer(w byte) (byte, error) { spi.Bus.SetMS_DLEN_MS_DATA_BITLEN(7) spi.Bus.SetW0(uint32(w)) // Send/receive byte. spi.Bus.SetCMD_UPDATE(1) for spi.Bus.GetCMD_UPDATE() != 0 { } spi.Bus.SetCMD_USR(1) for spi.Bus.GetCMD_USR() != 0 { } // The received byte is stored in W0. return byte(spi.Bus.GetW0()), nil } // Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read // interface, there must always be the same number of bytes written as bytes read. // This is accomplished by sending zero bits if r is bigger than w or discarding // the incoming data if w is bigger than r. func (spi *SPI) Tx(w, r []byte) error { toTransfer := len(w) if len(r) > toTransfer { toTransfer = len(r) } for toTransfer > 0 { // Chunk 64 bytes at a time. chunkSize := toTransfer if chunkSize > 64 { chunkSize = 64 } // Fill tx buffer. transferWords := (*[16]volatile.Register32)(unsafe.Pointer(uintptr(unsafe.Pointer(&spi.Bus.W0)))) if len(w) >= 64 { // We can fill the entire 64-byte transfer buffer with data. // This loop is slightly faster than the loop below. for i := 0; i < 16; i++ { word := uint32(w[i*4]) | uint32(w[i*4+1])<<8 | uint32(w[i*4+2])<<16 | uint32(w[i*4+3])<<24 transferWords[i].Set(word) } } else { // We can't fill the entire transfer buffer, so we need to be a bit // more careful. // Note that parts of the transfer buffer that aren't used still // need to be set to zero, otherwise we might be transferring // garbage from a previous transmission if w is smaller than r. for i := 0; i < 16; i++ { var word uint32 if i*4+3 < len(w) { word |= uint32(w[i*4+3]) << 24 } if i*4+2 < len(w) { word |= uint32(w[i*4+2]) << 16 } if i*4+1 < len(w) { word |= uint32(w[i*4+1]) << 8 } if i*4+0 < len(w) { word |= uint32(w[i*4+0]) << 0 } transferWords[i].Set(word) } } // Do the transfer. spi.Bus.SetMS_DLEN_MS_DATA_BITLEN(uint32(chunkSize)*8 - 1) spi.Bus.SetCMD_UPDATE(1) for spi.Bus.GetCMD_UPDATE() != 0 { } spi.Bus.SetCMD_USR(1) for spi.Bus.GetCMD_USR() != 0 { } // Read rx buffer. rxSize := 64 if rxSize > len(r) { rxSize = len(r) } for i := 0; i < rxSize; i++ { r[i] = byte(transferWords[i/4].Get() >> ((i % 4) * 8)) } // Cut off some part of the output buffer so the next iteration we will // only send the remaining bytes. if len(w) < chunkSize { w = nil } else { w = w[chunkSize:] } if len(r) < chunkSize { r = nil } else { r = r[chunkSize:] } toTransfer -= chunkSize } return nil } ================================================ FILE: src/machine/machine_esp32s3.go ================================================ //go:build esp32s3 package machine import ( "device/esp" "errors" "runtime/volatile" "unsafe" ) const deviceName = esp.Device const xtalClock = 40_000000 // 40MHz const apbClock = 80_000000 // 80MHz const cryptoPWMClock = 160_000000 // 160MHz // GetCPUFrequency returns the current CPU frequency of the chip. func GetCPUFrequency() (uint32, error) { switch esp.SYSTEM.GetSYSCLK_CONF_SOC_CLK_SEL() { case 0: return xtalClock / (esp.SYSTEM.GetSYSCLK_CONF_PRE_DIV_CNT() + 1), nil case 1: switch esp.SYSTEM.GetCPU_PER_CONF_CPUPERIOD_SEL() { case 0: return 80e6, nil case 1: return 160e6, nil case 2: // If esp.SYSTEM.GetCPU_PER_CONF_PLL_FREQ_SEL() == 1, this is undefined return 240e6, nil } case 2: //RC Fast Clock return (175e5) / (esp.SYSTEM.GetSYSCLK_CONF_PRE_DIV_CNT() + 1), nil } return 0, errors.New("machine: Unable to determine current cpu frequency") } // SetCPUFrequency sets the frequency of the CPU to one of several targets func SetCPUFrequency(frequency uint32) error { // Always assume we are on PLL. Lower frequencies can be set with a different // clock source, but this will change the behavior of APB clock and Crypto PWM // clock //esp.SYSTEM.SetSYSCLK_CONF_SOC_CLK_SEL(1) switch frequency { case 80_000000: esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(0) esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(0) // Reduce PLL freq when possible return nil case 160_000000: esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(1) esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(0) return nil case 240_000000: esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(1) // Increase PLL freq when needed esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(2) return nil } return errors.New("machine: Unsupported CPU frequency selected. Supported: 80, 160, 240 MHz") } var ( ErrInvalidSPIBus = errors.New("machine: invalid SPI bus") ) const ( PinOutput PinMode = iota PinInput PinInputPullup PinInputPulldown ) // Hardware pin numbers const ( GPIO0 Pin = 0 GPIO1 Pin = 1 GPIO2 Pin = 2 GPIO3 Pin = 3 GPIO4 Pin = 4 GPIO5 Pin = 5 GPIO6 Pin = 6 GPIO7 Pin = 7 GPIO8 Pin = 8 GPIO9 Pin = 9 GPIO10 Pin = 10 GPIO11 Pin = 11 GPIO12 Pin = 12 GPIO13 Pin = 13 GPIO14 Pin = 14 GPIO15 Pin = 15 GPIO16 Pin = 16 GPIO17 Pin = 17 GPIO18 Pin = 18 GPIO19 Pin = 19 GPIO20 Pin = 20 GPIO21 Pin = 21 GPIO26 Pin = 26 GPIO27 Pin = 27 GPIO28 Pin = 28 GPIO29 Pin = 29 GPIO30 Pin = 30 GPIO31 Pin = 31 GPIO32 Pin = 32 GPIO33 Pin = 33 GPIO34 Pin = 34 GPIO35 Pin = 35 GPIO36 Pin = 36 GPIO37 Pin = 37 GPIO38 Pin = 38 GPIO39 Pin = 39 GPIO40 Pin = 40 GPIO41 Pin = 41 GPIO42 Pin = 42 GPIO43 Pin = 43 GPIO44 Pin = 44 GPIO45 Pin = 45 GPIO46 Pin = 46 GPIO47 Pin = 47 GPIO48 Pin = 48 ) // Configure this pin with the given configuration. func (p Pin) Configure(config PinConfig) { // Output function 256 is a special value reserved for use as a regular GPIO // pin. Peripherals (SPI etc) can set a custom output function by calling // lowercase configure() instead with a signal name. p.configure(config, 256) } // configure is the same as Configure, but allows for setting a specific input // or output signal. // Signals are always routed through the GPIO matrix for simplicity. Output // signals are configured in FUNCx_OUT_SEL_CFG which selects a particular signal // to output on a given pin. Input signals are configured in FUNCy_IN_SEL_CFG, // which sets the pin to use for a particular input signal. func (p Pin) configure(config PinConfig, signal uint32) { if p == NoPin { // This simplifies pin configuration in peripherals such as SPI. return } ioConfig := uint32(0) // MCU_SEL: Function 1 is always GPIO ioConfig |= (1 << esp.IO_MUX_GPIO_MCU_SEL_Pos) // FUN_IE: Make this pin an input pin (always set for GPIO operation) ioConfig |= esp.IO_MUX_GPIO_FUN_IE // DRV: Set drive strength to 20 mA as a default. Pins 17 and 18 are special var drive uint32 if p == GPIO17 || p == GPIO18 { drive = 1 // 20 mA } else { drive = 2 // 20 mA } ioConfig |= (drive << esp.IO_MUX_GPIO_FUN_DRV_Pos) // WPU/WPD: Select pull mode. if config.Mode == PinInputPullup { ioConfig |= esp.IO_MUX_GPIO_FUN_WPU } else if config.Mode == PinInputPulldown { ioConfig |= esp.IO_MUX_GPIO_FUN_WPD } // Set configuration ioRegister := p.ioMuxReg() ioRegister.Set(ioConfig) switch config.Mode { case PinOutput: // Set the 'output enable' bit. if p < 32 { esp.GPIO.ENABLE_W1TS.Set(1 << p) } else { esp.GPIO.ENABLE1_W1TS.Set(1 << (p - 32)) } // Set the signal to read the output value from. It can be a peripheral // output signal, or the special value 256 which indicates regular GPIO // usage. p.outFunc().Set(signal) case PinInput, PinInputPullup, PinInputPulldown: // Clear the 'output enable' bit. if p < 32 { esp.GPIO.ENABLE_W1TC.Set(1 << p) } else { esp.GPIO.ENABLE1_W1TC.Set(1 << (p - 32)) } if signal != 256 { // Signal is a peripheral function (not a simple GPIO). Connect this // signal to the pin. // Note that outFunc and inFunc work in the opposite direction. // outFunc configures a pin to use a given output signal, while // inFunc specifies a pin to use to read the signal from. inFunc(signal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(p)<>16)&0xff >= 128 { // Read UART_TXFIFO_CNT from the status register, which indicates how // many bytes there are in the transmit buffer. Wait until there are // less than 128 bytes in this buffer (the default buffer size). } uart.Bus.FIFO.Set(uint32(b)) return nil } func (uart *UART) flush() {} // TODO: SPI ================================================ FILE: src/machine/machine_esp8266.go ================================================ //go:build esp8266 package machine import ( "device/esp" "runtime/volatile" ) const deviceName = esp.Device func CPUFrequency() uint32 { return 80000000 // 80MHz } const ( PinOutput PinMode = iota PinInput ) // Hardware pin numbers const ( GPIO0 Pin = iota GPIO1 GPIO2 GPIO3 GPIO4 GPIO5 GPIO6 GPIO7 GPIO8 GPIO9 GPIO10 GPIO11 GPIO12 GPIO13 GPIO14 GPIO15 GPIO16 ) // Pins that are fixed by the chip. const ( UART_TX_PIN Pin = 1 UART_RX_PIN Pin = 3 ) // Pin functions are not trivial. The below array maps a pin number (GPIO // number) to the pad as used in the IO mux. // Tables with the mapping: // https://www.esp8266.com/wiki/doku.php?id=esp8266_gpio_pin_allocations#pin_functions // https://www.espressif.com/sites/default/files/documentation/ESP8266_Pin_List_0.xls var pinPadMapping = [...]uint8{ 12: 0, 13: 1, 14: 2, 15: 3, 3: 4, 1: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, 11: 11, 0: 12, 2: 13, 4: 14, 5: 15, } // getPad returns the pad number and the register to configure this pad. func (p Pin) getPad() (uint8, *volatile.Register32) { pad := pinPadMapping[p] var reg *volatile.Register32 switch pad { case 0: reg = &esp.IO_MUX.IO_MUX_MTDI case 1: reg = &esp.IO_MUX.IO_MUX_MTCK case 2: reg = &esp.IO_MUX.IO_MUX_MTMS case 3: reg = &esp.IO_MUX.IO_MUX_MTDO case 4: reg = &esp.IO_MUX.IO_MUX_U0RXD case 5: reg = &esp.IO_MUX.IO_MUX_U0TXD case 6: reg = &esp.IO_MUX.IO_MUX_SD_CLK case 7: reg = &esp.IO_MUX.IO_MUX_SD_DATA0 case 8: reg = &esp.IO_MUX.IO_MUX_SD_DATA1 case 9: reg = &esp.IO_MUX.IO_MUX_SD_DATA2 case 10: reg = &esp.IO_MUX.IO_MUX_SD_DATA3 case 11: reg = &esp.IO_MUX.IO_MUX_SD_CMD case 12: reg = &esp.IO_MUX.IO_MUX_GPIO0 case 13: reg = &esp.IO_MUX.IO_MUX_GPIO2 case 14: reg = &esp.IO_MUX.IO_MUX_GPIO4 case 15: reg = &esp.IO_MUX.IO_MUX_GPIO5 } return pad, reg } // Configure sets the given pin as output or input pin. func (p Pin) Configure(config PinConfig) { switch config.Mode { case PinInput, PinOutput: pad, reg := p.getPad() if pad >= 12 { // pin 0, 2, 4, 5 reg.Set(0 << 4) // function 0 at bit position 4 } else { reg.Set(3 << 4) // function 3 at bit position 4 } if config.Mode == PinOutput { esp.GPIO.GPIO_ENABLE_W1TS.Set(1 << p) } else { esp.GPIO.GPIO_ENABLE_W1TC.Set(1 << p) } } } // Get returns the current value of a GPIO pin when the pin is configured as an // input or as an output. func (p Pin) Get() bool { // See this document for details // https://www.espressif.com/sites/default/files/documentation/esp8266-technical_reference_en.pdf return esp.GPIO.GPIO_IN.Get()&(1<>16)&0xff >= 128 { // Wait until the TX buffer has room. } esp.UART0.UART_FIFO.Set(uint32(c)) return nil } func (uart *UART) flush() {} ================================================ FILE: src/machine/machine_fe310.go ================================================ //go:build fe310 package machine import ( "device/sifive" "runtime/interrupt" "unsafe" ) const deviceName = sifive.Device func CPUFrequency() uint32 { return 320000000 // 320MHz } const ( PinInput PinMode = iota PinOutput PinPWM PinSPI PinI2C = PinSPI ) // Configure this pin with the given configuration. func (p Pin) Configure(config PinConfig) { sifive.GPIO0.INPUT_EN.SetBits(1 << uint8(p)) switch config.Mode { case PinInput: sifive.GPIO0.OUTPUT_EN.ClearBits(1 << uint8(p)) case PinOutput: sifive.GPIO0.OUTPUT_EN.SetBits(1 << uint8(p)) case PinPWM: sifive.GPIO0.IOF_EN.SetBits(1 << uint8(p)) sifive.GPIO0.IOF_SEL.SetBits(1 << uint8(p)) case PinSPI: sifive.GPIO0.IOF_EN.SetBits(1 << uint8(p)) sifive.GPIO0.IOF_SEL.ClearBits(1 << uint8(p)) } } // Set the pin to high or low. func (p Pin) Set(high bool) { if high { sifive.GPIO0.PORT.SetBits(1 << uint8(p)) } else { sifive.GPIO0.PORT.ClearBits(1 << uint8(p)) } } // Get returns the current value of a GPIO pin when the pin is configured as an // input or as an output. func (p Pin) Get() bool { val := sifive.GPIO0.VALUE.Get() & (1 << uint8(p)) return (val > 0) } // Return the register and mask to enable a given GPIO pin. This can be used to // implement bit-banged drivers. // // Warning: only use this on an output pin! func (p Pin) PortMaskSet() (*uint32, uint32) { return (*uint32)(unsafe.Pointer(&sifive.GPIO0.PORT)), sifive.GPIO0.PORT.Get() | (1 << uint8(p)) } // Return the register and mask to disable a given GPIO pin. This can be used to // implement bit-banged drivers. // // Warning: only use this on an output pin! func (p Pin) PortMaskClear() (*uint32, uint32) { return (*uint32)(unsafe.Pointer(&sifive.GPIO0.PORT)), sifive.GPIO0.PORT.Get() &^ (1 << uint8(p)) } type UART struct { Bus *sifive.UART_Type Buffer *RingBuffer } var ( UART0 = &_UART0 _UART0 = UART{Bus: sifive.UART0, Buffer: NewRingBuffer()} ) func (uart *UART) Configure(config UARTConfig) { if config.BaudRate == 0 { config.BaudRate = 115200 } // The divisor is: // fbaud = fin / (div + 1) // Restating to get the divisor: // div = fin / fbaud - 1 // But we're using integers, so we should take care of rounding: // div = (fin + fbaud/2) / fbaud - 1 divisor := (CPUFrequency()+config.BaudRate/2)/config.BaudRate - 1 sifive.UART0.DIV.Set(divisor) sifive.UART0.TXCTRL.Set(sifive.UART_TXCTRL_ENABLE) sifive.UART0.RXCTRL.Set(sifive.UART_RXCTRL_ENABLE) sifive.UART0.IE.Set(sifive.UART_IE_RXWM) // enable the receive interrupt (only) intr := interrupt.New(sifive.IRQ_UART0, _UART0.handleInterrupt) intr.SetPriority(5) intr.Enable() } func (uart *UART) handleInterrupt(interrupt.Interrupt) { rxdata := uart.Bus.RXDATA.Get() c := byte(rxdata) if uint32(c) != rxdata { // The rxdata has other bits set than just the low 8 bits. This probably // means that the 'empty' flag is set, which indicates there is no data // to be read and the byte is garbage. Ignore this byte. return } uart.Receive(c) } func (uart *UART) writeByte(c byte) error { for sifive.UART0.TXDATA.Get()&sifive.UART_TXDATA_FULL != 0 { } sifive.UART0.TXDATA.Set(uint32(c)) return nil } func (uart *UART) flush() {} // SPI on the FE310. The normal SPI0 is actually a quad-SPI meant for flash, so it is best // to use SPI1 or SPI2 port for most applications. type SPI struct { Bus *sifive.QSPI_Type } // SPIConfig is used to store config info for SPI. type SPIConfig struct { Frequency uint32 SCK Pin SDO Pin SDI Pin LSBFirst bool Mode uint8 } // Configure is intended to setup the SPI interface. func (spi *SPI) Configure(config SPIConfig) error { // Use default pins if not set. if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { config.SCK = SPI0_SCK_PIN config.SDO = SPI0_SDO_PIN config.SDI = SPI0_SDI_PIN } // enable pins for SPI config.SCK.Configure(PinConfig{Mode: PinSPI}) config.SDO.Configure(PinConfig{Mode: PinSPI}) config.SDI.Configure(PinConfig{Mode: PinSPI}) // set default frequency if config.Frequency == 0 { config.Frequency = 4000000 // 4MHz } // div = (SPI_CFG(dev)->f_sys / (2 * frequency)) - 1; div := CPUFrequency()/(2*config.Frequency) - 1 spi.Bus.DIV.Set(div) // set mode switch config.Mode { case 0: spi.Bus.MODE.ClearBits(sifive.QSPI_MODE_PHASE) spi.Bus.MODE.ClearBits(sifive.QSPI_MODE_POLARITY) case 1: spi.Bus.MODE.SetBits(sifive.QSPI_MODE_PHASE) spi.Bus.MODE.ClearBits(sifive.QSPI_MODE_POLARITY) case 2: spi.Bus.MODE.ClearBits(sifive.QSPI_MODE_PHASE) spi.Bus.MODE.SetBits(sifive.QSPI_MODE_POLARITY) case 3: spi.Bus.MODE.SetBits(sifive.QSPI_MODE_PHASE | sifive.QSPI_MODE_POLARITY) default: // to mode 0 spi.Bus.MODE.ClearBits(sifive.QSPI_MODE_PHASE) spi.Bus.MODE.ClearBits(sifive.QSPI_MODE_POLARITY) } // frame length spi.Bus.FMT.SetBits(8 << sifive.QSPI_FMT_LENGTH_Pos) // Set single line operation, by clearing all bits spi.Bus.FMT.ClearBits(sifive.QSPI_FMT_PROTOCOL_Msk) // set bit transfer order if config.LSBFirst { spi.Bus.FMT.SetBits(sifive.QSPI_FMT_ENDIAN) } else { spi.Bus.FMT.ClearBits(sifive.QSPI_FMT_ENDIAN) } return nil } // Transfer writes/reads a single byte using the SPI interface. func (spi *SPI) Transfer(w byte) (byte, error) { // wait for tx ready for spi.Bus.TXDATA.HasBits(sifive.QSPI_TXDATA_FULL) { } // write data spi.Bus.TXDATA.Set(uint32(w)) // wait until receive has data data := spi.Bus.RXDATA.Get() for data&sifive.QSPI_RXDATA_EMPTY > 0 { data = spi.Bus.RXDATA.Get() } // return data return byte(data), nil } // I2C on the FE310-G002. type I2C struct { Bus sifive.I2C_Type } var ( I2C0 = (*I2C)(unsafe.Pointer(sifive.I2C0)) ) // I2CConfig is used to store config info for I2C. type I2CConfig struct { Frequency uint32 SCL Pin SDA Pin } var i2cClockFrequency uint32 = 32000000 // Configure is intended to setup the I2C interface. func (i2c *I2C) Configure(config I2CConfig) error { if config.Frequency == 0 { config.Frequency = 100 * KHz } if config.SDA == 0 && config.SCL == 0 { config.SDA = I2C0_SDA_PIN config.SCL = I2C0_SCL_PIN } i2c.SetBaudRate(config.Frequency) config.SDA.Configure(PinConfig{Mode: PinI2C}) config.SCL.Configure(PinConfig{Mode: PinI2C}) return nil } // SetBaudRate sets the communication speed for I2C. func (i2c *I2C) SetBaudRate(br uint32) error { var prescaler = i2cClockFrequency/(5*br) - 1 // disable controller before setting the prescale registers i2c.Bus.CTR.ClearBits(sifive.I2C_CTR_EN) // set prescaler registers i2c.Bus.PRER_LO.Set(uint32(prescaler & 0xff)) i2c.Bus.PRER_HI.Set(uint32((prescaler >> 8) & 0xff)) // enable controller i2c.Bus.CTR.SetBits(sifive.I2C_CTR_EN) return nil } // Tx does a single I2C transaction at the specified address. // It clocks out the given address, writes the bytes in w, reads back len(r) // bytes and stores them in r, and generates a stop condition on the bus. func (i2c *I2C) Tx(addr uint16, w, r []byte) error { var err error if len(w) != 0 { // send start/address for write i2c.sendAddress(addr, true) // ACK received (0: ACK, 1: NACK) if i2c.Bus.CR_SR.HasBits(sifive.I2C_SR_RX_ACK) { return errI2CAckExpected } // write data for _, b := range w { err = i2c.writeByte(b) if err != nil { return err } } } if len(r) != 0 { // send start/address for read i2c.sendAddress(addr, false) // ACK received (0: ACK, 1: NACK) if i2c.Bus.CR_SR.HasBits(sifive.I2C_SR_RX_ACK) { return errI2CAckExpected } // read first byte r[0] = i2c.readByte() for i := 1; i < len(r); i++ { // send an ACK i2c.Bus.CR_SR.Set(^uint32(sifive.I2C_CR_ACK)) // read data and send the ACK r[i] = i2c.readByte() } // send NACK to end transmission i2c.Bus.CR_SR.Set(sifive.I2C_CR_ACK) } // generate stop condition i2c.Bus.CR_SR.Set(sifive.I2C_CR_STO) return nil } // Writes a single byte to the I2C bus. func (i2c *I2C) writeByte(data byte) error { // Send data byte i2c.Bus.TXR_RXR.Set(uint32(data)) i2c.Bus.CR_SR.Set(sifive.I2C_CR_WR) // wait until transmission complete for i2c.Bus.CR_SR.HasBits(sifive.I2C_SR_TIP) { } // ACK received (0: ACK, 1: NACK) if i2c.Bus.CR_SR.HasBits(sifive.I2C_SR_RX_ACK) { return errI2CAckExpected } return nil } // Reads a single byte from the I2C bus. func (i2c *I2C) readByte() byte { i2c.Bus.CR_SR.Set(sifive.I2C_CR_RD) // wait until transmission complete for i2c.Bus.CR_SR.HasBits(sifive.I2C_SR_TIP) { } return byte(i2c.Bus.TXR_RXR.Get()) } // Sends the address and start signal. func (i2c *I2C) sendAddress(address uint16, write bool) error { data := (address << 1) if !write { data |= 1 // set read flag in transmit register } // write address to transmit register i2c.Bus.TXR_RXR.Set(uint32(data)) // generate start condition i2c.Bus.CR_SR.Set((sifive.I2C_CR_STA | sifive.I2C_CR_WR)) // wait until transmission complete for i2c.Bus.CR_SR.HasBits(sifive.I2C_SR_TIP) { } return nil } ================================================ FILE: src/machine/machine_fe310_simulator.go ================================================ //go:build !baremetal && hifive1b package machine var I2C0 = &I2C{Bus: 0, PinsSDA: []Pin{P12}, PinsSCL: []Pin{P13}} ================================================ FILE: src/machine/machine_gameboyadvance.go ================================================ //go:build gameboyadvance package machine import ( "device/gba" "image/color" "runtime/volatile" "unsafe" ) // Not sure what name to pick here. Not using ARM7TDMI because that's the CPU // name, not the device name. const deviceName = "GBA" // Interrupt numbers as used on the GameBoy Advance. Register them with // runtime/interrupt.New. const ( IRQ_VBLANK = gba.IRQ_VBLANK IRQ_HBLANK = gba.IRQ_HBLANK IRQ_VCOUNT = gba.IRQ_VCOUNT IRQ_TIMER0 = gba.IRQ_TIMER0 IRQ_TIMER1 = gba.IRQ_TIMER1 IRQ_TIMER2 = gba.IRQ_TIMER2 IRQ_TIMER3 = gba.IRQ_TIMER3 IRQ_COM = gba.IRQ_COM IRQ_DMA0 = gba.IRQ_DMA0 IRQ_DMA1 = gba.IRQ_DMA1 IRQ_DMA2 = gba.IRQ_DMA2 IRQ_DMA3 = gba.IRQ_DMA3 IRQ_KEYPAD = gba.IRQ_KEYPAD IRQ_GAMEPAK = gba.IRQ_GAMEPAK ) // Set has not been implemented. func (p Pin) Set(value bool) { // do nothing } var Display = DisplayMode3{(*[160][240]volatile.Register16)(unsafe.Pointer(uintptr(gba.MEM_VRAM)))} type DisplayMode3 struct { port *[160][240]volatile.Register16 } func (d *DisplayMode3) Configure() { // Use video mode 3 (in BG2, a 16bpp bitmap in VRAM) and Enable BG2 gba.DISP.DISPCNT.Set(gba.DISPCNT_BGMODE_3<> 3) | ((uint16(c.G) >> 3) << 5) | ((uint16(c.B) >> 3) << 10)) } func (d *DisplayMode3) Display() error { // Nothing to do here. return nil } ================================================ FILE: src/machine/machine_generic.go ================================================ //go:build !baremetal package machine import ( "crypto/rand" "errors" "slices" ) // Dummy machine package that calls out to external functions. const deviceName = "generic" var ( USB = &UART{100} ) // The Serial port always points to the default UART in a simulated environment. // // TODO: perhaps this should be a special serial object that outputs via WASI // stdout calls. var Serial = hardwareUART0 const ( PinInput PinMode = iota PinOutput PinInputPullup PinInputPulldown ) func (p Pin) Configure(config PinConfig) { gpioConfigure(p, config) } func (p Pin) Set(value bool) { gpioSet(p, value) } func (p Pin) Get() bool { return gpioGet(p) } //export __tinygo_gpio_configure func gpioConfigure(pin Pin, config PinConfig) //export __tinygo_gpio_set func gpioSet(pin Pin, value bool) //export __tinygo_gpio_get func gpioGet(pin Pin) bool // Generic PWM/timer peripheral. Properties can be configured depending on the // hardware. type timerType struct { // Static properties. instance int32 frequency uint64 bits int prescalers []int channelPins [][]Pin // Configured 'top' value. top uint32 } // Configure the PWM/timer peripheral. func (t *timerType) Configure(config PWMConfig) error { // Note: for very large period values, this multiplication will overflow. top := config.Period * t.frequency / 1e9 if config.Period == 0 { top = 0xffff // default for LEDs } // The maximum value that can be stored with the given number of bits in // this timer. maxTop := uint64(1)<= 0 matchSDA := slices.Index(i2c.PinsSDA, config.SDA) >= 0 if !matchSCL && !matchSDA { return errors.New("i2c: SCL and SDA pins are incorrect for this I2C instance") } else if !matchSCL { return errors.New("i2c: SCL pin is incorrect for this I2C instance") } else if !matchSDA { return errors.New("i2c: SDA pin is incorrect for this I2C instance") } } if config.Frequency == 0 { config.Frequency = 100 * KHz } i2cConfigure(i2c.Bus, config.SCL, config.SDA, config.Frequency) return nil } // SetBaudRate sets the I2C frequency. func (i2c *I2C) SetBaudRate(br uint32) error { i2cSetBaudRate(i2c.Bus, br) return nil } // Tx does a single I2C transaction at the specified address. func (i2c *I2C) Tx(addr uint16, w, r []byte) error { var wptr, rptr *byte var wlen, rlen int if len(w) != 0 { wptr = &w[0] wlen = len(w) } if len(r) != 0 { rptr = &r[0] rlen = len(r) } errCode := i2cTransfer(i2c.Bus, addr, wptr, wlen, rptr, rlen) switch errCode { case 0: return nil case 1: return errI2CNoDevices case 2: return errI2CMultipleDevices case 3: return errI2CWrongAddress default: return errI2CBusError // unknown error code } } //export __tinygo_i2c_configure func i2cConfigure(bus uint8, scl Pin, sda Pin, frequency uint32) //export __tinygo_i2c_set_baud_rate func i2cSetBaudRate(bus uint8, br uint32) //export __tinygo_i2c_transfer func i2cTransfer(bus uint8, addr uint16, w *byte, wlen int, r *byte, rlen int) int type UART struct { Bus uint8 } // Configure the UART. func (uart *UART) Configure(config UARTConfig) { uartConfigure(uart.Bus, config.TX, config.RX) } // Read from the UART. func (uart *UART) Read(data []byte) (n int, err error) { return uartRead(uart.Bus, &data[0], len(data)), nil } // Write to the UART. func (uart *UART) Write(data []byte) (n int, err error) { return uartWrite(uart.Bus, &data[0], len(data)), nil } // Buffered returns the number of bytes currently stored in the RX buffer. func (uart *UART) Buffered() int { return 0 } // ReadByte reads a single byte from the UART. func (uart *UART) ReadByte() (byte, error) { var b byte uartRead(uart.Bus, &b, 1) return b, nil } // WriteByte writes a single byte to the UART. func (uart *UART) WriteByte(b byte) error { uartWrite(uart.Bus, &b, 1) return nil } //export __tinygo_uart_configure func uartConfigure(bus uint8, tx Pin, rx Pin) //export __tinygo_uart_read func uartRead(bus uint8, buf *byte, bufLen int) int //export __tinygo_uart_write func uartWrite(bus uint8, buf *byte, bufLen int) int var ( hardwareUART0 = &UART{0} hardwareUART1 = &UART{1} ) // Some objects used by Atmel SAM D chips (samd21, samd51). // Defined here (without build tag) for convenience. var ( sercomUSART0 = UART{0} sercomUSART1 = UART{1} sercomUSART2 = UART{2} sercomUSART3 = UART{3} sercomUSART4 = UART{4} sercomUSART5 = UART{5} sercomSPIM0 = &SPI{0} sercomSPIM1 = &SPI{1} sercomSPIM2 = &SPI{2} sercomSPIM3 = &SPI{3} sercomSPIM4 = &SPI{4} sercomSPIM5 = &SPI{5} sercomSPIM6 = &SPI{6} sercomSPIM7 = &SPI{7} ) // GetRNG returns 32 bits of random data from the WASI random source. func GetRNG() (uint32, error) { var buf [4]byte _, err := rand.Read(buf[:]) if err != nil { return 0, err } return uint32(buf[0])<<0 | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24, nil } ================================================ FILE: src/machine/machine_generic_peripherals.go ================================================ //go:build !baremetal && !arduino_mkr1000 && !arduino_mkrwifi1010 && !arduino_nano33 && !arduino_zero && !circuitplay_express && !feather_m0 && !feather_m4 && !grandcentral_m4 && !itsybitsy_m0 && !itsybitsy_m4 && !matrixportal_m4 && !metro_m4_airlift && !p1am_100 && !pybadge && !pygamer && !pyportal && !qtpy && !trinket_m0 && !wioterminal && !xiao package machine // These peripherals are defined separately so that they can be excluded on // boards that define their peripherals in the board file (e.g. board_qtpy.go). var ( UART0 = hardwareUART0 UART1 = hardwareUART1 SPI0 = &SPI{0} SPI1 = &SPI{1} ) ================================================ FILE: src/machine/machine_k210.go ================================================ //go:build k210 package machine import ( "device/kendryte" "device/riscv" "errors" "runtime/interrupt" "unsafe" ) const deviceName = kendryte.Device func CPUFrequency() uint32 { return 390000000 } type fpioaPullMode uint8 type PinChange uint8 // Pin modes. const ( PinInput PinMode = iota PinInputPullup PinInputPulldown PinOutput ) // Deprecated: use PinInputPullup and PinInputPulldown instead. const ( PinInputPullUp = PinInputPullup PinInputPullDown = PinInputPulldown ) // FPIOA internal pull resistors. const ( fpioaPullNone fpioaPullMode = iota fpioaPullDown fpioaPullUp ) // GPIOHS pin interrupt events. const ( PinRising PinChange = 1 << iota PinFalling PinToggle = PinRising | PinFalling ) var ( errUnsupportedSPIController = errors.New("SPI controller not supported. Use SPI0 or SPI1.") errI2CTxAbort = errors.New("I2C transmission has been aborted.") ) func (p Pin) setFPIOAIOPull(pull fpioaPullMode) { switch pull { case fpioaPullNone: kendryte.FPIOA.IO[uint8(p)].ClearBits(kendryte.FPIOA_IO_PU & kendryte.FPIOA_IO_PD) case fpioaPullUp: kendryte.FPIOA.IO[uint8(p)].SetBits(kendryte.FPIOA_IO_PU) kendryte.FPIOA.IO[uint8(p)].ClearBits(kendryte.FPIOA_IO_PD) case fpioaPullDown: kendryte.FPIOA.IO[uint8(p)].ClearBits(kendryte.FPIOA_IO_PU) kendryte.FPIOA.IO[uint8(p)].SetBits(kendryte.FPIOA_IO_PD) } } // SetFPIOAFunction is used to configure the pin for one of the FPIOA functions. // Each pin on the Kendryte K210 can be configured with any of the available FPIOA functions. func (p Pin) SetFPIOAFunction(f FPIOAFunction) { kendryte.FPIOA.IO[uint8(p)].Set(fpioaFuncDefaults[uint8(f)]) } // FPIOAFunction returns the current FPIOA function of the pin. func (p Pin) FPIOAFunction() FPIOAFunction { return FPIOAFunction((kendryte.FPIOA.IO[uint8(p)].Get() & kendryte.FPIOA_IO_CH_SEL_Msk)) } // Configure this pin with the given configuration. // The pin must already be set as GPIO or GPIOHS pin. func (p Pin) Configure(config PinConfig) { var input bool // Check if the current pin's FPIOA function is either GPIO or GPIOHS. f := p.FPIOAFunction() if f < FUNC_GPIOHS0 || f > FUNC_GPIO7 { return // The pin is not configured as GPIO or GPIOHS. } // Configure pin. kendryte.FPIOA.IO[uint8(p)].SetBits(kendryte.FPIOA_IO_OE_EN | kendryte.FPIOA_IO_IE_EN | kendryte.FPIOA_IO_ST | kendryte.FPIOA_IO_DS_Msk) switch config.Mode { case PinInput: p.setFPIOAIOPull(fpioaPullNone) input = true case PinInputPullup: p.setFPIOAIOPull(fpioaPullUp) input = true case PinInputPulldown: p.setFPIOAIOPull(fpioaPullDown) input = true case PinOutput: p.setFPIOAIOPull(fpioaPullNone) input = false } if f >= FUNC_GPIO0 && f <= FUNC_GPIO7 { // Converts the IO pin number in the effective GPIO number (based on the FPIOA function). gpioPin := uint8(f - FUNC_GPIO0) if input { kendryte.GPIO.DIRECTION.ClearBits(1 << gpioPin) } else { kendryte.GPIO.DIRECTION.SetBits(1 << gpioPin) } } else if f >= FUNC_GPIOHS0 && f <= FUNC_GPIOHS31 { // Converts the IO pin number in the effective GPIOHS number (based on the FPIOA function). gpioPin := uint8(f - FUNC_GPIOHS0) if input { kendryte.GPIOHS.INPUT_EN.SetBits(1 << gpioPin) kendryte.GPIOHS.OUTPUT_EN.ClearBits(1 << gpioPin) } else { kendryte.GPIOHS.OUTPUT_EN.SetBits(1 << gpioPin) kendryte.GPIOHS.INPUT_EN.ClearBits(1 << gpioPin) } } } // Set the pin to high or low. func (p Pin) Set(high bool) { // Check if the current pin's FPIOA function is either GPIO or GPIOHS. f := p.FPIOAFunction() if f < FUNC_GPIOHS0 || f > FUNC_GPIO7 { return // The pin is not configured as GPIO or GPIOHS. } if f >= FUNC_GPIO0 && f <= FUNC_GPIO7 { gpioPin := uint8(f - FUNC_GPIO0) if high { kendryte.GPIO.DATA_OUTPUT.SetBits(1 << gpioPin) } else { kendryte.GPIO.DATA_OUTPUT.ClearBits(1 << gpioPin) } } else if f >= FUNC_GPIOHS0 && f <= FUNC_GPIOHS31 { gpioPin := uint8(f - FUNC_GPIOHS0) if high { kendryte.GPIOHS.OUTPUT_VAL.SetBits(1 << gpioPin) } else { kendryte.GPIOHS.OUTPUT_VAL.ClearBits(1 << gpioPin) } } } // Get returns the current value of a GPIO pin. func (p Pin) Get() bool { // Check if the current pin's FPIOA function is either GPIO or GPIOHS. f := p.FPIOAFunction() if f < FUNC_GPIOHS0 || f > FUNC_GPIO7 { return false // The pin is not configured as GPIO or GPIOHS. } var val uint32 if f >= FUNC_GPIO0 && f <= FUNC_GPIO7 { gpioPin := uint8(f - FUNC_GPIO0) val = kendryte.GPIO.DATA_INPUT.Get() & (1 << gpioPin) } else if f >= FUNC_GPIOHS0 && f <= FUNC_GPIOHS31 { gpioPin := uint8(f - FUNC_GPIOHS0) val = kendryte.GPIOHS.INPUT_VAL.Get() & (1 << gpioPin) } return (val > 0) } // Callbacks to be called for GPIOHS pins configured with SetInterrupt. var pinCallbacks [32]func(Pin) // SetInterrupt sets an interrupt to be executed when a particular pin changes // state. The pin should already be configured as an input, including a pull up // or down if no external pull is provided. // // You can pass a nil func to unset the pin change interrupt. If you do so, // the change parameter is ignored and can be set to any value (such as 0). // If the pin is already configured with a callback, you must first unset // this pins interrupt before you can set a new callback. func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { // Check if the pin is a GPIOHS pin. f := p.FPIOAFunction() if f < FUNC_GPIOHS0 || f > FUNC_GPIOHS31 { return ErrInvalidDataPin } gpioPin := uint8(f - FUNC_GPIOHS0) // Clear all interrupts. kendryte.GPIOHS.RISE_IE.ClearBits(1 << gpioPin) kendryte.GPIOHS.FALL_IE.ClearBits(1 << gpioPin) kendryte.GPIOHS.HIGH_IE.ClearBits(1 << gpioPin) kendryte.GPIOHS.LOW_IE.ClearBits(1 << gpioPin) // Clear all the pending bits for this pin. kendryte.GPIOHS.RISE_IP.SetBits(1 << gpioPin) kendryte.GPIOHS.FALL_IP.SetBits(1 << gpioPin) kendryte.GPIOHS.HIGH_IP.SetBits(1 << gpioPin) kendryte.GPIOHS.LOW_IP.SetBits(1 << gpioPin) if callback == nil { if pinCallbacks[gpioPin] != nil { pinCallbacks[gpioPin] = nil } return nil } if pinCallbacks[gpioPin] != nil { // The pin was already configured. // To properly re-configure a pin, unset it first and set a new // configuration. return ErrNoPinChangeChannel } pinCallbacks[gpioPin] = callback // Enable interrupts. if change&PinRising != 0 { kendryte.GPIOHS.RISE_IE.SetBits(1 << gpioPin) } if change&PinFalling != 0 { kendryte.GPIOHS.FALL_IE.SetBits(1 << gpioPin) } handleInterrupt := func(inter interrupt.Interrupt) { pin := uint8(inter.GetNumber() - kendryte.IRQ_GPIOHS0) if kendryte.GPIOHS.RISE_IE.HasBits(1 << pin) { kendryte.GPIOHS.RISE_IE.ClearBits(1 << pin) // Acknowledge interrupt atomically. riscv.AsmFull( "amoor.w {}, {mask}, ({reg})", map[string]interface{}{ "mask": uint32(1 << pin), "reg": uintptr(unsafe.Pointer(&kendryte.GPIOHS.RISE_IP.Reg)), }) kendryte.GPIOHS.RISE_IE.SetBits(1 << pin) } if kendryte.GPIOHS.FALL_IE.HasBits(1 << pin) { kendryte.GPIOHS.FALL_IE.ClearBits(1 << pin) // Acknowledge interrupt atomically. riscv.AsmFull( "amoor.w {}, {mask}, ({reg})", map[string]interface{}{ "mask": uint32(1 << pin), "reg": uintptr(unsafe.Pointer(&kendryte.GPIOHS.FALL_IP.Reg)), }) kendryte.GPIOHS.FALL_IE.SetBits(1 << pin) } pinCallbacks[pin](Pin(pin)) } var ir interrupt.Interrupt switch f { case FUNC_GPIOHS0: ir = interrupt.New(kendryte.IRQ_GPIOHS0, handleInterrupt) case FUNC_GPIOHS1: ir = interrupt.New(kendryte.IRQ_GPIOHS1, handleInterrupt) case FUNC_GPIOHS2: ir = interrupt.New(kendryte.IRQ_GPIOHS2, handleInterrupt) case FUNC_GPIOHS3: ir = interrupt.New(kendryte.IRQ_GPIOHS3, handleInterrupt) case FUNC_GPIOHS4: ir = interrupt.New(kendryte.IRQ_GPIOHS4, handleInterrupt) case FUNC_GPIOHS5: ir = interrupt.New(kendryte.IRQ_GPIOHS5, handleInterrupt) case FUNC_GPIOHS6: ir = interrupt.New(kendryte.IRQ_GPIOHS6, handleInterrupt) case FUNC_GPIOHS7: ir = interrupt.New(kendryte.IRQ_GPIOHS7, handleInterrupt) case FUNC_GPIOHS8: ir = interrupt.New(kendryte.IRQ_GPIOHS8, handleInterrupt) case FUNC_GPIOHS9: ir = interrupt.New(kendryte.IRQ_GPIOHS9, handleInterrupt) case FUNC_GPIOHS10: ir = interrupt.New(kendryte.IRQ_GPIOHS10, handleInterrupt) case FUNC_GPIOHS11: ir = interrupt.New(kendryte.IRQ_GPIOHS11, handleInterrupt) case FUNC_GPIOHS12: ir = interrupt.New(kendryte.IRQ_GPIOHS12, handleInterrupt) case FUNC_GPIOHS13: ir = interrupt.New(kendryte.IRQ_GPIOHS13, handleInterrupt) case FUNC_GPIOHS14: ir = interrupt.New(kendryte.IRQ_GPIOHS14, handleInterrupt) case FUNC_GPIOHS15: ir = interrupt.New(kendryte.IRQ_GPIOHS15, handleInterrupt) case FUNC_GPIOHS16: ir = interrupt.New(kendryte.IRQ_GPIOHS16, handleInterrupt) case FUNC_GPIOHS17: ir = interrupt.New(kendryte.IRQ_GPIOHS17, handleInterrupt) case FUNC_GPIOHS18: ir = interrupt.New(kendryte.IRQ_GPIOHS18, handleInterrupt) case FUNC_GPIOHS19: ir = interrupt.New(kendryte.IRQ_GPIOHS19, handleInterrupt) case FUNC_GPIOHS20: ir = interrupt.New(kendryte.IRQ_GPIOHS20, handleInterrupt) case FUNC_GPIOHS21: ir = interrupt.New(kendryte.IRQ_GPIOHS21, handleInterrupt) case FUNC_GPIOHS22: ir = interrupt.New(kendryte.IRQ_GPIOHS22, handleInterrupt) case FUNC_GPIOHS23: ir = interrupt.New(kendryte.IRQ_GPIOHS23, handleInterrupt) case FUNC_GPIOHS24: ir = interrupt.New(kendryte.IRQ_GPIOHS24, handleInterrupt) case FUNC_GPIOHS25: ir = interrupt.New(kendryte.IRQ_GPIOHS25, handleInterrupt) case FUNC_GPIOHS26: ir = interrupt.New(kendryte.IRQ_GPIOHS26, handleInterrupt) case FUNC_GPIOHS27: ir = interrupt.New(kendryte.IRQ_GPIOHS27, handleInterrupt) case FUNC_GPIOHS28: ir = interrupt.New(kendryte.IRQ_GPIOHS28, handleInterrupt) case FUNC_GPIOHS29: ir = interrupt.New(kendryte.IRQ_GPIOHS29, handleInterrupt) case FUNC_GPIOHS30: ir = interrupt.New(kendryte.IRQ_GPIOHS30, handleInterrupt) case FUNC_GPIOHS31: ir = interrupt.New(kendryte.IRQ_GPIOHS31, handleInterrupt) } ir.SetPriority(5) ir.Enable() return nil } type UART struct { Bus *kendryte.UARTHS_Type Buffer *RingBuffer } var ( UART0 = &_UART0 _UART0 = UART{Bus: kendryte.UARTHS, Buffer: NewRingBuffer()} ) func (uart *UART) Configure(config UARTConfig) { // Use default baudrate if not set. if config.BaudRate == 0 { config.BaudRate = 115200 } // Use default pins if not set. if config.TX == 0 && config.RX == 0 { config.TX = UART_TX_PIN config.RX = UART_RX_PIN } config.TX.SetFPIOAFunction(FUNC_UARTHS_TX) config.RX.SetFPIOAFunction(FUNC_UARTHS_RX) div := CPUFrequency()/config.BaudRate - 1 uart.Bus.DIV.Set(div) uart.Bus.TXCTRL.Set(kendryte.UARTHS_TXCTRL_TXEN) uart.Bus.RXCTRL.Set(kendryte.UARTHS_RXCTRL_RXEN) // Enable interrupts on receive. uart.Bus.IE.Set(kendryte.UARTHS_IE_RXWM) intr := interrupt.New(kendryte.IRQ_UARTHS, _UART0.handleInterrupt) intr.SetPriority(5) intr.Enable() } func (uart *UART) handleInterrupt(interrupt.Interrupt) { rxdata := uart.Bus.RXDATA.Get() c := byte(rxdata) if uint32(c) != rxdata { // The rxdata has other bits set than just the low 8 bits. This probably // means that the 'empty' flag is set, which indicates there is no data // to be read and the byte is garbage. Ignore this byte. return } uart.Receive(c) } func (uart *UART) writeByte(c byte) error { for uart.Bus.TXDATA.Get()&kendryte.UARTHS_TXDATA_FULL != 0 { } uart.Bus.TXDATA.Set(uint32(c)) return nil } func (uart *UART) flush() {} type SPI struct { Bus *kendryte.SPI_Type } // SPIConfig is used to store config info for SPI. type SPIConfig struct { Frequency uint32 SCK Pin SDO Pin SDI Pin LSBFirst bool Mode uint8 } // Configure is intended to setup the SPI interface. // Only SPI controller 0 and 1 can be used because SPI2 is a special // peripheral-mode controller and SPI3 is used for flashing. func (spi *SPI) Configure(config SPIConfig) error { // Use default pins if not set. if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { config.SCK = SPI0_SCK_PIN config.SDO = SPI0_SDO_PIN config.SDI = SPI0_SDI_PIN } // Enable APB2 clock. kendryte.SYSCTL.CLK_EN_CENT.SetBits(kendryte.SYSCTL_CLK_EN_CENT_APB2_CLK_EN) switch spi.Bus { case kendryte.SPI0: // Initialize SPI clock. kendryte.SYSCTL.CLK_EN_PERI.SetBits(kendryte.SYSCTL_CLK_EN_PERI_SPI0_CLK_EN) kendryte.SYSCTL.CLK_TH1.ClearBits(kendryte.SYSCTL_CLK_TH1_SPI0_CLK_Msk) // Initialize pins. config.SCK.SetFPIOAFunction(FUNC_SPI0_SCLK) config.SDO.SetFPIOAFunction(FUNC_SPI0_D0) config.SDI.SetFPIOAFunction(FUNC_SPI0_D1) case kendryte.SPI1: // Initialize SPI clock. kendryte.SYSCTL.CLK_EN_PERI.SetBits(kendryte.SYSCTL_CLK_EN_PERI_SPI1_CLK_EN) kendryte.SYSCTL.CLK_TH1.ClearBits(kendryte.SYSCTL_CLK_TH1_SPI1_CLK_Msk) // Initialize pins. config.SCK.SetFPIOAFunction(FUNC_SPI1_SCLK) config.SDO.SetFPIOAFunction(FUNC_SPI1_D0) config.SDI.SetFPIOAFunction(FUNC_SPI1_D1) default: return errUnsupportedSPIController } // Set default frequency. if config.Frequency == 0 { config.Frequency = 4000000 // 4MHz } baudr := CPUFrequency() / config.Frequency spi.Bus.BAUDR.Set(baudr) // Configure SPI mode 0, standard frame format, 8-bit data, little-endian. spi.Bus.IMR.Set(0) spi.Bus.DMACR.Set(0) spi.Bus.DMATDLR.Set(0x10) spi.Bus.DMARDLR.Set(0) spi.Bus.SER.Set(0) spi.Bus.SSIENR.Set(0) spi.Bus.CTRLR0.Set((7 << 16)) spi.Bus.SPI_CTRLR0.Set(0) spi.Bus.ENDIAN.Set(0) return nil } // Transfer writes/reads a single byte using the SPI interface. func (spi *SPI) Transfer(w byte) (byte, error) { spi.Bus.SSIENR.Set(0) // Set transfer-receive mode. spi.Bus.CTRLR0.ClearBits(0x3 << 8) // Enable/disable SPI. spi.Bus.SSIENR.Set(1) defer spi.Bus.SSIENR.Set(0) // Enable/disable device. spi.Bus.SER.Set(0x1) defer spi.Bus.SER.Set(0) spi.Bus.DR0.Set(uint32(w)) // Wait for transfer. for spi.Bus.SR.Get()&0x05 != 0x04 { } // Wait for data. for spi.Bus.RXFLR.Get() == 0 { } return byte(spi.Bus.DR0.Get()), nil } // I2C on the K210. type I2C struct { Bus kendryte.I2C_Type } var ( I2C0 = (*I2C)(unsafe.Pointer(kendryte.I2C0)) I2C1 = (*I2C)(unsafe.Pointer(kendryte.I2C1)) I2C2 = (*I2C)(unsafe.Pointer(kendryte.I2C2)) ) // I2CConfig is used to store config info for I2C. type I2CConfig struct { Frequency uint32 SCL Pin SDA Pin } // Configure is intended to setup the I2C interface. func (i2c *I2C) Configure(config I2CConfig) error { if config.Frequency == 0 { config.Frequency = 100 * KHz } if config.SDA == 0 && config.SCL == 0 { config.SDA = I2C0_SDA_PIN config.SCL = I2C0_SCL_PIN } // Enable APB0 clock. kendryte.SYSCTL.CLK_EN_CENT.SetBits(kendryte.SYSCTL_CLK_EN_CENT_APB0_CLK_EN) switch &i2c.Bus { case kendryte.I2C0: // Initialize I2C0 clock. kendryte.SYSCTL.CLK_EN_PERI.SetBits(kendryte.SYSCTL_CLK_EN_PERI_I2C0_CLK_EN) kendryte.SYSCTL.CLK_TH5.ReplaceBits(0x03, kendryte.SYSCTL_CLK_TH5_I2C0_CLK_Msk, kendryte.SYSCTL_CLK_TH5_I2C0_CLK_Pos) // Initialize pins. config.SDA.SetFPIOAFunction(FUNC_I2C0_SDA) config.SCL.SetFPIOAFunction(FUNC_I2C0_SCLK) case kendryte.I2C1: // Initialize I2C1 clock. kendryte.SYSCTL.CLK_EN_PERI.SetBits(kendryte.SYSCTL_CLK_EN_PERI_I2C1_CLK_EN) kendryte.SYSCTL.CLK_TH5.ReplaceBits(0x03, kendryte.SYSCTL_CLK_TH5_I2C1_CLK_Msk, kendryte.SYSCTL_CLK_TH5_I2C1_CLK_Pos) // Initialize pins. config.SDA.SetFPIOAFunction(FUNC_I2C1_SDA) config.SCL.SetFPIOAFunction(FUNC_I2C1_SCLK) case kendryte.I2C2: // Initialize I2C2 clock. kendryte.SYSCTL.CLK_EN_PERI.SetBits(kendryte.SYSCTL_CLK_EN_PERI_I2C2_CLK_EN) kendryte.SYSCTL.CLK_TH5.ReplaceBits(0x03, kendryte.SYSCTL_CLK_TH5_I2C2_CLK_Msk, kendryte.SYSCTL_CLK_TH5_I2C2_CLK_Pos) // Initialize pins. config.SDA.SetFPIOAFunction(FUNC_I2C2_SDA) config.SCL.SetFPIOAFunction(FUNC_I2C2_SCLK) } i2c.SetBaudRate(config.Frequency) i2c.Bus.INTR_MASK.Set(0) i2c.Bus.DMA_CR.Set(0x03) i2c.Bus.DMA_RDLR.Set(0) i2c.Bus.DMA_TDLR.Set(0x4) return nil } // SetBaudRate sets the communication speed for I2C. func (i2c *I2C) SetBaudRate(br uint32) error { div := CPUFrequency() / br / 16 // Disable controller before setting the prescale register. i2c.Bus.ENABLE.Set(0) i2c.Bus.CON.Set(0x63) // Set prescaler registers. i2c.Bus.SS_SCL_HCNT.Set(uint32(div)) i2c.Bus.SS_SCL_LCNT.Set(uint32(div)) return nil } // Tx does a single I2C transaction at the specified address. // It clocks out the given address, writes the bytes in w, reads back len(r) // bytes and stores them in r, and generates a stop condition on the bus. func (i2c *I2C) Tx(addr uint16, w, r []byte) error { // Set peripheral address. i2c.Bus.TAR.Set(uint32(addr)) // Enable controller. i2c.Bus.ENABLE.Set(1) if len(w) != 0 { i2c.Bus.CLR_TX_ABRT.Set(i2c.Bus.CLR_TX_ABRT.Get()) dataLen := uint32(len(w)) di := 0 for dataLen != 0 { fifoLen := 8 - i2c.Bus.TXFLR.Get() if dataLen < fifoLen { fifoLen = dataLen } for i := uint32(0); i < fifoLen; i++ { i2c.Bus.DATA_CMD.Set(uint32(w[di])) di += 1 } if i2c.Bus.TX_ABRT_SOURCE.Get() != 0 { return errI2CTxAbort } dataLen -= fifoLen } // Wait for transmission to complete. for i2c.Bus.STATUS.HasBits(kendryte.I2C_STATUS_ACTIVITY) || !i2c.Bus.STATUS.HasBits(kendryte.I2C_STATUS_TFE) { } if i2c.Bus.TX_ABRT_SOURCE.Get() != 0 { return errI2CTxAbort } } if len(r) != 0 { dataLen := uint32(len(r)) cmdLen := uint32(len(r)) di := 0 for dataLen != 0 || cmdLen != 0 { fifoLen := i2c.Bus.RXFLR.Get() if dataLen < fifoLen { fifoLen = dataLen } for i := uint32(0); i < fifoLen; i++ { r[di] = byte(i2c.Bus.DATA_CMD.Get()) di += 1 } dataLen -= fifoLen fifoLen = 8 - i2c.Bus.TXFLR.Get() if cmdLen < fifoLen { fifoLen = cmdLen } for i := uint32(0); i < fifoLen; i++ { i2c.Bus.DATA_CMD.Set(0x100) } if i2c.Bus.TX_ABRT_SOURCE.Get() != 0 { return errI2CTxAbort } cmdLen -= fifoLen } } return nil } ================================================ FILE: src/machine/machine_mimxrt1062.go ================================================ //go:build mimxrt1062 package machine import ( "device/nxp" "math/bits" "runtime/interrupt" "runtime/volatile" ) // Peripheral abstraction layer for the MIMXRT1062 const deviceName = nxp.Device func CPUFrequency() uint32 { return 600000000 } const ( // GPIO PinInput PinMode = iota PinInputPullup PinInputPulldown PinOutput PinOutputOpenDrain PinDisable // ADC PinInputAnalog // UART PinModeUARTTX PinModeUARTRX // SPI PinModeSPISDI PinModeSPISDO PinModeSPICLK PinModeSPICS // I2C PinModeI2CSDA PinModeI2CSCL ) // Deprecated: use PinInputPullup and PinInputPulldown instead. const ( PinInputPullUp = PinInputPullup PinInputPullDown = PinInputPulldown ) type PinChange uint8 const ( PinRising PinChange = iota + 2 PinFalling PinToggle ) // pinJumpTable represents a function lookup table for all 128 GPIO pins. // // There are 4 GPIO ports (A-D) and 32 pins (0-31) on each port. The uint8 value // of a Pin is used as table index. The number of pins with a defined (non-nil) // function is recorded in the uint8 field numDefined. type pinJumpTable struct { lut [4 * 32]func(Pin) numDefined uint8 } // pinISR stores the interrupt callbacks for GPIO pins, and pinInterrupt holds // an interrupt service routine that dispatches the interrupt callbacks. var ( pinISR pinJumpTable pinInterrupt *interrupt.Interrupt ) // From the i.MXRT1062 Processor Reference Manual (Chapter 12 - GPIO): // // | High-speed GPIOs exist in this device: // | - GPIO1-5 are standard-speed GPIOs that run off the IPG_CLK_ROOT, while // | GPIO6-9 are high-speed GPIOs that run at the AHB_CLK_ROOT frequency. // | See the table "System Clocks, Gating, and Override" in CCM chapter. // | - Regular GPIO and high speed GPIO are paired (GPIO1 and GPIO6 share the // | same pins, GPIO2 and GPIO7 share, etc). The IOMUXC_GPR_GPR26-29 // | registers are used to determine if the regular or high-speed GPIO // | module is used for the GPIO pins on a given port. // // Therefore, we do not even use GPIO1-5 and instead use their high-speed // partner for all pins. This is configured at startup in the runtime package // (func initPins() in `runtime_mimxrt1062.go`). // We cannot declare 32 pins for all available ports (GPIO1-9) anyway, since Pin // is only uint8, and 9*32=288 > 256, so something has to be sacrificed. const ( portA Pin = iota * 32 // GPIO1(6) portB // GPIO2(7) portC // GPIO3(8) portD // GPIO4(9) ) const ( // [Pad]: Alt Func 0 Alt Func 1 Alt Func 2 Alt Func 3 Alt Func 4 Alt Func 5 Alt Func 6 Alt Func 7 Alt Func 8 Alt Func 9 // ---------- --------------- --------------- ------------------- -------------------- -------------------- ----------- -------------------- -------------------- --------------------- ---------------- PA0 = portA + 0 // [AD_B0_00]: FLEXPWM2_PWMA03 XBAR1_INOUT14 REF_CLK_32K USB_OTG2_ID LPI2C1_SCLS GPIO1_IO00 USDHC1_RESET_B LPSPI3_SCK ~ ~ PA1 = portA + 1 // [AD_B0_01]: FLEXPWM2_PWMB03 XBAR1_INOUT15 REF_CLK_24M USB_OTG1_ID LPI2C1_SDAS GPIO1_IO01 EWM_OUT_B LPSPI3_SDO ~ ~ PA2 = portA + 2 // [AD_B0_02]: FLEXCAN2_TX XBAR1_INOUT16 LPUART6_TX USB_OTG1_PWR FLEXPWM1_PWMX00 GPIO1_IO02 LPI2C1_HREQ LPSPI3_SDI ~ ~ PA3 = portA + 3 // [AD_B0_03]: FLEXCAN2_RX XBAR1_INOUT17 LPUART6_RX USB_OTG1_OC FLEXPWM1_PWMX01 GPIO1_IO03 REF_CLK_24M LPSPI3_PCS0 ~ ~ PA4 = portA + 4 // [AD_B0_04]: SRC_BOOT_MODE00 MQS_RIGHT ENET_TX_DATA03 SAI2_TX_SYNC CSI_DATA09 GPIO1_IO04 PIT_TRIGGER00 LPSPI3_PCS1 ~ ~ PA5 = portA + 5 // [AD_B0_05]: SRC_BOOT_MODE01 MQS_LEFT ENET_TX_DATA02 SAI2_TX_BCLK CSI_DATA08 GPIO1_IO05 XBAR1_INOUT17 LPSPI3_PCS2 ~ ~ PA6 = portA + 6 // [AD_B0_06]: JTAG_TMS GPT2_COMPARE1 ENET_RX_CLK SAI2_RX_BCLK CSI_DATA07 GPIO1_IO06 XBAR1_INOUT18 LPSPI3_PCS3 ~ ~ PA7 = portA + 7 // [AD_B0_07]: JTAG_TCK GPT2_COMPARE2 ENET_TX_ER SAI2_RX_SYNC CSI_DATA06 GPIO1_IO07 XBAR1_INOUT19 ENET_1588_EVENT3_OUT ~ ~ PA8 = portA + 8 // [AD_B0_08]: JTAG_MOD GPT2_COMPARE3 ENET_RX_DATA03 SAI2_RX_DATA CSI_DATA05 GPIO1_IO08 XBAR1_IN20 ENET_1588_EVENT3_IN ~ ~ PA9 = portA + 9 // [AD_B0_09]: JTAG_TDI FLEXPWM2_PWMA03 ENET_RX_DATA02 SAI2_TX_DATA CSI_DATA04 GPIO1_IO09 XBAR1_IN21 GPT2_CLK SEMC_DQS4 ~ PA10 = portA + 10 // [AD_B0_10]: JTAG_TDO FLEXPWM1_PWMA03 ENET_CRS SAI2_MCLK CSI_DATA03 GPIO1_IO10 XBAR1_IN22 ENET_1588_EVENT0_OUT FLEXCAN3_TX ARM_TRACE_SWO PA11 = portA + 11 // [AD_B0_11]: JTAG_TRSTB FLEXPWM1_PWMB03 ENET_COL WDOG1_WDOG_B CSI_DATA02 GPIO1_IO11 XBAR1_IN23 ENET_1588_EVENT0_IN FLEXCAN3_RX SEMC_CLK6 PA12 = portA + 12 // [AD_B0_12]: LPI2C4_SCL CCM_PMIC_READY LPUART1_TX WDOG2_WDOG_B FLEXPWM1_PWMX02 GPIO1_IO12 ENET_1588_EVENT1_OUT NMI_GLUE_NMI ~ ~ PA13 = portA + 13 // [AD_B0_13]: LPI2C4_SDA GPT1_CLK LPUART1_RX EWM_OUT_B FLEXPWM1_PWMX03 GPIO1_IO13 ENET_1588_EVENT1_IN REF_CLK_24M ~ ~ PA14 = portA + 14 // [AD_B0_14]: USB_OTG2_OC XBAR1_IN24 LPUART1_CTS_B ENET_1588_EVENT0_OUT CSI_VSYNC GPIO1_IO14 FLEXCAN2_TX FLEXCAN3_TX ~ ~ PA15 = portA + 15 // [AD_B0_15]: USB_OTG2_PWR XBAR1_IN25 LPUART1_RTS_B ENET_1588_EVENT0_IN CSI_HSYNC GPIO1_IO15 FLEXCAN2_RX WDOG1_WDOG_RST_B_DEB FLEXCAN3_RX ~ PA16 = portA + 16 // [AD_B1_00]: USB_OTG2_ID QTIMER3_TIMER0 LPUART2_CTS_B LPI2C1_SCL WDOG1_B GPIO1_IO16 USDHC1_WP KPP_ROW07 ENET2_1588_EVENT0_OUT FLEXIO3_FLEXIO00 PA17 = portA + 17 // [AD_B1_01]: USB_OTG1_PWR QTIMER3_TIMER1 LPUART2_RTS_B LPI2C1_SDA CCM_PMIC_READY GPIO1_IO17 USDHC1_VSELECT KPP_COL07 ENET2_1588_EVENT0_IN FLEXIO3_FLEXIO01 PA18 = portA + 18 // [AD_B1_02]: USB_OTG1_ID QTIMER3_TIMER2 LPUART2_TX SPDIF_OUT ENET_1588_EVENT2_OUT GPIO1_IO18 USDHC1_CD_B KPP_ROW06 GPT2_CLK FLEXIO3_FLEXIO02 PA19 = portA + 19 // [AD_B1_03]: USB_OTG1_OC QTIMER3_TIMER3 LPUART2_RX SPDIF_IN ENET_1588_EVENT2_IN GPIO1_IO19 USDHC2_CD_B KPP_COL06 GPT2_CAPTURE1 FLEXIO3_FLEXIO03 PA20 = portA + 20 // [AD_B1_04]: FLEXSPIB_DATA03 ENET_MDC LPUART3_CTS_B SPDIF_SR_CLK CSI_PIXCLK GPIO1_IO20 USDHC2_DATA0 KPP_ROW05 GPT2_CAPTURE2 FLEXIO3_FLEXIO04 PA21 = portA + 21 // [AD_B1_05]: FLEXSPIB_DATA02 ENET_MDIO LPUART3_RTS_B SPDIF_OUT CSI_MCLK GPIO1_IO21 USDHC2_DATA1 KPP_COL05 GPT2_COMPARE1 FLEXIO3_FLEXIO05 PA22 = portA + 22 // [AD_B1_06]: FLEXSPIB_DATA01 LPI2C3_SDA LPUART3_TX SPDIF_LOCK CSI_VSYNC GPIO1_IO22 USDHC2_DATA2 KPP_ROW04 GPT2_COMPARE2 FLEXIO3_FLEXIO06 PA23 = portA + 23 // [AD_B1_07]: FLEXSPIB_DATA00 LPI2C3_SCL LPUART3_RX SPDIF_EXT_CLK CSI_HSYNC GPIO1_IO23 USDHC2_DATA3 KPP_COL04 GPT2_COMPARE3 FLEXIO3_FLEXIO07 PA24 = portA + 24 // [AD_B1_08]: FLEXSPIA_SS1_B FLEXPWM4_PWMA00 FLEXCAN1_TX CCM_PMIC_READY CSI_DATA09 GPIO1_IO24 USDHC2_CMD KPP_ROW03 FLEXIO3_FLEXIO08 ~ PA25 = portA + 25 // [AD_B1_09]: FLEXSPIA_DQS FLEXPWM4_PWMA01 FLEXCAN1_RX SAI1_MCLK CSI_DATA08 GPIO1_IO25 USDHC2_CLK KPP_COL03 FLEXIO3_FLEXIO09 ~ PA26 = portA + 26 // [AD_B1_10]: FLEXSPIA_DATA03 WDOG1_B LPUART8_TX SAI1_RX_SYNC CSI_DATA07 GPIO1_IO26 USDHC2_WP KPP_ROW02 ENET2_1588_EVENT1_OUT FLEXIO3_FLEXIO10 PA27 = portA + 27 // [AD_B1_11]: FLEXSPIA_DATA02 EWM_OUT_B LPUART8_RX SAI1_RX_BCLK CSI_DATA06 GPIO1_IO27 USDHC2_RESET_B KPP_COL02 ENET2_1588_EVENT1_IN FLEXIO3_FLEXIO11 PA28 = portA + 28 // [AD_B1_12]: FLEXSPIA_DATA01 ACMP_OUT00 LPSPI3_PCS0 SAI1_RX_DATA00 CSI_DATA05 GPIO1_IO28 USDHC2_DATA4 KPP_ROW01 ENET2_1588_EVENT2_OUT FLEXIO3_FLEXIO12 PA29 = portA + 29 // [AD_B1_13]: FLEXSPIA_DATA00 ACMP_OUT01 LPSPI3_SDI SAI1_TX_DATA00 CSI_DATA04 GPIO1_IO29 USDHC2_DATA5 KPP_COL01 ENET2_1588_EVENT2_IN FLEXIO3_FLEXIO13 PA30 = portA + 30 // [AD_B1_14]: FLEXSPIA_SCLK ACMP_OUT02 LPSPI3_SDO SAI1_TX_BCLK CSI_DATA03 GPIO1_IO30 USDHC2_DATA6 KPP_ROW00 ENET2_1588_EVENT3_OUT FLEXIO3_FLEXIO14 PA31 = portA + 31 // [AD_B1_15]: FLEXSPIA_SS0_B ACMP_OUT03 LPSPI3_SCK SAI1_TX_SYNC CSI_DATA02 GPIO1_IO31 USDHC2_DATA7 KPP_COL00 ENET2_1588_EVENT3_IN FLEXIO3_FLEXIO15 PB0 = portB + 0 // [B0_00]: LCD_CLK QTIMER1_TIMER0 MQS_RIGHT LPSPI4_PCS0 FLEXIO2_FLEXIO00 GPIO2_IO00 SEMC_CSX01 ENET2_MDC ~ ~ PB1 = portB + 1 // [B0_01]: LCD_ENABLE QTIMER1_TIMER1 MQS_LEFT LPSPI4_SDI FLEXIO2_FLEXIO01 GPIO2_IO01 SEMC_CSX02 ENET2_MDIO ~ ~ PB2 = portB + 2 // [B0_02]: LCD_HSYNC QTIMER1_TIMER2 FLEXCAN1_TX LPSPI4_SDO FLEXIO2_FLEXIO02 GPIO2_IO02 SEMC_CSX03 ENET2_1588_EVENT0_OUT ~ ~ PB3 = portB + 3 // [B0_03]: LCD_VSYNC QTIMER2_TIMER0 FLEXCAN1_RX LPSPI4_SCK FLEXIO2_FLEXIO03 GPIO2_IO03 WDOG2_RESET_B_DEB ENET2_1588_EVENT0_IN ~ ~ PB4 = portB + 4 // [B0_04]: LCD_DATA00 QTIMER2_TIMER1 LPI2C2_SCL ARM_TRACE0 FLEXIO2_FLEXIO04 GPIO2_IO04 SRC_BOOT_CFG00 ENET2_TDATA03 ~ ~ PB5 = portB + 5 // [B0_05]: LCD_DATA01 QTIMER2_TIMER2 LPI2C2_SDA ARM_TRACE1 FLEXIO2_FLEXIO05 GPIO2_IO05 SRC_BOOT_CFG01 ENET2_TDATA02 ~ ~ PB6 = portB + 6 // [B0_06]: LCD_DATA02 QTIMER3_TIMER0 FLEXPWM2_PWMA00 ARM_TRACE2 FLEXIO2_FLEXIO06 GPIO2_IO06 SRC_BOOT_CFG02 ENET2_RX_CLK ~ ~ PB7 = portB + 7 // [B0_07]: LCD_DATA03 QTIMER3_TIMER1 FLEXPWM2_PWMB00 ARM_TRACE3 FLEXIO2_FLEXIO07 GPIO2_IO07 SRC_BOOT_CFG03 ENET2_TX_ER ~ ~ PB8 = portB + 8 // [B0_08]: LCD_DATA04 QTIMER3_TIMER2 FLEXPWM2_PWMA01 LPUART3_TX FLEXIO2_FLEXIO08 GPIO2_IO08 SRC_BOOT_CFG04 ENET2_RDATA03 ~ ~ PB9 = portB + 9 // [B0_09]: LCD_DATA05 QTIMER4_TIMER0 FLEXPWM2_PWMB01 LPUART3_RX FLEXIO2_FLEXIO09 GPIO2_IO09 SRC_BOOT_CFG05 ENET2_RDATA02 ~ ~ PB10 = portB + 10 // [B0_10]: LCD_DATA06 QTIMER4_TIMER1 FLEXPWM2_PWMA02 SAI1_TX_DATA03 FLEXIO2_FLEXIO10 GPIO2_IO10 SRC_BOOT_CFG06 ENET2_CRS ~ ~ PB11 = portB + 11 // [B0_11]: LCD_DATA07 QTIMER4_TIMER2 FLEXPWM2_PWMB02 SAI1_TX_DATA02 FLEXIO2_FLEXIO11 GPIO2_IO11 SRC_BOOT_CFG07 ENET2_COL ~ ~ PB12 = portB + 12 // [B0_12]: LCD_DATA08 XBAR1_INOUT10 ARM_TRACE_CLK SAI1_TX_DATA01 FLEXIO2_FLEXIO12 GPIO2_IO12 SRC_BOOT_CFG08 ENET2_TDATA00 ~ ~ PB13 = portB + 13 // [B0_13]: LCD_DATA09 XBAR1_INOUT11 ARM_TRACE_SWO SAI1_MCLK FLEXIO2_FLEXIO13 GPIO2_IO13 SRC_BOOT_CFG09 ENET2_TDATA01 ~ ~ PB14 = portB + 14 // [B0_14]: LCD_DATA10 XBAR1_INOUT12 ARM_TXEV SAI1_RX_SYNC FLEXIO2_FLEXIO14 GPIO2_IO14 SRC_BOOT_CFG10 ENET2_TX_EN ~ ~ PB15 = portB + 15 // [B0_15]: LCD_DATA11 XBAR1_INOUT13 ARM_RXEV SAI1_RX_BCLK FLEXIO2_FLEXIO15 GPIO2_IO15 SRC_BOOT_CFG11 ENET2_TX_CLK ENET2_REF_CLK2 ~ PB16 = portB + 16 // [B1_00]: LCD_DATA12 XBAR1_INOUT14 LPUART4_TX SAI1_RX_DATA00 FLEXIO2_FLEXIO16 GPIO2_IO16 FLEXPWM1_PWMA03 ENET2_RX_ER FLEXIO3_FLEXIO16 ~ PB17 = portB + 17 // [B1_01]: LCD_DATA13 XBAR1_INOUT15 LPUART4_RX SAI1_TX_DATA00 FLEXIO2_FLEXIO17 GPIO2_IO17 FLEXPWM1_PWMB03 ENET2_RDATA00 FLEXIO3_FLEXIO17 ~ PB18 = portB + 18 // [B1_02]: LCD_DATA14 XBAR1_INOUT16 LPSPI4_PCS2 SAI1_TX_BCLK FLEXIO2_FLEXIO18 GPIO2_IO18 FLEXPWM2_PWMA03 ENET2_RDATA01 FLEXIO3_FLEXIO18 ~ PB19 = portB + 19 // [B1_03]: LCD_DATA15 XBAR1_INOUT17 LPSPI4_PCS1 SAI1_TX_SYNC FLEXIO2_FLEXIO19 GPIO2_IO19 FLEXPWM2_PWMB03 ENET2_RX_EN FLEXIO3_FLEXIO19 ~ PB20 = portB + 20 // [B1_04]: LCD_DATA16 LPSPI4_PCS0 CSI_DATA15 ENET_RX_DATA00 FLEXIO2_FLEXIO20 GPIO2_IO20 GPT1_CLK FLEXIO3_FLEXIO20 ~ ~ PB21 = portB + 21 // [B1_05]: LCD_DATA17 LPSPI4_SDI CSI_DATA14 ENET_RX_DATA01 FLEXIO2_FLEXIO21 GPIO2_IO21 GPT1_CAPTURE1 FLEXIO3_FLEXIO21 ~ ~ PB22 = portB + 22 // [B1_06]: LCD_DATA18 LPSPI4_SDO CSI_DATA13 ENET_RX_EN FLEXIO2_FLEXIO22 GPIO2_IO22 GPT1_CAPTURE2 FLEXIO3_FLEXIO22 ~ ~ PB23 = portB + 23 // [B1_07]: LCD_DATA19 LPSPI4_SCK CSI_DATA12 ENET_TX_DATA00 FLEXIO2_FLEXIO23 GPIO2_IO23 GPT1_COMPARE1 FLEXIO3_FLEXIO23 ~ ~ PB24 = portB + 24 // [B1_08]: LCD_DATA20 QTIMER1_TIMER3 CSI_DATA11 ENET_TX_DATA01 FLEXIO2_FLEXIO24 GPIO2_IO24 FLEXCAN2_TX GPT1_COMPARE2 FLEXIO3_FLEXIO24 ~ PB25 = portB + 25 // [B1_09]: LCD_DATA21 QTIMER2_TIMER3 CSI_DATA10 ENET_TX_EN FLEXIO2_FLEXIO25 GPIO2_IO25 FLEXCAN2_RX GPT1_COMPARE3 FLEXIO3_FLEXIO25 ~ PB26 = portB + 26 // [B1_10]: LCD_DATA22 QTIMER3_TIMER3 CSI_DATA00 ENET_TX_CLK FLEXIO2_FLEXIO26 GPIO2_IO26 ENET_REF_CLK FLEXIO3_FLEXIO26 ~ ~ PB27 = portB + 27 // [B1_11]: LCD_DATA23 QTIMER4_TIMER3 CSI_DATA01 ENET_RX_ER FLEXIO2_FLEXIO27 GPIO2_IO27 LPSPI4_PCS3 FLEXIO3_FLEXIO27 ~ ~ PB28 = portB + 28 // [B1_12]: LPUART5_TX CSI_PIXCLK ENET_1588_EVENT0_IN FLEXIO2_FLEXIO28 GPIO2_IO28 USDHC1_CD_B FLEXIO3_FLEXIO28 ~ ~ ~ PB29 = portB + 29 // [B1_13]: WDOG1_B LPUART5_RX CSI_VSYNC ENET_1588_EVENT0_OUT FLEXIO2_FLEXIO29 GPIO2_IO29 USDHC1_WP SEMC_DQS4 FLEXIO3_FLEXIO29 ~ PB30 = portB + 30 // [B1_14]: ENET_MDC FLEXPWM4_PWMA02 CSI_HSYNC XBAR1_IN02 FLEXIO2_FLEXIO30 GPIO2_IO30 USDHC1_VSELECT ENET2_TDATA00 FLEXIO3_FLEXIO30 ~ PB31 = portB + 31 // [B1_15]: ENET_MDIO FLEXPWM4_PWMA03 CSI_MCLK XBAR1_IN03 FLEXIO2_FLEXIO31 GPIO2_IO31 USDHC1_RESET_B ENET2_TDATA01 FLEXIO3_FLEXIO31 ~ PC0 = portC + 0 // [SD_B1_00]: USDHC2_DATA3 FLEXSPIB_DATA03 FLEXPWM1_PWMA03 SAI1_TX_DATA03 LPUART4_TX GPIO3_IO00 SAI3_RX_DATA ~ ~ ~ PC1 = portC + 1 // [SD_B1_01]: USDHC2_DATA2 FLEXSPIB_DATA02 FLEXPWM1_PWMB03 SAI1_TX_DATA02 LPUART4_RX GPIO3_IO01 SAI3_TX_DATA ~ ~ ~ PC2 = portC + 2 // [SD_B1_02]: USDHC2_DATA1 FLEXSPIB_DATA01 FLEXPWM2_PWMA03 SAI1_TX_DATA01 FLEXCAN1_TX GPIO3_IO02 CCM_WAIT SAI3_TX_SYNC ~ ~ PC3 = portC + 3 // [SD_B1_03]: USDHC2_DATA0 FLEXSPIB_DATA00 FLEXPWM2_PWMB03 SAI1_MCLK FLEXCAN1_RX GPIO3_IO03 CCM_PMIC_READY SAI3_TX_BCLK ~ ~ PC4 = portC + 4 // [SD_B1_04]: USDHC2_CLK FLEXSPIB_SCLK LPI2C1_SCL SAI1_RX_SYNC FLEXSPIA_SS1_B GPIO3_IO04 CCM_STOP SAI3_MCLK ~ ~ PC5 = portC + 5 // [SD_B1_05]: USDHC2_CMD FLEXSPIA_DQS LPI2C1_SDA SAI1_RX_BCLK FLEXSPIB_SS0_B GPIO3_IO05 SAI3_RX_SYNC ~ ~ ~ PC6 = portC + 6 // [SD_B1_06]: USDHC2_RESET_B FLEXSPIA_SS0_B LPUART7_CTS_B SAI1_RX_DATA00 LPSPI2_PCS0 GPIO3_IO06 SAI3_RX_BCLK ~ ~ ~ PC7 = portC + 7 // [SD_B1_07]: SEMC_CSX01 FLEXSPIA_SCLK LPUART7_RTS_B SAI1_TX_DATA00 LPSPI2_SCK GPIO3_IO07 ~ ~ ~ ~ PC8 = portC + 8 // [SD_B1_08]: USDHC2_DATA4 FLEXSPIA_DATA00 LPUART7_TX SAI1_TX_BCLK LPSPI2_SD0 GPIO3_IO08 SEMC_CSX02 ~ ~ ~ PC9 = portC + 9 // [SD_B1_09]: USDHC2_DATA5 FLEXSPIA_DATA01 LPUART7_RX SAI1_TX_SYNC LPSPI2_SDI GPIO3_IO09 ~ ~ ~ ~ PC10 = portC + 10 // [SD_B1_10]: USDHC2_DATA6 FLEXSPIA_DATA02 LPUART2_RX LPI2C2_SDA LPSPI2_PCS2 GPIO3_IO10 ~ ~ ~ ~ PC11 = portC + 11 // [SD_B1_11]: USDHC2_DATA7 FLEXSPIA_DATA03 LPUART2_TX LPI2C2_SCL LPSPI2_PCS3 GPIO3_IO11 ~ ~ ~ ~ PC12 = portC + 12 // [SD_B0_00]: USDHC1_CMD FLEXPWM1_PWMA00 LPI2C3_SCL XBAR1_INOUT04 LPSPI1_SCK GPIO3_IO12 FLEXSPIA_SS1_B ENET2_TX_EN SEMC_DQS4 ~ PC13 = portC + 13 // [SD_B0_01]: USDHC1_CLK FLEXPWM1_PWMB00 LPI2C3_SDA XBAR1_INOUT05 LPSPI1_PCS0 GPIO3_IO13 FLEXSPIB_SS1_B ENET2_TX_CLK ENET2_REF_CLK2 ~ PC14 = portC + 14 // [SD_B0_02]: USDHC1_DATA0 FLEXPWM1_PWMA01 LPUART8_CTS_B XBAR1_INOUT06 LPSPI1_SDO GPIO3_IO14 ENET2_RX_ER SEMC_CLK5 ~ ~ PC15 = portC + 15 // [SD_B0_03]: USDHC1_DATA1 FLEXPWM1_PWMB01 LPUART8_RTS_B XBAR1_INOUT07 LPSPI1_SDI GPIO3_IO15 ENET2_RDATA00 SEMC_CLK6 ~ ~ PC16 = portC + 16 // [SD_B0_04]: USDHC1_DATA2 FLEXPWM1_PWMA02 LPUART8_TX XBAR1_INOUT08 FLEXSPIB_SS0_B GPIO3_IO16 CCM_CLKO1 ENET2_RDATA01 ~ ~ PC17 = portC + 17 // [SD_B0_05]: USDHC1_DATA3 FLEXPWM1_PWMB02 LPUART8_RX XBAR1_INOUT09 FLEXSPIB_DQS GPIO3_IO17 CCM_CLKO2 ENET2_RX_EN ~ ~ PC18 = portC + 18 // [EMC_32]: SEMC_DATA10 FLEXPWM3_PWMB01 LPUART7_RX CCM_PMIC_RDY CSI_DATA21 GPIO3_IO18 ENET2_TX_EN ~ ~ ~ PC19 = portC + 19 // [EMC_33]: SEMC_DATA11 FLEXPWM3_PWMA02 USDHC1_RESET_B SAI3_RX_DATA CSI_DATA20 GPIO3_IO19 ENET2_TX_CLK ENET2_REF_CLK2 ~ ~ PC20 = portC + 20 // [EMC_34]: SEMC_DATA12 FLEXPWM3_PWMB02 USDHC1_VSELECT SAI3_RX_SYNC CSI_DATA19 GPIO3_IO20 ENET2_RX_ER ~ ~ ~ PC21 = portC + 21 // [EMC_35]: SEMC_DATA13 XBAR1_INOUT18 GPT1_COMPARE1 SAI3_RX_BCLK CSI_DATA18 GPIO3_IO21 USDHC1_CD_B ENET2_RDATA00 ~ ~ PC22 = portC + 22 // [EMC_36]: SEMC_DATA14 XBAR1_IN22 GPT1_COMPARE2 SAI3_TX_DATA CSI_DATA17 GPIO3_IO22 USDHC1_WP ENET2_RDATA01 FLEXCAN3_TX ~ PC23 = portC + 23 // [EMC_37]: SEMC_DATA15 XBAR1_IN23 GPT1_COMPARE3 SAI3_MCLK CSI_DATA16 GPIO3_IO23 USDHC2_WP ENET2_RX_EN FLEXCAN3_RX ~ PC24 = portC + 24 // [EMC_38]: SEMC_DM01 FLEXPWM1_PWMA03 LPUART8_TX SAI3_TX_BCLK CSI_FIELD GPIO3_IO24 USDHC2_VSELECT ENET2_MDC ~ ~ PC25 = portC + 25 // [EMC_39]: SEMC_DQS FLEXPWM1_PWMB03 LPUART8_RX SAI3_TX_SYNC WDOG1_WDOG_B GPIO3_IO25 USDHC2_CD_B ENET2_MDIO SEMC_DQS4 ~ PC26 = portC + 26 // [EMC_40]: SEMC_RDY GPT2_CAPTURE2 LPSPI1_PCS2 USB_OTG2_OC ENET_MDC GPIO3_IO26 USDHC2_RESET_B SEMC_CLK5 ~ ~ PC27 = portC + 27 // [EMC_41]: SEMC_CSX00 GPT2_CAPTURE1 LPSPI1_PCS3 USB_OTG2_PWR ENET_MDIO GPIO3_IO27 USDHC1_VSELECT ~ ~ ~ _ = portC + 28 // _ = portC + 29 // _ = portC + 30 // _ = portC + 31 // PD0 = portD + 0 // [EMC_00]: SEMC_DATA00 FLEXPWM4_PWMA00 LPSPI2_SCK XBAR1_XBAR_IN02 FLEXIO1_FLEXIO00 GPIO4_IO00 ~ ~ ~ ~ PD1 = portD + 1 // [EMC_01]: SEMC_DATA01 FLEXPWM4_PWMB00 LPSPI2_PCS0 XBAR1_IN03 FLEXIO1_FLEXIO01 GPIO4_IO01 ~ ~ ~ ~ PD2 = portD + 2 // [EMC_02]: SEMC_DATA02 FLEXPWM4_PWMA01 LPSPI2_SDO XBAR1_INOUT04 FLEXIO1_FLEXIO02 GPIO4_IO02 ~ ~ ~ ~ PD3 = portD + 3 // [EMC_03]: SEMC_DATA03 FLEXPWM4_PWMB01 LPSPI2_SDI XBAR1_INOUT05 FLEXIO1_FLEXIO03 GPIO4_IO03 ~ ~ ~ ~ PD4 = portD + 4 // [EMC_04]: SEMC_DATA04 FLEXPWM4_PWMA02 SAI2_TX_DATA XBAR1_INOUT06 FLEXIO1_FLEXIO04 GPIO4_IO04 ~ ~ ~ ~ PD5 = portD + 5 // [EMC_05]: SEMC_DATA05 FLEXPWM4_PWMB02 SAI2_TX_SYNC XBAR1_INOUT07 FLEXIO1_FLEXIO05 GPIO4_IO05 ~ ~ ~ ~ PD6 = portD + 6 // [EMC_06]: SEMC_DATA06 FLEXPWM2_PWMA00 SAI2_TX_BCLK XBAR1_INOUT08 FLEXIO1_FLEXIO06 GPIO4_IO06 ~ ~ ~ ~ PD7 = portD + 7 // [EMC_07]: SEMC_DATA07 FLEXPWM2_PWMB00 SAI2_MCLK XBAR1_INOUT09 FLEXIO1_FLEXIO07 GPIO4_IO07 ~ ~ ~ ~ PD8 = portD + 8 // [EMC_08]: SEMC_DM00 FLEXPWM2_PWMA01 SAI2_RX_DATA XBAR1_INOUT17 FLEXIO1_FLEXIO08 GPIO4_IO08 ~ ~ ~ ~ PD9 = portD + 9 // [EMC_09]: SEMC_ADDR00 FLEXPWM2_PWMB01 SAI2_RX_SYNC FLEXCAN2_TX FLEXIO1_FLEXIO09 GPIO4_IO09 FLEXSPI2_B_SS1_B ~ ~ ~ PD10 = portD + 10 // [EMC_10]: SEMC_ADDR01 FLEXPWM2_PWMA02 SAI2_RX_BCLK FLEXCAN2_RX FLEXIO1_FLEXIO10 GPIO4_IO10 FLEXSPI2_B_SS0_B ~ ~ ~ PD11 = portD + 11 // [EMC_11]: SEMC_ADDR02 FLEXPWM2_PWMB02 LPI2C4_SDA USDHC2_RESET_B FLEXIO1_FLEXIO11 GPIO4_IO11 FLEXSPI2_B_DQS ~ ~ ~ PD12 = portD + 12 // [EMC_12]: SEMC_ADDR03 XBAR1_IN24 LPI2C4_SCL USDHC1_WP FLEXPWM1_PWMA03 GPIO4_IO12 FLEXSPI2_B_SCLK ~ ~ ~ PD13 = portD + 13 // [EMC_13]: SEMC_ADDR04 XBAR1_IN25 LPUART3_TX MQS_RIGHT FLEXPWM1_PWMB03 GPIO4_IO13 FLEXSPI2_B_DATA00 ~ ~ ~ PD14 = portD + 14 // [EMC_14]: SEMC_ADDR05 XBAR1_INOUT19 LPUART3_RX MQS_LEFT LPSPI2_PCS1 GPIO4_IO14 FLEXSPI2_B_DATA01 ~ ~ ~ PD15 = portD + 15 // [EMC_15]: SEMC_ADDR06 XBAR1_IN20 LPUART3_CTS_B SPDIF_OUT QTIMER3_TIMER0 GPIO4_IO15 FLEXSPI2_B_DATA02 ~ ~ ~ PD16 = portD + 16 // [EMC_16]: SEMC_ADDR07 XBAR1_IN21 LPUART3_RTS_B SPDIF_IN QTIMER3_TIMER1 GPIO4_IO16 FLEXSPI2_B_DATA03 ~ ~ ~ PD17 = portD + 17 // [EMC_17]: SEMC_ADDR08 FLEXPWM4_PWMA03 LPUART4_CTS_B FLEXCAN1_TX QTIMER3_TIMER2 GPIO4_IO17 ~ ~ ~ ~ PD18 = portD + 18 // [EMC_18]: SEMC_ADDR09 FLEXPWM4_PWMB03 LPUART4_RTS_B FLEXCAN1_RX QTIMER3_TIMER3 GPIO4_IO18 SNVS_VIO_5_CTL ~ ~ ~ PD19 = portD + 19 // [EMC_19]: SEMC_ADDR11 FLEXPWM2_PWMA03 LPUART4_TX ENET_RDATA01 QTIMER2_TIMER0 GPIO4_IO19 SNVS_VIO_5 ~ ~ ~ PD20 = portD + 20 // [EMC_20]: SEMC_ADDR12 FLEXPWM2_PWMB03 LPUART4_RX ENET_RDATA00 QTIMER2_TIMER1 GPIO4_IO20 ~ ~ ~ ~ PD21 = portD + 21 // [EMC_21]: SEMC_BA0 FLEXPWM3_PWMA03 LPI2C3_SDA ENET_TDATA01 QTIMER2_TIMER2 GPIO4_IO21 ~ ~ ~ ~ PD22 = portD + 22 // [EMC_22]: SEMC_BA1 FLEXPWM3_PWMB03 LPI2C3_SCL ENET_TDATA00 QTIMER2_TIMER3 GPIO4_IO22 FLEXSPI2_A_SS1_B ~ ~ ~ PD23 = portD + 23 // [EMC_23]: SEMC_ADDR10 FLEXPWM1_PWMA00 LPUART5_TX ENET_RX_EN GPT1_CAPTURE2 GPIO4_IO23 FLEXSPI2_A_DQS ~ ~ ~ PD24 = portD + 24 // [EMC_24]: SEMC_CAS FLEXPWM1_PWMB00 LPUART5_RX ENET_TX_EN GPT1_CAPTURE1 GPIO4_IO24 FLEXSPI2_A_SS0_B ~ ~ ~ PD25 = portD + 25 // [EMC_25]: SEMC_RAS FLEXPWM1_PWMA01 LPUART6_TX ENET_TX_CLK ENET_REF_CLK GPIO4_IO25 FLEXSPI2_A_SCLK ~ ~ ~ PD26 = portD + 26 // [EMC_26]: SEMC_CLK FLEXPWM1_PWMB01 LPUART6_RX ENET_RX_ER FLEXIO1_FLEXIO12 GPIO4_IO26 FLEXSPI2_A_DATA00 ~ ~ ~ PD27 = portD + 27 // [EMC_27]: SEMC_CKE FLEXPWM1_PWMA02 LPUART5_RTS_B LPSPI1_SCK FLEXIO1_FLEXIO13 GPIO4_IO27 FLEXSPI2_A_DATA01 ~ ~ ~ PD28 = portD + 28 // [EMC_28]: SEMC_WE FLEXPWM1_PWMB02 LPUART5_CTS_B LPSPI1_SDO FLEXIO1_FLEXIO14 GPIO4_IO28 FLEXSPI2_A_DATA02 ~ ~ ~ PD29 = portD + 29 // [EMC_29]: SEMC_CS0 FLEXPWM3_PWMA00 LPUART6_RTS_B LPSPI1_SDI FLEXIO1_FLEXIO15 GPIO4_IO29 FLEXSPI2_A_DATA03 ~ ~ ~ PD30 = portD + 30 // [EMC_30]: SEMC_DATA08 FLEXPWM3_PWMB00 LPUART6_CTS_B LPSPI1_PCS0 CSI_DATA23 GPIO4_IO30 ENET2_TDATA00 ~ ~ ~ PD31 = portD + 31 // [EMC_31]: SEMC_DATA09 FLEXPWM3_PWMA01 LPUART7_TX LPSPI1_PCS1 CSI_DATA22 GPIO4_IO31 ENET2_TDATA01 ~ ~ ~ ) func (p Pin) getPos() uint8 { return uint8(p % 32) } func (p Pin) getMask() uint32 { return uint32(1) << p.getPos() } func (p Pin) getPort() Pin { return Pin(p/32) * 32 } // Configure sets the GPIO pad and pin properties, and selects the appropriate // alternate function, for a given Pin and PinConfig. func (p Pin) Configure(config PinConfig) { var ( sre = uint32(0x01 << 0) dse = func(n uint32) uint32 { return (n & 0x07) << 3 } spd = func(n uint32) uint32 { return (n & 0x03) << 6 } ode = uint32(0x01 << 11) pke = uint32(0x01 << 12) pue = uint32(0x01 << 13) pup = func(n uint32) uint32 { return (n & 0x03) << 14 } hys = uint32(0x01 << 16) ) _, gpio := p.getGPIO() // use fast GPIO for all pins pad, mux := p.getPad() // first configure the pad characteristics switch config.Mode { case PinInput: gpio.GDIR.ClearBits(p.getMask()) pad.Set(dse(7)) case PinInputPullup: gpio.GDIR.ClearBits(p.getMask()) pad.Set(dse(7) | pke | pue | pup(3) | hys) case PinInputPulldown: gpio.GDIR.ClearBits(p.getMask()) pad.Set(dse(7) | pke | pue | hys) case PinOutput: gpio.GDIR.SetBits(p.getMask()) pad.Set(dse(7)) case PinOutputOpenDrain: gpio.GDIR.SetBits(p.getMask()) pad.Set(dse(7) | ode) case PinDisable: gpio.GDIR.ClearBits(p.getMask()) pad.Set(dse(7) | hys) case PinInputAnalog: gpio.GDIR.ClearBits(p.getMask()) pad.Set(dse(7)) case PinModeUARTTX: pad.Set(sre | dse(3) | spd(3)) case PinModeUARTRX: pad.Set(dse(7) | pke | pue | pup(3) | hys) case PinModeSPISDI: pad.Set(dse(7) | spd(2)) case PinModeSPISDO: pad.Set(dse(7) | spd(2)) case PinModeSPICLK: pad.Set(dse(7) | spd(2)) case PinModeSPICS: pad.Set(dse(7)) case PinModeI2CSDA, PinModeI2CSCL: pad.Set(ode | sre | dse(4) | spd(1) | pke | pue | pup(3)) } // then configure the alternate function mux mux.Set(p.getMuxMode(config)) } // Get returns the current value of a GPIO pin. func (p Pin) Get() bool { _, gpio := p.getGPIO() // use fast GPIO for all pins return gpio.PSR.HasBits(p.getMask()) } // Set changes the value of the GPIO pin. The pin must be configured as output. func (p Pin) Set(value bool) { _, gpio := p.getGPIO() // use fast GPIO for all pins if value { gpio.DR_SET.Set(p.getMask()) } else { gpio.DR_CLEAR.Set(p.getMask()) } } // Toggle switches an output pin from low to high or from high to low. func (p Pin) Toggle() { _, gpio := p.getGPIO() // use fast GPIO for all pins gpio.DR_TOGGLE.Set(p.getMask()) } // dispatchInterrupt invokes the user-provided callback functions for external // interrupts generated on the high-speed GPIO pins. // // Unfortunately, all four high-speed GPIO ports (A-D) are connected to just a // single interrupt control line. Therefore, the interrupt status register (ISR) // must be checked in all four GPIO ports on every interrupt. func (jt *pinJumpTable) dispatchInterrupt(interrupt.Interrupt) { handle := func(gpio *nxp.GPIO_Type, port Pin) { if status := gpio.ISR.Get() & gpio.IMR.Get(); status != 0 { gpio.ISR.Set(status) // clear interrupt for status != 0 { off := Pin(bits.TrailingZeros32(status)) // ctz pin := Pin(port + off) jt.lut[pin](pin) status &^= 1 << off } } } if jt.numDefined > 0 { handle(nxp.GPIO6, portA) handle(nxp.GPIO7, portB) handle(nxp.GPIO8, portC) handle(nxp.GPIO9, portD) } } // set associates a function with a given Pin in the receiver lookup table. If // the function is nil, the given Pin's associated function is removed. func (jt *pinJumpTable) set(pin Pin, fn func(Pin)) { if int(pin) < len(jt.lut) { if nil != fn { if nil == jt.lut[pin] { jt.numDefined++ } jt.lut[pin] = fn } else { if nil != jt.lut[pin] { jt.numDefined-- } jt.lut[pin] = nil } } } // SetInterrupt sets an interrupt to be executed when a particular pin changes // state. The pin should already be configured as an input, including a pull up // or down if no external pull is provided. // // This call will replace a previously set callback on this pin. You can pass a // nil func to unset the pin change interrupt. If you do so, the change // parameter is ignored and can be set to any value (such as 0). func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { _, gpio := p.getGPIO() // use fast GPIO for all pins mask := p.getMask() if nil != callback { switch change { case PinRising, PinFalling: gpio.EDGE_SEL.ClearBits(mask) var reg *volatile.Register32 var pos uint8 if pos = p.getPos(); pos < 16 { reg = &gpio.ICR1 // ICR1 = pins 0-15 } else { reg = &gpio.ICR2 // ICR2 = pins 16-31 pos -= 16 } reg.ReplaceBits(uint32(change), 0x3, pos*2) case PinToggle: gpio.EDGE_SEL.SetBits(mask) } pinISR.set(p, callback) // associate the callback with the pin gpio.ISR.Set(mask) // clear any pending interrupt (W1C) gpio.IMR.SetBits(mask) // enable external interrupt } else { pinISR.set(p, nil) // remove any associated callback from the pin gpio.ISR.Set(mask) // clear any pending interrupt (W1C) gpio.IMR.ClearBits(mask) // disable external interrupt } // enable or disable the interrupt based on number of defined callbacks if pinISR.numDefined > 0 { if nil == pinInterrupt { // create the Interrupt if it is not yet defined irq := interrupt.New(nxp.IRQ_GPIO6_7_8_9, pinISR.dispatchInterrupt) pinInterrupt = &irq pinInterrupt.Enable() } } else { if nil != pinInterrupt { // disable the interrupt if it is defined pinInterrupt.Disable() } } return nil } // getGPIO returns both the normal (IPG_CLK_ROOT) and high-speed (AHB_CLK_ROOT) // GPIO peripherals to which a given Pin is connected. // // Note that, currently, the device is configured to use high-speed GPIO for all // pins (GPIO6-9), so the first return value should not be used (GPIO1-4). // See the remarks and documentation reference in the comments preceding the // const Pin definitions above. func (p Pin) getGPIO() (norm *nxp.GPIO_Type, fast *nxp.GPIO_Type) { switch p.getPort() { case portA: return nxp.GPIO1, nxp.GPIO6 case portB: return nxp.GPIO2, nxp.GPIO7 case portC: return nxp.GPIO3, nxp.GPIO8 case portD: return nxp.GPIO4, nxp.GPIO9 default: panic("machine: unknown port") } } // getPad returns both the pad and mux configuration registers for a given Pin. func (p Pin) getPad() (pad *volatile.Register32, mux *volatile.Register32) { switch p.getPort() { case portA: switch p.getPos() { case 0: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_00, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_00 case 1: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_01, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_01 case 2: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_02, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_02 case 3: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_03, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_03 case 4: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_04, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_04 case 5: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_05, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_05 case 6: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_06, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_06 case 7: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_07, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_07 case 8: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_08, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_08 case 9: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_09, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_09 case 10: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_10, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_10 case 11: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_11, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_11 case 12: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_12, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_12 case 13: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_13, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_13 case 14: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_14, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_14 case 15: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_15, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_15 case 16: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_00, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_00 case 17: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_01, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_01 case 18: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_02, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_02 case 19: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_03, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_03 case 20: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_04, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_04 case 21: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_05, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_05 case 22: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_06, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_06 case 23: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_07, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_07 case 24: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_08, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_08 case 25: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_09, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_09 case 26: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_10, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_10 case 27: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_11, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_11 case 28: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_12, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_12 case 29: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_13, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_13 case 30: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_14, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_14 case 31: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_15, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_15 } case portB: switch p.getPos() { case 0: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_00, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_00 case 1: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_01, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_01 case 2: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_02, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_02 case 3: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_03, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_03 case 4: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_04, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_04 case 5: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_05, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_05 case 6: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_06, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_06 case 7: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_07, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_07 case 8: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_08, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_08 case 9: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_09, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_09 case 10: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_10, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_10 case 11: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_11, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_11 case 12: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_12, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_12 case 13: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_13, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_13 case 14: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_14, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_14 case 15: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_15, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_15 case 16: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_00, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_00 case 17: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_01, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_01 case 18: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_02, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_02 case 19: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_03, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_03 case 20: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_04, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_04 case 21: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_05, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_05 case 22: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_06, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_06 case 23: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_07, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_07 case 24: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_08, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_08 case 25: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_09, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_09 case 26: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_10, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_10 case 27: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_11, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_11 case 28: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_12, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_12 case 29: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_13, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_13 case 30: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_14, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_14 case 31: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_15, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_15 } case portC: switch p.getPos() { case 0: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_00, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_00 case 1: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_01, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_01 case 2: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_02, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_02 case 3: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_03, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_03 case 4: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_04, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_04 case 5: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_05, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_05 case 6: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_06, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_06 case 7: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_07, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_07 case 8: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_08, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_08 case 9: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_09, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_09 case 10: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_10, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_10 case 11: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_11, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_11 case 12: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B0_00, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B0_00 case 13: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B0_01, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B0_01 case 14: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B0_02, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B0_02 case 15: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B0_03, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B0_03 case 16: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B0_04, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B0_04 case 17: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B0_05, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B0_05 case 18: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_32, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_32 case 19: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_33, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_33 case 20: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_34, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_34 case 21: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_35, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_35 case 22: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_36, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_36 case 23: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_37, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_37 case 24: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_38, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_38 case 25: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_39, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_39 case 26: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_40, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_40 case 27: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_41, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_41 case 28, 29, 30, 31: } case portD: switch p.getPos() { case 0: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_00, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_00 case 1: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_01, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_01 case 2: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_02, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_02 case 3: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_03, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_03 case 4: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_04, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_04 case 5: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_05, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_05 case 6: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_06, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_06 case 7: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_07, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_07 case 8: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_08, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_08 case 9: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_09, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_09 case 10: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_10, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_10 case 11: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_11, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_11 case 12: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_12, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_12 case 13: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_13, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_13 case 14: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_14, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_14 case 15: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_15, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_15 case 16: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_16, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_16 case 17: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_17, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_17 case 18: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_18, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_18 case 19: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_19, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_19 case 20: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_20, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_20 case 21: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_21, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_21 case 22: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_22, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_22 case 23: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_23, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_23 case 24: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_24, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_24 case 25: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_25, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_25 case 26: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_26, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_26 case 27: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_27, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_27 case 28: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_28, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_28 case 29: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_29, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_29 case 30: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_30, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_30 case 31: return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_31, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_31 } } panic("machine: invalid pin") } // muxSelect is yet another level of indirection required to connect pins in an // alternate function state to a desired peripheral (since more than one pin can // provide a given alternate function). // // Once a pin is configured with a given alternate function mode, the IOMUXC // device must then be configured to select which alternate function pin to // route to the desired peripheral. // // The reference manual refers to this functionality as a "Daisy Chain". The // associated docs are found in the i.MX RT1060 Processor Reference Manual: // "Chapter 11.3.3 Daisy chain - multi pads driving same module input pin" type muxSelect struct { mux uint8 // AF mux selection (NOT a Pin type) sel *volatile.Register32 // AF selection register } // connect configures the IOMUXC controller to route a given pin with alternate // function to a desired peripheral (see godoc comments on type muxSelect). func (s muxSelect) connect() { s.sel.Set(uint32(s.mux)) } // getMuxMode acts as a callback from the `(Pin).Configure(PinMode)` routine to // determine the alternate function setting for a given Pin and PinConfig. // This value is used in the IOMUXC device's SW_MUX_CTL_PAD_GPIO_* registers. func (p Pin) getMuxMode(config PinConfig) uint32 { const forcePath = true // TODO: should be input parameter? switch config.Mode { // GPIO case PinInput, PinInputPullup, PinInputPulldown, PinOutput, PinOutputOpenDrain, PinDisable: mode := uint32(0x5) // GPIO is always alternate function 5 if forcePath { mode |= 0x10 // SION bit } return mode // ADC case PinInputAnalog: mode := uint32(0x5) // use alternate function 5 (GPIO) if forcePath { mode |= 0x10 // SION bit } return mode // UART RX/TX case PinModeUARTRX, PinModeUARTTX: mode := uint32(0x2) // UART is usually alternate function 2 on Teensy 4.x // Teensy 4.1 has a UART (LPUART5) with alternate function 1 if p == PB28 || p == PB29 { mode = 0x1 } return mode // SPI SDI case PinModeSPISDI: var mode uint32 switch p { case PC15: // LPSPI1 SDI on PC15 alternate function 4 mode = uint32(0x4) case PA2: // LPSPI3 SDI on PA2 alternate function 7 mode = uint32(0x7) case PB1: // LPSPI4 SDI on PB1 alternate function 3 mode = uint32(0x3) default: panic("machine: invalid SPI SDI pin") } if forcePath { mode |= 0x10 // SION bit } return mode // SPI SDO case PinModeSPISDO: var mode uint32 switch p { case PC14: // LPSPI1 SDO on PC14 alternate function 4 mode = uint32(0x4) case PA30: // LPSPI3 SDO on PA30 alternate function 2 mode = uint32(0x2) case PB2: // LPSPI4 SDO on PB2 alternate function 3 mode = uint32(0x3) default: panic("machine: invalid SPI SDO pin") } if forcePath { mode |= 0x10 // SION bit } return mode // SPI SCK case PinModeSPICLK: var mode uint32 switch p { case PC12: // LPSPI1 SCK on PC12 alternate function 4 mode = uint32(0x4) case PA31: // LPSPI3 SCK on PA31 alternate function 2 mode = uint32(0x2) case PB3: // LPSPI4 SCK on PB3 alternate function 3 mode = uint32(0x3) default: panic("machine: invalid SPI CLK pin") } if forcePath { mode |= 0x10 // SION bit } return mode // SPI CS case PinModeSPICS: var mode uint32 switch p { case PC13: // LPSPI1 CS on PC13 alternate function 4 mode = uint32(0x4) case PA3: // LPSPI3 CS on PA3 alternate function 7 mode = uint32(0x7) case PB0: // LPSPI4 CS on PB0 alternate function 3 mode = uint32(0x3) default: // use alternate function 5 (GPIO) if non-CS pin selected mode = uint32(0x5) } if forcePath { mode |= 0x10 // SION bit } return mode // I2C SDA case PinModeI2CSDA: var mode uint32 switch p { case PA13: // LPI2C4 SDA on PA13 alternate function 0 mode = uint32(0) case PA17: // LPI2C1 SDA on PA17 alternate function 3 mode = uint32(3) case PA22: // LPI2C3 SDA on PA22 alternate function 1 mode = uint32(1) default: panic("machine: invalid I2C SDA pin") } if forcePath { mode |= 0x10 // SION bit } return mode // I2C SCL case PinModeI2CSCL: var mode uint32 switch p { case PA12: // LPI2C4 SCL on PA12 alternate function 0 mode = uint32(0) case PA16: // LPI2C1 SCL on PA16 alternate function 3 mode = uint32(3) case PA23: // LPI2C3 SCL on PA23 alternate function 1 mode = uint32(1) default: panic("machine: invalid I2C SCL pin") } if forcePath { mode |= 0x10 // SION bit } return mode default: panic("machine: invalid pin mode") } } // maximum ADC value for the currently configured resolution (used for scaling) var adcMaximum uint32 // InitADC is not used by this machine. Use `(ADC).Configure()`. func InitADC() {} // Configure initializes the receiver's ADC peripheral and pin for analog input. func (a ADC) Configure(config ADCConfig) { // if not specified, use defaults: 10-bit resolution, 4 samples/conversion const ( defaultResolution = uint32(10) defaultSamples = uint32(4) ) a.Pin.Configure(PinConfig{Mode: PinInputAnalog}) resolution, samples := config.Resolution, config.Samples if 0 == resolution { resolution = defaultResolution } if 0 == samples { samples = defaultSamples } if resolution > 12 { resolution = 12 // maximum resolution of 12 bits } adcMaximum = (uint32(1) << resolution) - 1 mode, average := a.mode(resolution, samples) nxp.ADC1.CFG.Set(mode | nxp.ADC_CFG_ADHSC) // configure ADC1 nxp.ADC2.CFG.Set(mode | nxp.ADC_CFG_ADHSC) // configure ADC2 // begin calibration nxp.ADC1.GC.Set(average | nxp.ADC_GC_CAL) nxp.ADC2.GC.Set(average | nxp.ADC_GC_CAL) for a.isCalibrating() { } // wait for calibration } // Get performs a single ADC conversion, returning a 16-bit unsigned integer. // The value returned will be scaled (uniformly distributed) if necessary so // that it is always in the range [0..65535], regardless of the ADC's configured // bit size (resolution). func (a ADC) Get() uint16 { if ch1, ch2, ok := a.Pin.getADCChannel(); ok { for a.isCalibrating() { } // wait for calibration var val uint32 if noADCChannel != ch1 { nxp.ADC1.HC0.Set(uint32(ch1)) for !nxp.ADC1.HS.HasBits(nxp.ADC_HS_COCO0) { } val = nxp.ADC1.R0.Get() & 0xFFFF } else { nxp.ADC2.HC0.Set(uint32(ch2)) for !nxp.ADC2.HS.HasBits(nxp.ADC_HS_COCO0) { } val = nxp.ADC2.R0.Get() & 0xFFFF } // should never be zero, but just in case, use UINT16_MAX so that the scalar // gets factored out of the conversion result, leaving the original reading // to be returned unaltered/unscaled. if adcMaximum == 0 { adcMaximum = 0xFFFF } // scale up to a 16-bit value return uint16((val * 0xFFFF) / adcMaximum) } return 0 } // mode constructs bit masks for mode and average - used in ADC configuration // registers - from a given ADC bit size (resolution) and sample count. func (a ADC) mode(resolution, samples uint32) (mode, average uint32) { // use asynchronous clock (ADACK) (0 = IPG, 1 = IPG/2, or 3 = ADACK) mode = (nxp.ADC_CFG_ADICLK_ADICLK_3 << nxp.ADC_CFG_ADICLK_Pos) & nxp.ADC_CFG_ADICLK_Msk // input clock DIV2 (0 = DIV1, 1 = DIV2, 2 = DIV4, or 3 = DIV8) mode |= (nxp.ADC_CFG_ADIV_ADIV_1 << nxp.ADC_CFG_ADIV_Pos) & nxp.ADC_CFG_ADIV_Msk switch resolution { case 8: // 8-bit conversion, sample period (ADC clocks) = 8 mode |= (nxp.ADC_CFG_MODE_MODE_0 << nxp.ADC_CFG_MODE_Pos) & nxp.ADC_CFG_MODE_Msk mode |= (nxp.ADC_CFG_ADSTS_ADSTS_3 << nxp.ADC_CFG_ADSTS_Pos) & nxp.ADC_CFG_ADSTS_Msk case 12: // 12-bit conversion, sample period (ADC clocks) = 24 mode |= (nxp.ADC_CFG_MODE_MODE_2 << nxp.ADC_CFG_MODE_Pos) & nxp.ADC_CFG_MODE_Msk mode |= (nxp.ADC_CFG_ADSTS_ADSTS_3 << nxp.ADC_CFG_ADSTS_Pos) & nxp.ADC_CFG_ADSTS_Msk mode |= nxp.ADC_CFG_ADLSMP default: // 10-bit conversion, sample period (ADC clocks) = 20 mode |= (nxp.ADC_CFG_MODE_MODE_1 << nxp.ADC_CFG_MODE_Pos) & nxp.ADC_CFG_MODE_Msk mode |= (nxp.ADC_CFG_ADSTS_ADSTS_2 << nxp.ADC_CFG_ADSTS_Pos) & nxp.ADC_CFG_ADSTS_Msk mode |= nxp.ADC_CFG_ADLSMP } if samples >= 4 { if samples >= 32 { // 32 samples averaged mode |= (nxp.ADC_CFG_AVGS_AVGS_3 << nxp.ADC_CFG_AVGS_Pos) & nxp.ADC_CFG_AVGS_Msk } else if samples >= 16 { // 16 samples averaged mode |= (nxp.ADC_CFG_AVGS_AVGS_2 << nxp.ADC_CFG_AVGS_Pos) & nxp.ADC_CFG_AVGS_Msk } else if samples >= 8 { // 8 samples averaged mode |= (nxp.ADC_CFG_AVGS_AVGS_1 << nxp.ADC_CFG_AVGS_Pos) & nxp.ADC_CFG_AVGS_Msk } else { // 4 samples averaged mode |= (nxp.ADC_CFG_AVGS_AVGS_0 << nxp.ADC_CFG_AVGS_Pos) & nxp.ADC_CFG_AVGS_Msk } average = nxp.ADC_GC_AVGE } return mode, average } // isCalibrating returns true if and only if either one (or both) of ADC1 and // ADC2 have their calibrating flags set. ADC reads must wait until these flags // are clear before attempting a conversion. func (a ADC) isCalibrating() bool { return nxp.ADC1.GC.HasBits(nxp.ADC_GC_CAL) || nxp.ADC2.GC.HasBits(nxp.ADC_GC_CAL) } const noADCChannel = uint8(0xFF) // getADCChannel returns the input channel for ADC1/ADC2 of the receiver Pin p. func (p Pin) getADCChannel() (adc1, adc2 uint8, ok bool) { switch p { case PA12: // [AD_B0_12]: ADC1_IN1 ~ return 1, noADCChannel, true case PA13: // [AD_B0_13]: ADC1_IN2 ~ return 2, noADCChannel, true case PA14: // [AD_B0_14]: ADC1_IN3 ~ return 3, noADCChannel, true case PA15: // [AD_B0_15]: ADC1_IN4 ~ return 4, noADCChannel, true case PA16: // [AD_B1_00]: ADC1_IN5 ADC2_IN5 return 5, 5, true case PA17: // [AD_B1_01]: ADC1_IN6 ADC2_IN6 return 6, 6, true case PA18: // [AD_B1_02]: ADC1_IN7 ADC2_IN7 return 7, 7, true case PA19: // [AD_B1_03]: ADC1_IN8 ADC2_IN8 return 8, 8, true case PA20: // [AD_B1_04]: ADC1_IN9 ADC2_IN9 return 9, 9, true case PA21: // [AD_B1_05]: ADC1_IN10 ADC2_IN10 return 10, 10, true case PA22: // [AD_B1_06]: ADC1_IN11 ADC2_IN11 return 11, 11, true case PA23: // [AD_B1_07]: ADC1_IN12 ADC2_IN12 return 12, 12, true case PA24: // [AD_B1_08]: ADC1_IN13 ADC2_IN13 return 13, 13, true case PA25: // [AD_B1_09]: ADC1_IN14 ADC2_IN14 return 14, 14, true case PA26: // [AD_B1_10]: ADC1_IN15 ADC2_IN15 return 15, 15, true case PA27: // [AD_B1_11]: ADC1_IN0 ADC2_IN0 return 16, 16, true case PA28: // [AD_B1_12]: ~ ADC2_IN1 return noADCChannel, 1, true case PA29: // [AD_B1_13]: ~ ADC2_IN2 return noADCChannel, 2, true case PA30: // [AD_B1_14]: ~ ADC2_IN3 return noADCChannel, 3, true case PA31: // [AD_B1_15]: ~ ADC2_IN4 return noADCChannel, 4, true default: return noADCChannel, noADCChannel, false } } ================================================ FILE: src/machine/machine_mimxrt1062_i2c.go ================================================ //go:build mimxrt1062 package machine // I2C peripheral abstraction layer for the MIMXRT1062 import ( "device/nxp" ) // I2CConfig is used to store config info for I2C. type I2CConfig struct { Frequency uint32 SDA Pin SCL Pin } type I2C struct { Bus *nxp.LPI2C_Type // these pins are initialized by each global I2C variable declared in the // board_teensy4x.go file according to the board manufacturer's default pin // mapping. they can be overridden with the I2CConfig argument given to // (*I2C) Configure(I2CConfig). sda, scl Pin // these hold the input selector ("daisy chain") values that select which pins // are connected to the LPI2C device, and should be defined where the I2C // instance is declared (e.g., in the board definition). see the godoc // comments on type muxSelect for more details. muxSDA, muxSCL muxSelect } type i2cDirection bool const ( directionWrite i2cDirection = false directionRead i2cDirection = true ) func (dir i2cDirection) shift(addr uint16) uint32 { if addr <<= 1; dir == directionRead { addr |= 1 } return uint32(addr) & 0xFF } // I2C enumerated types type ( resultFlag uint32 statusFlag uint32 transferFlag uint32 commandFlag uint32 stateFlag uint32 ) const ( // general purpose results resultSuccess resultFlag = 0x0 // success resultFail resultFlag = 0x1 // fail resultReadOnly resultFlag = 0x2 // read only failure resultOutOfRange resultFlag = 0x3 // out of range access resultInvalidArgument resultFlag = 0x4 // invalid argument check // I2C-specific results resultBusy resultFlag = 0x0384 + 0x0 // the controller is already performing a transfer resultIdle resultFlag = 0x0384 + 0x1 // the peripheral driver is idle resultNak resultFlag = 0x0384 + 0x2 // the peripheral device sent a NAK in response to a byte resultFifoError resultFlag = 0x0384 + 0x3 // FIFO under run or overrun resultBitError resultFlag = 0x0384 + 0x4 // transferred bit was not seen on the bus resultArbitrationLost resultFlag = 0x0384 + 0x5 // arbitration lost error resultPinLowTimeout resultFlag = 0x0384 + 0x6 // SCL or SDA were held low longer than the timeout resultNoTransferInProgress resultFlag = 0x0384 + 0x7 // attempt to abort a transfer when one is not in progress resultDmaRequestFail resultFlag = 0x0384 + 0x8 // DMA request failed resultTimeout resultFlag = 0x0384 + 0x9 // timeout polling status flags ) const ( statusTxReady statusFlag = nxp.LPI2C_MSR_TDF // transmit data flag statusRxReady statusFlag = nxp.LPI2C_MSR_RDF // receive data flag statusEndOfPacket statusFlag = nxp.LPI2C_MSR_EPF // end Packet flag statusStopDetect statusFlag = nxp.LPI2C_MSR_SDF // stop detect flag statusNackDetect statusFlag = nxp.LPI2C_MSR_NDF // NACK detect flag statusArbitrationLost statusFlag = nxp.LPI2C_MSR_ALF // arbitration lost flag statusFifoErr statusFlag = nxp.LPI2C_MSR_FEF // FIFO error flag statusPinLowTimeout statusFlag = nxp.LPI2C_MSR_PLTF // pin low timeout flag statusI2CDataMatch statusFlag = nxp.LPI2C_MSR_DMF // data match flag statusBusy statusFlag = nxp.LPI2C_MSR_MBF // busy flag statusBusBusy statusFlag = nxp.LPI2C_MSR_BBF // bus busy flag // all flags which are cleared by the driver upon starting a transfer statusClear statusFlag = statusEndOfPacket | statusStopDetect | statusNackDetect | statusArbitrationLost | statusFifoErr | statusPinLowTimeout | statusI2CDataMatch // IRQ sources enabled by the non-blocking transactional API statusIrq statusFlag = statusArbitrationLost | statusTxReady | statusRxReady | statusStopDetect | statusNackDetect | statusPinLowTimeout | statusFifoErr // errors to check for statusError statusFlag = statusNackDetect | statusArbitrationLost | statusFifoErr | statusPinLowTimeout ) // LPI2C transfer modes const ( transferDefault transferFlag = 0x0 // transfer starts with a start signal, stops with a stop signal transferNoStart transferFlag = 0x1 // don't send a start condition, address, and sub address transferRepeatedStart transferFlag = 0x2 // send a repeated start condition transferNoStop transferFlag = 0x4 // don't send a stop condition ) // LPI2C FIFO commands const ( commandTxData commandFlag = (0x0 << nxp.LPI2C_MTDR_CMD_Pos) & nxp.LPI2C_MTDR_CMD_Msk // transmit commandRxData commandFlag = (0x1 << nxp.LPI2C_MTDR_CMD_Pos) & nxp.LPI2C_MTDR_CMD_Msk // receive commandStop commandFlag = (0x2 << nxp.LPI2C_MTDR_CMD_Pos) & nxp.LPI2C_MTDR_CMD_Msk // generate STOP condition commandStart commandFlag = (0x4 << nxp.LPI2C_MTDR_CMD_Pos) & nxp.LPI2C_MTDR_CMD_Msk // generate (REPEATED)START and transmit ) // LPI2C transactional states const ( stateIdle stateFlag = 0x0 stateSendCommand stateFlag = 0x1 stateIssueReadCommand stateFlag = 0x2 stateTransferData stateFlag = 0x3 stateStop stateFlag = 0x4 stateWaitForCompletion stateFlag = 0x5 ) func (i2c *I2C) setPins(c I2CConfig) (sda, scl Pin) { // if both given pins are defined, or either receiver pin is undefined. if 0 != c.SDA && 0 != c.SCL || 0 == i2c.sda || 0 == i2c.scl { // override the receiver's pins. i2c.sda, i2c.scl = c.SDA, c.SCL } // return the selected pins. return i2c.sda, i2c.scl } // Configure is intended to setup an I2C interface for transmit/receive. func (i2c *I2C) Configure(config I2CConfig) error { // init pins sda, scl := i2c.setPins(config) // configure the mux and pad control registers sda.Configure(PinConfig{Mode: PinModeI2CSDA}) scl.Configure(PinConfig{Mode: PinModeI2CSCL}) // configure the mux input selector i2c.muxSDA.connect() i2c.muxSCL.connect() freq := config.Frequency if 0 == freq { freq = 100 * KHz } // reset clock and registers, and enable LPI2C module interface i2c.reset(freq) return nil } // SetBaudRate sets the communication speed for I2C. func (i2c I2C) SetBaudRate(br uint32) error { // TODO: implement return errI2CNotImplemented } func (i2c I2C) Tx(addr uint16, w, r []byte) error { // perform transmit transfer if nil != w { // generate start condition on bus if result := i2c.start(addr, directionWrite); resultSuccess != result { return errI2CSignalStartTimeout } // ensure TX FIFO is empty if result := i2c.waitForTxEmpty(); resultSuccess != result { return errI2CBusReadyTimeout } // check if communication was successful if status := statusFlag(i2c.Bus.MSR.Get()); 0 != (status & statusNackDetect) { return errI2CAckExpected } // send transmit data if result := i2c.controllerTransmit(w); resultSuccess != result { return errI2CWriteTimeout } } // perform receive transfer if nil != r { // generate (repeated-)start condition on bus if result := i2c.start(addr, directionRead); resultSuccess != result { return errI2CSignalStartTimeout } // read received data if result := i2c.controllerReceive(r); resultSuccess != result { return errI2CReadTimeout } } // generate stop condition on bus if result := i2c.stop(); resultSuccess != result { return errI2CSignalStopTimeout } return nil } // WriteRegisterEx transmits first the register and then the data to the // peripheral device. // // Many I2C-compatible devices are organized in terms of registers. This method // is a shortcut to easily write to such registers. Also, it only works for // devices with 7-bit addresses, which is the vast majority. func (i2c I2C) WriteRegisterEx(address uint8, register uint8, data []byte) error { option := transferOption{ flags: transferDefault, // transfer options bit mask (0 = normal transfer) peripheral: uint16(address), // 7-bit peripheral address direction: directionWrite, // directionRead or directionWrite subaddress: uint16(register), // peripheral sub-address (transferred MSB first) subaddressSize: 1, // byte length of sub-address (maximum = 4 bytes) } if result := i2c.controllerTransferPoll(option, data); resultSuccess != result { return errI2CWriteTimeout } return nil } // ReadRegisterEx transmits the register, restarts the connection as a read // operation, and reads the response. // // Many I2C-compatible devices are organized in terms of registers. This method // is a shortcut to easily read such registers. Also, it only works for devices // with 7-bit addresses, which is the vast majority. func (i2c I2C) ReadRegisterEx(address uint8, register uint8, data []byte) error { option := transferOption{ flags: transferDefault, // transfer options bit mask (0 = normal transfer) peripheral: uint16(address), // 7-bit peripheral address direction: directionRead, // directionRead or directionWrite subaddress: uint16(register), // peripheral sub-address (transferred MSB first) subaddressSize: 1, // byte length of sub-address (maximum = 4 bytes) } if result := i2c.controllerTransferPoll(option, data); resultSuccess != result { return errI2CWriteTimeout } return nil } func (i2c *I2C) reset(freq uint32) { // disable interface i2c.Bus.MCR.ClearBits(nxp.LPI2C_MCR_MEN) // software reset all interface registers i2c.Bus.MCR.Set(nxp.LPI2C_MCR_RST) // RST remains set until manually cleared! i2c.Bus.MCR.ClearBits(nxp.LPI2C_MCR_RST) // disable host request i2c.Bus.MCFGR0.Set(0) // enable ACK, use I2C 2-pin open drain mode i2c.Bus.MCFGR1.Set(0) // set FIFO watermarks (RX=1, TX=1) mfcr := (uint32(0x1) << nxp.LPI2C_MFCR_RXWATER_Pos) & nxp.LPI2C_MFCR_RXWATER_Msk mfcr |= (uint32(0x1) << nxp.LPI2C_MFCR_TXWATER_Pos) & nxp.LPI2C_MFCR_TXWATER_Msk i2c.Bus.MFCR.Set(mfcr) // configure clock using receiver frequency i2c.setFrequency(freq) // clear reset, and enable the interface i2c.Bus.MCR.Set(nxp.LPI2C_MCR_MEN) // wait for the I2C bus to idle for i2c.Bus.MSR.Get()&nxp.LPI2C_MSR_BBF != 0 { } } func (i2c *I2C) setFrequency(freq uint32) { var ( bestPre uint32 = 0 bestClkHi uint32 = 0 bestError uint32 = 0xFFFFFFFF ) // disable interface wasEnabled := i2c.Bus.MCR.HasBits(nxp.LPI2C_MCR_MEN) i2c.Bus.MCR.ClearBits(nxp.LPI2C_MCR_MEN) // baud rate = (24MHz/(2^pre))/(CLKLO+1 + CLKHI+1 + FLOOR((2+FILTSCL)/(2^pre))) // assume: CLKLO=2*CLKHI, SETHOLD=CLKHI, DATAVD=CLKHI/2 for pre := uint32(1); pre <= 128; pre *= 2 { if bestError == 0 { break } for clkHi := uint32(1); clkHi < 32; clkHi++ { var absError, rate uint32 if clkHi == 1 { rate = (24 * MHz / pre) / (1 + 3 + 2 + 2/pre) } else { rate = (24 * MHz / pre) / (3*clkHi + 2 + 2/pre) } if freq > rate { absError = freq - rate } else { absError = rate - freq } if absError < bestError { bestPre = pre bestClkHi = clkHi bestError = absError // if the error is 0, then we can stop searching because we won't find a // better match if absError == 0 { break } } } } var ( clklo = func(n uint32) uint32 { return (n << nxp.LPI2C_MCCR0_CLKLO_Pos) & nxp.LPI2C_MCCR0_CLKLO_Msk } clkhi = func(n uint32) uint32 { return (n << nxp.LPI2C_MCCR0_CLKHI_Pos) & nxp.LPI2C_MCCR0_CLKHI_Msk } datavd = func(n uint32) uint32 { return (n << nxp.LPI2C_MCCR0_DATAVD_Pos) & nxp.LPI2C_MCCR0_DATAVD_Msk } sethold = func(n uint32) uint32 { return (n << nxp.LPI2C_MCCR0_SETHOLD_Pos) & nxp.LPI2C_MCCR0_SETHOLD_Msk } ) // StandardMode, FastMode, FastModePlus, and UltraFastMode mccr0 := clkhi(bestClkHi) if bestClkHi < 2 { mccr0 |= (clklo(3) | sethold(2) | datavd(1)) } else { mccr0 |= clklo(2*bestClkHi) | sethold(bestClkHi) | datavd(bestClkHi/2) } i2c.Bus.MCCR0.Set(mccr0) i2c.Bus.MCCR1.Set(i2c.Bus.MCCR0.Get()) for i := uint32(0); i < 8; i++ { if bestPre == (1 << i) { bestPre = i break } } preMask := (bestPre << nxp.LPI2C_MCFGR1_PRESCALE_Pos) & nxp.LPI2C_MCFGR1_PRESCALE_Msk i2c.Bus.MCFGR1.Set((i2c.Bus.MCFGR1.Get() & ^uint32(nxp.LPI2C_MCFGR1_PRESCALE_Msk)) | preMask) var ( filtsda = func(n uint32) uint32 { return (n << nxp.LPI2C_MCFGR2_FILTSDA_Pos) & nxp.LPI2C_MCFGR2_FILTSDA_Msk } filtscl = func(n uint32) uint32 { return (n << nxp.LPI2C_MCFGR2_FILTSCL_Pos) & nxp.LPI2C_MCFGR2_FILTSCL_Msk } busidle = func(n uint32) uint32 { return (n << nxp.LPI2C_MCFGR2_BUSIDLE_Pos) & nxp.LPI2C_MCFGR2_BUSIDLE_Msk } pinlow = func(n uint32) uint32 { return (n << nxp.LPI2C_MCFGR3_PINLOW_Pos) & nxp.LPI2C_MCFGR3_PINLOW_Msk } mcfgr2, mcfgr3 uint32 ) const i2cClockStretchTimeout = 15000 // microseconds if freq >= 5*MHz { // I2C UltraFastMode 5 MHz mcfgr2 = 0 // disable glitch filters and timeout for UltraFastMode mcfgr3 = 0 // } else if freq >= 1*MHz { // I2C FastModePlus 1 MHz mcfgr2 = filtsda(1) | filtscl(1) | busidle(2400) // 100us timeout mcfgr3 = pinlow(i2cClockStretchTimeout*24/256 + 1) } else if freq >= 400*KHz { // I2C FastMode 400 kHz mcfgr2 = filtsda(2) | filtscl(2) | busidle(3600) // 150us timeout mcfgr3 = pinlow(i2cClockStretchTimeout*24/256 + 1) } else { // I2C StandardMode 100 kHz mcfgr2 = filtsda(5) | filtscl(5) | busidle(3000) // 250us timeout mcfgr3 = pinlow(i2cClockStretchTimeout*12/256 + 1) } i2c.Bus.MCFGR2.Set(mcfgr2) i2c.Bus.MCFGR3.Set(mcfgr3) // restore controller mode if it was enabled when called if wasEnabled { i2c.Bus.MCR.SetBits(nxp.LPI2C_MCR_MEN) } } // checkStatus converts the status register to a resultFlag for return, and // clears any errors if present. func (i2c *I2C) checkStatus(status statusFlag) resultFlag { result := resultSuccess // check for error. these errors cause a stop to be sent automatically. // we must clear the errors before a new transfer can start. if status &= statusError; 0 != status { // select the correct error code ordered by severity, bus issues first. if 0 != (status & statusPinLowTimeout) { result = resultPinLowTimeout } else if 0 != (status & statusArbitrationLost) { result = resultArbitrationLost } else if 0 != (status & statusNackDetect) { result = resultNak } else if 0 != (status & statusFifoErr) { result = resultFifoError } // clear the flags i2c.Bus.MSR.Set(uint32(status)) // reset fifos. these flags clear automatically. i2c.Bus.MCR.SetBits(nxp.LPI2C_MCR_RRF | nxp.LPI2C_MCR_RTF) } return result } func (i2c *I2C) getFIFOSize() (rx, tx uint32) { return 4, 4 } func (i2c *I2C) getFIFOCount() (rx, tx uint32) { mfsr := i2c.Bus.MFSR.Get() return (mfsr & nxp.LPI2C_MFSR_RXCOUNT_Msk) >> nxp.LPI2C_MFSR_RXCOUNT_Pos, (mfsr & nxp.LPI2C_MFSR_TXCOUNT_Msk) >> nxp.LPI2C_MFSR_TXCOUNT_Pos } func (i2c *I2C) waitForTxReady() resultFlag { result := resultSuccess _, txSize := i2c.getFIFOSize() for { _, txCount := i2c.getFIFOCount() status := statusFlag(i2c.Bus.MSR.Get()) if result = i2c.checkStatus(status); resultSuccess != result { break } if txSize-txCount > 0 { break } } return result } func (i2c *I2C) waitForTxEmpty() resultFlag { result := resultSuccess for { _, txCount := i2c.getFIFOCount() status := statusFlag(i2c.Bus.MSR.Get()) if result = i2c.checkStatus(status); resultSuccess != result { break } if 0 == txCount { break } } return result } // isBusBusy checks if the I2C bus is busy, returning true if it is busy and we // are not the ones driving it, otherwise false. func (i2c *I2C) isBusBusy() bool { status := statusFlag(i2c.Bus.MSR.Get()) return (0 != (status & statusBusBusy)) && (0 == (status & statusBusy)) } // start sends a START signal and peripheral address on the I2C bus. // // This function is used to initiate a new controller mode transfer. First, the // bus state is checked to ensure that another controller is not occupying the // bus. Then a START signal is transmitted, followed by the 7-bit peripheral // address. Note that this function does not actually wait until the START and // address are successfully sent on the bus before returning. func (i2c *I2C) start(address uint16, dir i2cDirection) resultFlag { // return an error if the bus is already in use by another controller if i2c.isBusBusy() { return resultBusy } // clear all flags i2c.Bus.MSR.Set(uint32(statusClear)) // turn off auto-stop i2c.Bus.MCFGR1.ClearBits(nxp.LPI2C_MCFGR1_AUTOSTOP) // wait until there is room in the FIFO if result := i2c.waitForTxReady(); resultSuccess != result { return result } // issue start command i2c.Bus.MTDR.Set(uint32(commandStart) | dir.shift(address)) return resultSuccess } // stop sends a STOP signal on the I2C bus. // // This function does not return until the STOP signal is seen on the bus, or // an error occurs. func (i2c *I2C) stop() resultFlag { const tryMax = 0 // keep waiting forever // wait until there is room in the FIFO result := i2c.waitForTxReady() if resultSuccess != result { return result } // send the STOP signal i2c.Bus.MTDR.Set(uint32(commandStop)) // wait for the stop detected flag to set, indicating the transfer has // completed on the bus. also check for errors while waiting. try := 0 for resultSuccess == result && (0 == tryMax || try < tryMax) { status := statusFlag(i2c.Bus.MSR.Get()) result = i2c.checkStatus(status) if (0 != (status & statusStopDetect)) && (0 != (status & statusTxReady)) { i2c.Bus.MSR.Set(uint32(statusStopDetect)) break } try++ } if 0 != tryMax && try >= tryMax { return resultTimeout } return result } // controllerReceive performs a polling receive transfer on the I2C bus. func (i2c *I2C) controllerReceive(rxBuffer []byte) resultFlag { const tryMax = 0 // keep trying forever rxSize := len(rxBuffer) if rxSize == 0 { return resultSuccess } // wait until there is room in the FIFO result := i2c.waitForTxReady() if resultSuccess != result { return result } sizeMask := (uint32(rxSize-1) << nxp.LPI2C_MTDR_DATA_Pos) & nxp.LPI2C_MTDR_DATA_Msk i2c.Bus.MTDR.Set(uint32(commandRxData) | sizeMask) // receive data for rxSize > 0 { // read LPI2C receive FIFO register. the register includes a flag to // indicate whether the FIFO is empty, so we can both get the data and check // if we need to keep reading using a single register read. var data uint32 try := 0 for 0 == tryMax || try < tryMax { // check for errors on the bus status := statusFlag(i2c.Bus.MSR.Get()) result = i2c.checkStatus(status) if resultSuccess != result { return result } // read received data, break if FIFO was non-empty data = i2c.Bus.MRDR.Get() if 0 == (data & nxp.LPI2C_MRDR_RXEMPTY_Msk) { break } try++ } // ensure we didn't timeout waiting for data if 0 != tryMax && try >= tryMax { return resultTimeout } // copy data to RX buffer rxBuffer[len(rxBuffer)-rxSize] = byte(data & nxp.LPI2C_MRDR_DATA_Msk) rxSize-- } return result } // controllerTransmit performs a polling transmit transfer on the I2C bus. func (i2c *I2C) controllerTransmit(txBuffer []byte) resultFlag { txSize := len(txBuffer) for txSize > 0 { // wait until there is room in the FIFO result := i2c.waitForTxReady() if resultSuccess != result { return result } // write byte into LPI2C data register i2c.Bus.MTDR.Set(uint32(txBuffer[len(txBuffer)-txSize] & nxp.LPI2C_MTDR_DATA_Msk)) txSize-- } return resultSuccess } type transferOption struct { flags transferFlag // transfer options bit mask (0 = normal transfer) peripheral uint16 // 7-bit peripheral address direction i2cDirection // directionRead or directionWrite subaddress uint16 // peripheral sub-address (transferred MSB first) subaddressSize uint16 // byte length of sub-address (maximum = 4 bytes) } func (i2c *I2C) controllerTransferPoll(option transferOption, data []byte) resultFlag { // return an error if the bus is already in use by another controller if i2c.isBusBusy() { return resultBusy } // clear all flags i2c.Bus.MSR.Set(uint32(statusClear)) // turn off auto-stop i2c.Bus.MCFGR1.ClearBits(nxp.LPI2C_MCFGR1_AUTOSTOP) cmd := make([]uint16, 0, 7) size := len(data) direction := option.direction if option.subaddressSize > 0 { direction = directionWrite } // peripheral address if 0 == (option.flags & transferNoStart) { addr := direction.shift(option.peripheral) cmd = append(cmd, uint16(uint32(commandStart)|addr)) } // sub-address (MSB-first) rem := option.subaddressSize for rem > 0 { rem-- cmd = append(cmd, (option.subaddress>>(8*rem))&0xFF) } // need to send repeated start if switching directions to read if (0 != size) && (directionRead == option.direction) { if directionWrite == direction { addr := directionRead.shift(option.peripheral) cmd = append(cmd, uint16(uint32(commandStart)|addr)) } } // send command buffer result := resultSuccess for _, c := range cmd { // wait until there is room in the FIFO if result = i2c.waitForTxReady(); resultSuccess != result { return result } // write byte into LPI2C controller data register i2c.Bus.MTDR.Set(uint32(c)) } // send data if option.direction == directionWrite && size > 0 { result = i2c.controllerTransmit(data) } // receive data if option.direction == directionRead && size > 0 { result = i2c.controllerReceive(data) } if resultSuccess != result { return result } if 0 == (option.flags & transferNoStop) { result = i2c.stop() } return result } ================================================ FILE: src/machine/machine_mimxrt1062_spi.go ================================================ //go:build mimxrt1062 package machine // SPI peripheral abstraction layer for the MIMXRT1062 import ( "device/nxp" "errors" "unsafe" ) // SPIConfig is used to store config info for SPI. type SPIConfig struct { Frequency uint32 SDI Pin SDO Pin SCK Pin CS Pin LSBFirst bool Mode uint8 } func (c SPIConfig) getPins() (di, do, ck, cs Pin) { if 0 == c.SDI && 0 == c.SDO && 0 == c.SCK && 0 == c.CS { // default pins if none specified return SPI_SDI_PIN, SPI_SDO_PIN, SPI_SCK_PIN, SPI_CS_PIN } return c.SDI, c.SDO, c.SCK, c.CS } type SPI struct { Bus *nxp.LPSPI_Type // these hold the input selector ("daisy chain") values that select which pins // are connected to the LPSPI device, and should be defined where the SPI // instance is declared (e.g., in the board definition). see the godoc // comments on type muxSelect for more details. muxSDI, muxSDO, muxSCK, muxCS muxSelect // these are copied from SPIConfig, during (*SPI).Configure(SPIConfig), and // should be considered read-only for internal reference (i.e., modifying them // will have no desirable effect). sdi, sdo, sck, cs Pin frequency uint32 // auxiliary state data used internally configured bool } const ( statusTxDataRequest = nxp.LPSPI_SR_TDF // Transmit data flag statusRxDataReady = nxp.LPSPI_SR_RDF // Receive data flag statusWordComplete = nxp.LPSPI_SR_WCF // Word Complete flag statusFrameComplete = nxp.LPSPI_SR_FCF // Frame Complete flag statusTransferComplete = nxp.LPSPI_SR_TCF // Transfer Complete flag statusTransmitError = nxp.LPSPI_SR_TEF // Transmit Error flag (FIFO underrun) statusReceiveError = nxp.LPSPI_SR_REF // Receive Error flag (FIFO overrun) statusDataMatch = nxp.LPSPI_SR_DMF // Data Match flag statusModuleBusy = nxp.LPSPI_SR_MBF // Module Busy flag statusAll = nxp.LPSPI_SR_TDF | nxp.LPSPI_SR_RDF | nxp.LPSPI_SR_WCF | nxp.LPSPI_SR_FCF | nxp.LPSPI_SR_TCF | nxp.LPSPI_SR_TEF | nxp.LPSPI_SR_REF | nxp.LPSPI_SR_DMF | nxp.LPSPI_SR_MBF ) var ( errSPINotConfigured = errors.New("SPI interface is not yet configured") ) // Configure is intended to setup an SPI interface for transmit/receive. func (spi *SPI) Configure(config SPIConfig) error { const defaultSpiFreq = 4000000 // 4 MHz // init pins spi.sdi, spi.sdo, spi.sck, spi.cs = config.getPins() // configure the mux and pad control registers spi.sdi.Configure(PinConfig{Mode: PinModeSPISDI}) spi.sdo.Configure(PinConfig{Mode: PinModeSPISDO}) spi.sck.Configure(PinConfig{Mode: PinModeSPICLK}) spi.cs.Configure(PinConfig{Mode: PinModeSPICS}) // configure the mux input selector spi.muxSDI.connect() spi.muxSDO.connect() spi.muxSCK.connect() spi.muxCS.connect() // software reset of LPSPI state registers spi.Bus.CR.SetBits(nxp.LPSPI_CR_RST) // also reset FIFOs (not performed by software reset above) spi.Bus.CR.SetBits(nxp.LPSPI_CR_RRF | nxp.LPSPI_CR_RTF) spi.Bus.CR.Set(0) // set controller mode, and input data is sampled on delayed SCK edge spi.Bus.CFGR1.Set(nxp.LPSPI_CFGR1_MASTER | nxp.LPSPI_CFGR1_SAMPLE) spi.frequency = config.Frequency if 0 == spi.frequency { spi.frequency = defaultSpiFreq } // configure LPSPI clock divisor and CS assertion delays div := spi.getClockDivisor(config.Frequency) ccr := (div << nxp.LPSPI_CCR_SCKDIV_Pos) & nxp.LPSPI_CCR_SCKDIV_Msk ccr |= ((div / 2) << nxp.LPSPI_CCR_DBT_Pos) & nxp.LPSPI_CCR_DBT_Msk ccr |= ((div / 2) << nxp.LPSPI_CCR_PCSSCK_Pos) & nxp.LPSPI_CCR_PCSSCK_Msk spi.Bus.CCR.Set(ccr) // 8-bit frame size (words) tcr := uint32(7) if config.LSBFirst { tcr |= nxp.LPSPI_TCR_LSBF } // set polarity and phase switch config.Mode { case Mode1: tcr |= nxp.LPSPI_TCR_CPHA case Mode2: tcr |= nxp.LPSPI_TCR_CPOL case Mode3: tcr |= nxp.LPSPI_TCR_CPOL tcr |= nxp.LPSPI_TCR_CPHA } spi.Bus.TCR.Set(tcr) // clear FIFO water marks spi.setWatermark(0, 0) // enable LPSPI module spi.Bus.CR.Set(nxp.LPSPI_CR_MEN) spi.configured = true return nil } // Transfer writes/reads a single byte using the SPI interface. func (spi *SPI) Transfer(w byte) (byte, error) { if !spi.configured { return 0, errSPINotConfigured } const readTryMax = 10000 for spi.Bus.SR.HasBits(statusModuleBusy) { } // wait for SPI busy bit to clear _, txFIFOSize := spi.getFIFOSize() spi.flushFIFO(true, true) spi.Bus.SR.Set(statusAll) // clear all status flags (W1C) // enable LPSPI module spi.Bus.CR.Set(nxp.LPSPI_CR_MEN) // TODO: unnecessary since we just flushed the FIFO? for { // wait for TX FIFO to not be full if _, txFIFO := spi.getFIFOCount(); txFIFO < txFIFOSize { break } } // write out byte to TX FIFO spi.Bus.TDR.Set(uint32(w)) // try to read from RX FIFO if anything exists didRead := false data := byte(0) for i := 0; !didRead && (i < readTryMax); i++ { rxFIFO, _ := spi.getFIFOCount() didRead = rxFIFO > 0 if didRead { data = byte(spi.Bus.RDR.Get()) } } // if nothing was read, then wait for transfer complete flag to decide when // we are finished if !didRead { for !spi.Bus.SR.HasBits(nxp.LPSPI_SR_TCF) { } // wait for all transfers complete flag to set } return data, nil } func (spi *SPI) isHardwareCSPin(pin Pin) bool { switch unsafe.Pointer(spi.Bus) { case unsafe.Pointer(nxp.LPSPI1): return SPI1_CS_PIN == pin case unsafe.Pointer(nxp.LPSPI2): return SPI2_CS_PIN == pin case unsafe.Pointer(nxp.LPSPI3): return SPI3_CS_PIN == pin } return false } func (spi *SPI) hasHardwareCSPin() bool { return spi.isHardwareCSPin(spi.cs) } // getClockDivisor finds the SPI prescalar that minimizes the error between // requested frequency and possible frequencies available with the LPSPI clock. // this routine is based on Teensyduino (libraries/SPI/SPI.cpp): // // void SPIClass::setClockDivider_noInline(uint32_t clk) func (spi *SPI) getClockDivisor(freq uint32) uint32 { const clock = 132000000 // LPSPI root clock frequency (PLL2) d := uint32(clock) if freq > 0 { d /= freq } if d > 0 && clock/d > freq { d++ } if d > 257 { return 255 } if d > 2 { return d - 2 } return 0 } func (spi *SPI) getFIFOSize() (rx, tx uint32) { param := spi.Bus.PARAM.Get() return uint32(1) << ((param & nxp.LPSPI_PARAM_RXFIFO_Msk) >> nxp.LPSPI_PARAM_RXFIFO_Pos), uint32(1) << ((param & nxp.LPSPI_PARAM_TXFIFO_Msk) >> nxp.LPSPI_PARAM_TXFIFO_Pos) } func (spi *SPI) getFIFOCount() (rx, tx uint32) { fsr := spi.Bus.FSR.Get() return (fsr & nxp.LPSPI_FSR_RXCOUNT_Msk) >> nxp.LPSPI_FSR_RXCOUNT_Pos, (fsr & nxp.LPSPI_FSR_TXCOUNT_Msk) >> nxp.LPSPI_FSR_TXCOUNT_Pos } func (spi *SPI) flushFIFO(rx, tx bool) { var flush uint32 if rx { flush |= nxp.LPSPI_CR_RRF } if tx { flush |= nxp.LPSPI_CR_RTF } spi.Bus.CR.SetBits(flush) } func (spi *SPI) setWatermark(rx, tx uint32) { spi.Bus.FCR.Set(((rx << nxp.LPSPI_FCR_RXWATER_Pos) & nxp.LPSPI_FCR_RXWATER_Msk) | ((tx << nxp.LPSPI_FCR_TXWATER_Pos) & nxp.LPSPI_FCR_TXWATER_Msk)) } ================================================ FILE: src/machine/machine_mimxrt1062_uart.go ================================================ //go:build mimxrt1062 package machine import ( "device/nxp" "runtime/interrupt" "runtime/volatile" ) // UART peripheral abstraction layer for the MIMXRT1062 type UART struct { Bus *nxp.LPUART_Type Buffer *RingBuffer Interrupt interrupt.Interrupt // txBuffer should be allocated globally (such as when UART is created) to // prevent it being reclaimed or cleaned up prematurely. txBuffer *RingBuffer // these hold the input selector ("daisy chain") values that select which pins // are connected to the LPUART device, and should be defined where the UART // instance is declared. see the godoc comments on type muxSelect for more // details. muxRX, muxTX muxSelect // these are copied from UARTConfig, during (*UART).Configure(UARTConfig), and // should be considered read-only for internal reference (i.e., modifying them // will have no desirable effect). rx, tx Pin baud uint32 // auxiliary state data used internally configured bool transmitting volatile.Register32 } func (uart *UART) isTransmitting() bool { return uart.transmitting.Get() != 0 } func (uart *UART) startTransmitting() { uart.transmitting.Set(1) } func (uart *UART) stopTransmitting() { uart.transmitting.Set(0) } func (uart *UART) resetTransmitting() { uart.stopTransmitting() uart.Bus.GLOBAL.SetBits(nxp.LPUART_GLOBAL_RST) uart.Bus.GLOBAL.ClearBits(nxp.LPUART_GLOBAL_RST) } // Configure initializes a UART with the given UARTConfig and other default // settings. func (uart *UART) Configure(config UARTConfig) { const defaultUartFreq = 115200 // use default baud rate if not specified if config.BaudRate == 0 { config.BaudRate = defaultUartFreq } // use default UART pins if not specified if config.RX == 0 && config.TX == 0 { config.RX = UART_RX_PIN config.TX = UART_TX_PIN } uart.baud = config.BaudRate uart.rx = config.RX uart.tx = config.TX // configure the mux and pad control registers uart.rx.Configure(PinConfig{Mode: PinModeUARTRX}) uart.tx.Configure(PinConfig{Mode: PinModeUARTTX}) // configure the mux input selector uart.muxRX.connect() uart.muxTX.connect() // reset all internal logic and registers uart.resetTransmitting() // disable until we have finished configuring registers uart.Bus.CTRL.Set(0) // determine the baud rate and over-sample divisors sbr, osr := uart.getBaudRateDivisor(uart.baud) // set the baud rate, over-sample configuration, stop bits baudBits := (((osr - 1) << nxp.LPUART_BAUD_OSR_Pos) & nxp.LPUART_BAUD_OSR_Msk) | ((sbr << nxp.LPUART_BAUD_SBR_Pos) & nxp.LPUART_BAUD_SBR_Msk) if osr <= 8 { // if OSR less than or equal to 8, we must enable sampling on both edges baudBits |= nxp.LPUART_BAUD_BOTHEDGE } uart.Bus.BAUD.Set(baudBits) uart.Bus.PINCFG.Set(0) // disable triggers // configure watermarks, flush and enable TX/RX FIFOs rxSize, txSize := uart.getFIFOSize() rxWater := rxSize >> 1 if rxWater > uint32(nxp.LPUART_FIFO_RXFIFOSIZE_Msk>>nxp.LPUART_FIFO_RXFIFOSIZE_Pos) { rxWater = uint32(nxp.LPUART_FIFO_RXFIFOSIZE_Msk >> nxp.LPUART_FIFO_RXFIFOSIZE_Pos) } txWater := txSize >> 1 if txWater > uint32(nxp.LPUART_FIFO_TXFIFOSIZE_Msk>>nxp.LPUART_FIFO_TXFIFOSIZE_Pos) { txWater = uint32(nxp.LPUART_FIFO_TXFIFOSIZE_Msk >> nxp.LPUART_FIFO_TXFIFOSIZE_Pos) } uart.Bus.WATER.Set( ((rxWater << nxp.LPUART_WATER_RXWATER_Pos) & nxp.LPUART_WATER_RXWATER_Msk) | ((txWater << nxp.LPUART_WATER_TXWATER_Pos) & nxp.LPUART_WATER_TXWATER_Msk)) uart.Bus.FIFO.SetBits(nxp.LPUART_FIFO_RXFE | nxp.LPUART_FIFO_TXFE | nxp.LPUART_FIFO_RXFLUSH | nxp.LPUART_FIFO_TXFLUSH) // for now we assume some configuration. in particular: // Data bits -> 8-bit // Parity bit -> None (parity bit generation disabled) // Stop bits -> 1 stop bit // MSB first -> false // RX idle type -> idle count starts after start bit // RX idle config -> 1 idle character // RX RTS enabled -> false // TX CTS enabled -> false // enable transmitter, receiver functions uart.Bus.CTRL.Set(nxp.LPUART_CTRL_TE | nxp.LPUART_CTRL_RE | // enable receiver, idle line interrupts nxp.LPUART_CTRL_RIE | nxp.LPUART_CTRL_ILIE) // clear all status flags uart.Bus.STAT.Set(uart.Bus.STAT.Get()) // enable RX interrupt uart.Interrupt.SetPriority(0xC0) uart.Interrupt.Enable() uart.configured = true } // Disable disables the UART interface. // // If any buffered data has not yet been transmitted, Disable waits until // transmission completes before disabling the interface. The receiver UART's // interrupt is also disabled, and the RX/TX pins are reconfigured for GPIO // input (pull-up). func (uart *UART) Disable() { // first ensure the device is enabled if uart.configured { // wait for any buffered data to send uart.Sync() // stop trapping RX interrupts uart.Interrupt.Disable() // reset all internal registers uart.resetTransmitting() // disable RX/TX functions uart.Bus.CTRL.ClearBits(nxp.LPUART_CTRL_TE | nxp.LPUART_CTRL_RE) // put pins back into GPIO mode uart.rx.Configure(PinConfig{Mode: PinInputPullup}) uart.tx.Configure(PinConfig{Mode: PinInputPullup}) } uart.configured = false } // Sync blocks the calling goroutine until all data in the output buffer has // been transmitted. func (uart *UART) Sync() error { for uart.isTransmitting() { } return nil } // WriteByte writes a single byte of data to the UART interface. func (uart *UART) writeByte(c byte) error { uart.startTransmitting() for !uart.txBuffer.Put(c) { } uart.Bus.CTRL.SetBits(nxp.LPUART_CTRL_TIE) return nil } func (uart *UART) flush() {} // getBaudRateDivisor finds the greatest over-sampling factor (4..32) and // corresponding baud rate divisor (1..8191) that best partition a given baud // rate into equal intervals. // // This is an integral (non-floating point) translation of the logic at the // beginning of: // // void HardwareSerial::begin(uint32_t baud, uint16_t format) // // (from Teensyduino: cores/teensy4/HardwareSerial.cpp) // // We don't want to use floating point here in case it gets called from an ISR // or very early during system init. func (uart *UART) getBaudRateDivisor(baudRate uint32) (sbr uint32, osr uint32) { const clock = 24000000 // UART is muxed to 24 MHz OSC err := uint32(0xFFFFFFFF) sbr, osr = 0, 0 for o := uint32(4); o <= 32; o++ { s := ((clock*10)/(baudRate*o) + 5) / 10 if s == 0 { s = 1 } b := clock / (s * o) var e uint32 if b > baudRate { e = b - baudRate } else { e = baudRate - b } if e <= err { err = e osr = o sbr = s } } return sbr, osr } func (uart *UART) getFIFOSize() (rx, tx uint32) { fifo := uart.Bus.FIFO.Get() rx = uint32(1) << ((fifo & nxp.LPUART_FIFO_RXFIFOSIZE_Msk) >> nxp.LPUART_FIFO_RXFIFOSIZE_Pos) if rx > 1 { rx <<= 1 } tx = uint32(1) << ((fifo & nxp.LPUART_FIFO_TXFIFOSIZE_Msk) >> nxp.LPUART_FIFO_TXFIFOSIZE_Pos) if tx > 1 { tx <<= 1 } return rx, tx } func (uart *UART) getStatus() uint32 { return uart.Bus.STAT.Get() | ((uart.Bus.FIFO.Get() & uint32(nxp.LPUART_FIFO_TXEMPT_Msk|nxp.LPUART_FIFO_RXEMPT_Msk| nxp.LPUART_FIFO_TXOF_Msk|nxp.LPUART_FIFO_RXUF_Msk)) >> 16) } func (uart *UART) getEnabledInterrupts() uint32 { return ((uart.Bus.BAUD.Get() & uint32(nxp.LPUART_BAUD_LBKDIE_Msk|nxp.LPUART_BAUD_RXEDGIE_Msk)) >> 8) | ((uart.Bus.FIFO.Get() & uint32(nxp.LPUART_FIFO_TXOFE_Msk|nxp.LPUART_FIFO_RXUFE_Msk)) >> 8) | (uart.Bus.CTRL.Get() & uint32(0xFF0C000)) } func (uart *UART) disableInterrupts(mask uint32) { uart.Bus.BAUD.ClearBits((mask << 8) & uint32(nxp.LPUART_BAUD_LBKDIE_Msk|nxp.LPUART_BAUD_RXEDGIE_Msk)) uart.Bus.FIFO.Set((uart.Bus.FIFO.Get() & ^uint32(nxp.LPUART_FIFO_TXOF_Msk|nxp.LPUART_FIFO_RXUF_Msk)) & ^uint32((mask<<8)&(nxp.LPUART_FIFO_TXOFE_Msk|nxp.LPUART_FIFO_RXUFE_Msk))) mask &= uint32(0xFFFFFF00) uart.Bus.CTRL.ClearBits(mask) } func (uart *UART) handleInterrupt(interrupt.Interrupt) { stat := uart.getStatus() inte := uart.getEnabledInterrupts() _, txSize := uart.getFIFOSize() // check for and clear overrun, otherwise RX will not work if (stat & uint32(nxp.LPUART_STAT_OR)) != 0 { uart.Bus.STAT.Set((uart.Bus.STAT.Get() & uint32(0x3FE00000)) | nxp.LPUART_STAT_OR) } // idle or receive data register is full if (stat & uint32(nxp.LPUART_STAT_RDRF|nxp.LPUART_STAT_IDLE)) != 0 { count := (uart.Bus.WATER.Get() & uint32(nxp.LPUART_WATER_RXCOUNT_Msk)) >> nxp.LPUART_WATER_RXCOUNT_Pos for ; count > 0; count-- { // read up to 8 bits of data at a time // TODO: 7, 9, and 10-bit support? uart.Buffer.Put(uint8(uart.Bus.DATA.Get() & uint32(0xFF))) } // if it was an IDLE status, clear the flag if (stat & uint32(nxp.LPUART_STAT_IDLE)) != 0 { uart.Bus.STAT.SetBits(nxp.LPUART_STAT_IDLE) } // disable idle line interrupts uart.disableInterrupts(nxp.LPUART_CTRL_RIE | nxp.LPUART_CTRL_ORIE) } // check if we have data to write if ((inte & nxp.LPUART_CTRL_TIE) != 0) && ((stat & nxp.LPUART_STAT_TDRE) != 0) { for ((uart.Bus.WATER.Get() & uint32(nxp.LPUART_WATER_TXCOUNT_Msk)) >> nxp.LPUART_WATER_TXCOUNT_Pos) < txSize { if b, ok := uart.txBuffer.Get(); ok { uart.Bus.DATA.Set(uint32(b)) } else { break } } if uart.Bus.STAT.HasBits(nxp.LPUART_STAT_TDRE) { uart.Bus.CTRL.Set((uart.Bus.CTRL.Get() & ^uint32(nxp.LPUART_CTRL_TIE)) | nxp.LPUART_CTRL_TCIE) } } if ((inte & nxp.LPUART_CTRL_TCIE) != 0) && ((stat & nxp.LPUART_STAT_TC) != 0) { uart.stopTransmitting() uart.Bus.CTRL.ClearBits(nxp.LPUART_CTRL_TCIE) } } ================================================ FILE: src/machine/machine_nrf.go ================================================ //go:build nrf package machine import ( "device/arm" "device/nrf" "errors" "internal/binary" "runtime/interrupt" "unsafe" ) const deviceName = nrf.Device var deviceID [8]byte // DeviceID returns an identifier that is unique within // a particular chipset. // // The identity is one burnt into the MCU itself, or the // flash chip at time of manufacture. // // It's possible that two different vendors may allocate // the same DeviceID, so callers should take this into // account if needing to generate a globally unique id. // // The length of the hardware ID is vendor-specific, but // 8 bytes (64 bits) is common. func DeviceID() []byte { words := make([]uint32, 2) words[0] = nrf.FICR.DEVICEID[0].Get() words[1] = nrf.FICR.DEVICEID[1].Get() for i := 0; i < 8; i++ { shift := (i % 4) * 8 w := i / 4 deviceID[i] = byte(words[w] >> shift) } return deviceID[:] } const ( PinInput PinMode = (nrf.GPIO_PIN_CNF_DIR_Input << nrf.GPIO_PIN_CNF_DIR_Pos) | (nrf.GPIO_PIN_CNF_INPUT_Connect << nrf.GPIO_PIN_CNF_INPUT_Pos) PinInputPullup PinMode = PinInput | (nrf.GPIO_PIN_CNF_PULL_Pullup << nrf.GPIO_PIN_CNF_PULL_Pos) PinInputPulldown PinMode = PinInput | (nrf.GPIO_PIN_CNF_PULL_Pulldown << nrf.GPIO_PIN_CNF_PULL_Pos) PinOutput PinMode = (nrf.GPIO_PIN_CNF_DIR_Output << nrf.GPIO_PIN_CNF_DIR_Pos) | (nrf.GPIO_PIN_CNF_INPUT_Connect << nrf.GPIO_PIN_CNF_INPUT_Pos) ) type PinChange uint8 // Pin change interrupt constants for SetInterrupt. const ( PinRising PinChange = nrf.GPIOTE_CONFIG_POLARITY_LoToHi PinFalling PinChange = nrf.GPIOTE_CONFIG_POLARITY_HiToLo PinToggle PinChange = nrf.GPIOTE_CONFIG_POLARITY_Toggle ) // Callbacks to be called for pins configured with SetInterrupt. var pinCallbacks [len(nrf.GPIOTE.CONFIG)]func(Pin) // Configure this pin with the given configuration. func (p Pin) Configure(config PinConfig) { cfg := config.Mode | nrf.GPIO_PIN_CNF_DRIVE_S0S1 | nrf.GPIO_PIN_CNF_SENSE_Disabled port, pin := p.getPortPin() port.PIN_CNF[pin].Set(uint32(cfg)) } // Set the pin to high or low. // Warning: only use this on an output pin! func (p Pin) Set(high bool) { port, pin := p.getPortPin() if high { port.OUTSET.Set(1 << pin) } else { port.OUTCLR.Set(1 << pin) } } // Return the register and mask to enable a given GPIO pin. This can be used to // implement bit-banged drivers. func (p Pin) PortMaskSet() (*uint32, uint32) { port, pin := p.getPortPin() return &port.OUTSET.Reg, 1 << pin } // Return the register and mask to disable a given port. This can be used to // implement bit-banged drivers. func (p Pin) PortMaskClear() (*uint32, uint32) { port, pin := p.getPortPin() return &port.OUTCLR.Reg, 1 << pin } // Get returns the current value of a GPIO pin when the pin is configured as an // input or as an output. func (p Pin) Get() bool { port, pin := p.getPortPin() return (port.IN.Get()>>pin)&1 != 0 } // SetInterrupt sets an interrupt to be executed when a particular pin changes // state. The pin should already be configured as an input, including a pull up // or down if no external pull is provided. // // This call will replace a previously set callback on this pin. You can pass a // nil func to unset the pin change interrupt. If you do so, the change // parameter is ignored and can be set to any value (such as 0). func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { // Some variables to easily check whether a channel was already configured // as an event channel for the given pin. // This is not just an optimization, this is required: the datasheet says // that configuring more than one channel for a given pin results in // unpredictable behavior. expectedConfigMask := uint32(nrf.GPIOTE_CONFIG_MODE_Msk | nrf.GPIOTE_CONFIG_PSEL_Msk) expectedConfig := nrf.GPIOTE_CONFIG_MODE_Event<> nrf.GPIOTE_CONFIG_PSEL_Pos) pinCallbacks[i](pin) } } }).Enable() // Everything was configured correctly. return nil } // UART on the NRF. type UART struct { Buffer *RingBuffer } // UART var ( // UART0 is the hardware UART on the NRF SoC. _UART0 = UART{Buffer: NewRingBuffer()} UART0 = &_UART0 ) // Configure the UART. func (uart *UART) Configure(config UARTConfig) { // Default baud rate to 115200. if config.BaudRate == 0 { config.BaudRate = 115200 } uart.SetBaudRate(config.BaudRate) // Set TX and RX pins if config.TX == 0 && config.RX == 0 { // Use default pins uart.setPins(UART_TX_PIN, UART_RX_PIN) } else { uart.setPins(config.TX, config.RX) } nrf.UART0.ENABLE.Set(nrf.UART_ENABLE_ENABLE_Enabled) nrf.UART0.TASKS_STARTTX.Set(1) nrf.UART0.TASKS_STARTRX.Set(1) nrf.UART0.INTENSET.Set(nrf.UART_INTENSET_RXDRDY_Msk) // Enable RX IRQ. intr := interrupt.New(nrf.IRQ_UART0, _UART0.handleInterrupt) intr.SetPriority(0xc0) // low priority intr.Enable() } // SetBaudRate sets the communication speed for the UART. func (uart *UART) SetBaudRate(br uint32) { // Magic: calculate 'baudrate' register from the input number. // Every value listed in the datasheet will be converted to the // correct register value, except for 192600. I suspect the value // listed in the nrf52 datasheet (0x0EBED000) is incorrectly rounded // and should be 0x0EBEE000, as the nrf51 datasheet lists the // nonrounded value 0x0EBEDFA4. // Some background: // https://devzone.nordicsemi.com/f/nordic-q-a/391/uart-baudrate-register-values/2046#2046 rate := uint32((uint64(br/400)*uint64(400*0xffffffff/16000000) + 0x800) & 0xffffff000) nrf.UART0.BAUDRATE.Set(rate) } // WriteByte writes a byte of data to the UART. func (uart *UART) writeByte(c byte) error { nrf.UART0.EVENTS_TXDRDY.Set(0) nrf.UART0.TXD.Set(uint32(c)) for nrf.UART0.EVENTS_TXDRDY.Get() == 0 { } return nil } func (uart *UART) flush() {} func (uart *UART) handleInterrupt(interrupt.Interrupt) { if nrf.UART0.EVENTS_RXDRDY.Get() != 0 { uart.Receive(byte(nrf.UART0.RXD.Get())) nrf.UART0.EVENTS_RXDRDY.Set(0x0) } } const i2cTimeout = 0xffff // this is around 29ms on a nrf52 // I2CConfig is used to store config info for I2C. type I2CConfig struct { Frequency uint32 SCL Pin SDA Pin Mode I2CMode } // Configure is intended to setup the I2C interface. func (i2c *I2C) Configure(config I2CConfig) error { i2c.disable() // Default I2C bus speed is 100 kHz. if config.Frequency == 0 { config.Frequency = 100 * KHz } // Default I2C pins if not set. if config.SDA == 0 && config.SCL == 0 { config.SDA = SDA_PIN config.SCL = SCL_PIN } // do config sclPort, sclPin := config.SCL.getPortPin() sclPort.PIN_CNF[sclPin].Set((nrf.GPIO_PIN_CNF_DIR_Input << nrf.GPIO_PIN_CNF_DIR_Pos) | (nrf.GPIO_PIN_CNF_INPUT_Connect << nrf.GPIO_PIN_CNF_INPUT_Pos) | (nrf.GPIO_PIN_CNF_PULL_Pullup << nrf.GPIO_PIN_CNF_PULL_Pos) | (nrf.GPIO_PIN_CNF_DRIVE_S0D1 << nrf.GPIO_PIN_CNF_DRIVE_Pos) | (nrf.GPIO_PIN_CNF_SENSE_Disabled << nrf.GPIO_PIN_CNF_SENSE_Pos)) sdaPort, sdaPin := config.SDA.getPortPin() sdaPort.PIN_CNF[sdaPin].Set((nrf.GPIO_PIN_CNF_DIR_Input << nrf.GPIO_PIN_CNF_DIR_Pos) | (nrf.GPIO_PIN_CNF_INPUT_Connect << nrf.GPIO_PIN_CNF_INPUT_Pos) | (nrf.GPIO_PIN_CNF_PULL_Pullup << nrf.GPIO_PIN_CNF_PULL_Pos) | (nrf.GPIO_PIN_CNF_DRIVE_S0D1 << nrf.GPIO_PIN_CNF_DRIVE_Pos) | (nrf.GPIO_PIN_CNF_SENSE_Disabled << nrf.GPIO_PIN_CNF_SENSE_Pos)) i2c.setPins(config.SCL, config.SDA) i2c.mode = config.Mode if i2c.mode == I2CModeController { i2c.SetBaudRate(config.Frequency) i2c.enableAsController() } else { i2c.enableAsTarget() } return nil } // SetBaudRate sets the I2C frequency. It has the side effect of also // enabling the I2C hardware if disabled beforehand. // //go:inline func (i2c *I2C) SetBaudRate(br uint32) error { switch { case br >= 400*KHz: i2c.Bus.SetFREQUENCY(nrf.TWI_FREQUENCY_FREQUENCY_K400) case br >= 250*KHz: i2c.Bus.SetFREQUENCY(nrf.TWI_FREQUENCY_FREQUENCY_K250) default: i2c.Bus.SetFREQUENCY(nrf.TWI_FREQUENCY_FREQUENCY_K100) } return nil } // signalStop sends a stop signal to the I2C peripheral and waits for confirmation. func (i2c *I2C) signalStop() error { tries := 0 i2c.Bus.TASKS_STOP.Set(1) for i2c.Bus.EVENTS_STOPPED.Get() == 0 { tries++ if tries >= i2cTimeout { return errI2CSignalStopTimeout } } i2c.Bus.EVENTS_STOPPED.Set(0) return nil } var rngStarted = false // getRNG returns 32 bits of non-deterministic random data based on internal thermal noise. // According to Nordic's documentation, the random output is suitable for cryptographic purposes. func getRNG() (ret uint32, err error) { // There's no apparent way to check the status of the RNG peripheral's task, so simply start it // to avoid deadlocking while waiting for output. if !rngStarted { nrf.RNG.TASKS_START.Set(1) nrf.RNG.SetCONFIG_DERCEN(nrf.RNG_CONFIG_DERCEN_Enabled) rngStarted = true } // The RNG returns one byte at a time, so stack up four bytes into a single uint32 for return. for i := 0; i < 4; i++ { // Wait for data to be ready. for nrf.RNG.EVENTS_VALRDY.Get() == 0 { } // Append random byte to output. ret = (ret << 8) ^ nrf.RNG.GetVALUE() // Unset the EVENTS_VALRDY register to avoid reading the same random output twice. nrf.RNG.EVENTS_VALRDY.Set(0) } return ret, nil } // ReadTemperature reads the silicon die temperature of the chip. The return // value is in milli-celsius. func ReadTemperature() int32 { nrf.TEMP.TASKS_START.Set(1) for nrf.TEMP.EVENTS_DATARDY.Get() == 0 { } temp := int32(nrf.TEMP.TEMP.Get()) * 250 // the returned value is in units of 0.25°C nrf.TEMP.EVENTS_DATARDY.Set(0) return temp } const memoryStart = 0x0 // compile-time check for ensuring we fulfill BlockDevice interface var _ BlockDevice = flashBlockDevice{} var Flash flashBlockDevice type flashBlockDevice struct { } // ReadAt reads the given number of bytes from the block device. func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) { if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() { return 0, errFlashCannotReadPastEOF } data := unsafe.Slice((*byte)(unsafe.Pointer(FlashDataStart()+uintptr(off))), len(p)) copy(p, data) return len(p), nil } // WriteAt writes the given number of bytes to the block device. // Only double-word (64 bits) length data can be programmed. See rm0461 page 78. // If the length of p is not long enough it will be padded with 0xFF bytes. // This method assumes that the destination is already erased. func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { if len(p) == 0 { return 0, nil // nothing to do (and it would fail in sd_flash_write) } if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() { return 0, errFlashCannotWritePastEOF } address := FlashDataStart() + uintptr(off) padded := flashPad(p, int(f.WriteBlockSize())) // When the SoftDevice is enabled, access to the flash is restricted and // must go through the SoftDevice API. if isSoftDeviceEnabled() { // Call sd_flash_write, which is SVC_SOC_BASE + 9 in all the // SoftDevices I've checked. // Documentation: // https://docs.nordicsemi.com/bundle/s140_v6.0.0_api/page/group_n_r_f_s_o_c_f_u_n_c_t_i_o_n_s.html numberOfWords := len(padded) / 4 // flash access goes in 32-bit words result := arm.SVCall3(0x20+9, address, &padded[0], uint32(numberOfWords)) if result != 0 { // Could not queue flash operation? Not sure when this can // happen. return 0, flashError } // Wait until the SoftDevice is finished. flashStatus = flashStatusBusy for flashStatus == flashStatusBusy { handleSoftDeviceEvents() } // Check whether the operation was successful. if flashStatus != flashStatusOk { flashStatus = flashStatusOk return 0, flashError } return len(p), nil } waitWhileFlashBusy() nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Wen) defer nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Ren) for j := 0; j < len(padded); j += int(f.WriteBlockSize()) { // write word *(*uint32)(unsafe.Pointer(address)) = binary.LittleEndian.Uint32(padded[j : j+int(f.WriteBlockSize())]) address += uintptr(f.WriteBlockSize()) waitWhileFlashBusy() } return len(padded), nil } // Size returns the number of bytes in this block device. func (f flashBlockDevice) Size() int64 { return int64(FlashDataEnd() - FlashDataStart()) } const writeBlockSize = 4 // WriteBlockSize returns the block size in which data can be written to // memory. It can be used by a client to optimize writes, non-aligned writes // should always work correctly. func (f flashBlockDevice) WriteBlockSize() int64 { return writeBlockSize } // EraseBlockSize returns the smallest erasable area on this particular chip // in bytes. This is used for the block size in EraseBlocks. // It must be a power of two, and may be as small as 1. A typical size is 4096. func (f flashBlockDevice) EraseBlockSize() int64 { return eraseBlockSize() } // EraseBlocks erases the given number of blocks. An implementation may // transparently coalesce ranges of blocks into larger bundles if the chip // supports this. The start and len parameters are in block numbers, use // EraseBlockSize to map addresses to blocks. func (f flashBlockDevice) EraseBlocks(start, len int64) error { // When the SoftDevice is enabled, access to the flash is restricted and // must go through the SoftDevice API. if isSoftDeviceEnabled() { for i := range uint32(len) { flashPage := uint32(FlashDataStart())/eraseBlockSizeValue + uint32(start) + i // Call sd_flash_page_erase, which is SVC_SOC_BASE + 8 in all the // SoftDevices I've checked. // Documentation: // https://docs.nordicsemi.com/bundle/s140_v6.0.0_api/page/group_n_r_f_s_o_c_f_u_n_c_t_i_o_n_s.html#ga9c93dd94a138ad8b5ed3693ea38ffb3e result := arm.SVCall1(0x20+8, flashPage) if result != 0 { // Could not queue flash operation? Not sure when this can // happen. return flashError } // Wait until the SoftDevice is finished. flashStatus = flashStatusBusy for flashStatus == flashStatusBusy { handleSoftDeviceEvents() } // Check whether the operation was successful. if flashStatus != flashStatusOk { flashStatus = flashStatusOk return flashError } } return nil } // SoftDevice is not used or enabled. Use NVIC directly. address := FlashDataStart() + uintptr(start*f.EraseBlockSize()) waitWhileFlashBusy() nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Een) defer nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Ren) for i := start; i < start+len; i++ { nrf.NVMC.ERASEPAGE.Set(uint32(address)) waitWhileFlashBusy() address += uintptr(f.EraseBlockSize()) } return nil } func waitWhileFlashBusy() { for nrf.NVMC.GetREADY() != nrf.NVMC_READY_READY_Ready { } } var flashError = errors.New("machine: flash operation failed") const ( flashStatusOk = iota flashStatusError flashStatusBusy ) var flashStatus uint8 = flashStatusOk var sdEvent uint32 // Process all queued SoftDevice events. May only be called when the SoftDevice is enabled. // // Normally these are handled in the same interrupt where Bluetooth events are // handled. But in TinyGo, that's complicated. One option would be to put it in // the tinygo.org/x/bluetooth package, but that would cause a circular // dependency between the machine and the bluetooth package. Another would be to // put it here, but let the bluetooth package call handleSoftDeviceEvents, but // that relies on updating the bluetooth package at the same time. As a // compromise, these events are handled directly where they are expected (here // in the machine package). This works in practice since there are only very few // of such events (at the moment, only flash-related ones which is in the // machine package anyway). func handleSoftDeviceEvents() { for { var result uintptr if nrf.Device == "nrf52" || nrf.Device == "nrf52840" || nrf.Device == "nrf52833" { // sd_evt_get: SOC_SVC_BASE_NOT_AVAILABLE + 31 result = arm.SVCall1(0x2C+31, &sdEvent) } else { return // TODO: nrf51 etc } if result != 0 { // Some error occured. The only possible error is // NRF_ERROR_NOT_FOUND, which means there are no more events. return } // The following events are the same numbers in all SoftDevices I've checked. switch sdEvent { case 2: // NRF_EVT_FLASH_OPERATION_SUCCESS flashStatus = flashStatusOk case 3: // NRF_EVT_FLASH_OPERATION_ERROR flashStatus = flashStatusError } } } ================================================ FILE: src/machine/machine_nrf51.go ================================================ //go:build nrf51 package machine import ( "device/nrf" ) const eraseBlockSizeValue = 1024 func eraseBlockSize() int64 { return eraseBlockSizeValue } // Get peripheral and pin number for this GPIO pin. func (p Pin) getPortPin() (*nrf.GPIO_Type, uint32) { return nrf.GPIO, uint32(p) } func (uart *UART) setPins(tx, rx Pin) { nrf.UART0.PSELTXD.Set(uint32(tx)) nrf.UART0.PSELRXD.Set(uint32(rx)) } func (i2c *I2C) setPins(scl, sda Pin) { i2c.Bus.PSELSCL.Set(uint32(scl)) i2c.Bus.PSELSDA.Set(uint32(sda)) } // SPI on the NRF. type SPI struct { Bus *nrf.SPI_Type } // There are 2 SPI interfaces on the NRF51. var ( SPI0 = &SPI{Bus: nrf.SPI0} SPI1 = &SPI{Bus: nrf.SPI1} ) // SPIConfig is used to store config info for SPI. type SPIConfig struct { Frequency uint32 SCK Pin SDO Pin SDI Pin LSBFirst bool Mode uint8 } // Configure is intended to setup the SPI interface. func (spi *SPI) Configure(config SPIConfig) error { // Disable bus to configure it spi.Bus.ENABLE.Set(nrf.SPI_ENABLE_ENABLE_Disabled) // set frequency var freq uint32 if config.Frequency == 0 { config.Frequency = 4000000 // 4MHz } switch { case config.Frequency >= 8000000: freq = nrf.SPI_FREQUENCY_FREQUENCY_M8 case config.Frequency >= 4000000: freq = nrf.SPI_FREQUENCY_FREQUENCY_M4 case config.Frequency >= 2000000: freq = nrf.SPI_FREQUENCY_FREQUENCY_M2 case config.Frequency >= 1000000: freq = nrf.SPI_FREQUENCY_FREQUENCY_M1 case config.Frequency >= 500000: freq = nrf.SPI_FREQUENCY_FREQUENCY_K500 case config.Frequency >= 250000: freq = nrf.SPI_FREQUENCY_FREQUENCY_K250 default: // below 250kHz, default to the lowest speed available freq = nrf.SPI_FREQUENCY_FREQUENCY_K125 } spi.Bus.FREQUENCY.Set(freq) var conf uint32 // set bit transfer order if config.LSBFirst { conf = (nrf.SPI_CONFIG_ORDER_LsbFirst << nrf.SPI_CONFIG_ORDER_Pos) } // set mode switch config.Mode { case 0: conf &^= (nrf.SPI_CONFIG_CPOL_ActiveHigh << nrf.SPI_CONFIG_CPOL_Pos) conf &^= (nrf.SPI_CONFIG_CPHA_Leading << nrf.SPI_CONFIG_CPHA_Pos) case 1: conf &^= (nrf.SPI_CONFIG_CPOL_ActiveHigh << nrf.SPI_CONFIG_CPOL_Pos) conf |= (nrf.SPI_CONFIG_CPHA_Trailing << nrf.SPI_CONFIG_CPHA_Pos) case 2: conf |= (nrf.SPI_CONFIG_CPOL_ActiveLow << nrf.SPI_CONFIG_CPOL_Pos) conf &^= (nrf.SPI_CONFIG_CPHA_Leading << nrf.SPI_CONFIG_CPHA_Pos) case 3: conf |= (nrf.SPI_CONFIG_CPOL_ActiveLow << nrf.SPI_CONFIG_CPOL_Pos) conf |= (nrf.SPI_CONFIG_CPHA_Trailing << nrf.SPI_CONFIG_CPHA_Pos) default: // to mode conf &^= (nrf.SPI_CONFIG_CPOL_ActiveHigh << nrf.SPI_CONFIG_CPOL_Pos) conf &^= (nrf.SPI_CONFIG_CPHA_Leading << nrf.SPI_CONFIG_CPHA_Pos) } spi.Bus.CONFIG.Set(conf) // set pins if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { config.SCK = SPI0_SCK_PIN config.SDO = SPI0_SDO_PIN config.SDI = SPI0_SDI_PIN } spi.Bus.PSELSCK.Set(uint32(config.SCK)) spi.Bus.PSELMOSI.Set(uint32(config.SDO)) spi.Bus.PSELMISO.Set(uint32(config.SDI)) // Re-enable bus now that it is configured. spi.Bus.ENABLE.Set(nrf.SPI_ENABLE_ENABLE_Enabled) return nil } // Transfer writes/reads a single byte using the SPI interface. func (spi *SPI) Transfer(w byte) (byte, error) { spi.Bus.TXD.Set(uint32(w)) for spi.Bus.EVENTS_READY.Get() == 0 { } r := spi.Bus.RXD.Get() spi.Bus.EVENTS_READY.Set(0) // TODO: handle SPI errors return byte(r), nil } // Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read // interface, there must always be the same number of bytes written as bytes read. // The Tx method knows about this, and offers a few different ways of calling it. // // This form sends the bytes in tx buffer, putting the resulting bytes read into the rx buffer. // Note that the tx and rx buffers must be the same size: // // spi.Tx(tx, rx) // // This form sends the tx buffer, ignoring the result. Useful for sending "commands" that return zeros // until all the bytes in the command packet have been received: // // spi.Tx(tx, nil) // // This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": // // spi.Tx(nil, rx) func (spi *SPI) Tx(w, r []byte) error { var err error switch { case len(w) == 0: // read only, so write zero and read a result. for i := range r { r[i], err = spi.Transfer(0) if err != nil { return err } } case len(r) == 0: // write only spi.Bus.TXD.Set(uint32(w[0])) w = w[1:] for _, b := range w { spi.Bus.TXD.Set(uint32(b)) for spi.Bus.EVENTS_READY.Get() == 0 { } spi.Bus.EVENTS_READY.Set(0) _ = spi.Bus.RXD.Get() } for spi.Bus.EVENTS_READY.Get() == 0 { } spi.Bus.EVENTS_READY.Set(0) _ = spi.Bus.RXD.Get() default: // write/read if len(w) != len(r) { return ErrTxInvalidSliceSize } for i, b := range w { r[i], err = spi.Transfer(b) if err != nil { return err } } } return nil } // InitADC initializes the registers needed for ADC. func InitADC() { return // no specific setup on nrf51 machine. } // Configure configures an ADC pin to be able to read analog data. func (a ADC) Configure(ADCConfig) { return // no pin specific setup on nrf51 machine. } // Get returns the current value of a ADC pin in the range 0..0xffff. func (a ADC) Get() uint16 { var value uint32 adcPin := a.getADCPin() // Enable ADC. nrf.ADC.SetENABLE(nrf.ADC_ENABLE_ENABLE_Enabled) // Set pin to read. nrf.ADC.SetCONFIG_PSEL(adcPin) // config ADC nrf.ADC.SetCONFIG_RES(nrf.ADC_CONFIG_RES_10bit) nrf.ADC.SetCONFIG_INPSEL(nrf.ADC_CONFIG_INPSEL_AnalogInputOneThirdPrescaling) nrf.ADC.SetCONFIG_REFSEL(nrf.ADC_CONFIG_REFSEL_SupplyOneThirdPrescaling) // Start tasks. nrf.ADC.TASKS_START.Set(1) // Wait until the sample task is done. for nrf.ADC.EVENTS_END.Get() == 0 { } nrf.ADC.EVENTS_END.Set(0x00) value = nrf.ADC.GetRESULT() // Stop the ADC nrf.ADC.TASKS_STOP.Set(1) // Disable ADC. nrf.ADC.SetENABLE(nrf.ADC_ENABLE_ENABLE_Disabled) if value < 0 { value = 0 } // Return 16-bit result from 10-bit value. return uint16(value << 6) } func (a ADC) getADCPin() uint32 { switch a.Pin { case 1: return nrf.ADC_CONFIG_PSEL_AnalogInput2 case 2: return nrf.ADC_CONFIG_PSEL_AnalogInput3 case 3: return nrf.ADC_CONFIG_PSEL_AnalogInput4 case 4: return nrf.ADC_CONFIG_PSEL_AnalogInput5 case 5: return nrf.ADC_CONFIG_PSEL_AnalogInput6 case 6: return nrf.ADC_CONFIG_PSEL_AnalogInput7 case 26: return nrf.ADC_CONFIG_PSEL_AnalogInput0 case 27: return nrf.ADC_CONFIG_PSEL_AnalogInput1 default: return 0 } } ================================================ FILE: src/machine/machine_nrf51_simulator.go ================================================ //go:build !baremetal && (microbit || pca10031 || hw_651) package machine var I2C0 = &I2C{Bus: 0} var I2C1 = &I2C{Bus: 1} ================================================ FILE: src/machine/machine_nrf52.go ================================================ //go:build nrf52 package machine import ( "device/nrf" ) // Hardware pins const ( P0_00 Pin = 0 P0_01 Pin = 1 P0_02 Pin = 2 P0_03 Pin = 3 P0_04 Pin = 4 P0_05 Pin = 5 P0_06 Pin = 6 P0_07 Pin = 7 P0_08 Pin = 8 P0_09 Pin = 9 P0_10 Pin = 10 P0_11 Pin = 11 P0_12 Pin = 12 P0_13 Pin = 13 P0_14 Pin = 14 P0_15 Pin = 15 P0_16 Pin = 16 P0_17 Pin = 17 P0_18 Pin = 18 P0_19 Pin = 19 P0_20 Pin = 20 P0_21 Pin = 21 P0_22 Pin = 22 P0_23 Pin = 23 P0_24 Pin = 24 P0_25 Pin = 25 P0_26 Pin = 26 P0_27 Pin = 27 P0_28 Pin = 28 P0_29 Pin = 29 P0_30 Pin = 30 P0_31 Pin = 31 ) // Get peripheral and pin number for this GPIO pin. func (p Pin) getPortPin() (*nrf.GPIO_Type, uint32) { return nrf.P0, uint32(p) } func (uart *UART) setPins(tx, rx Pin) { nrf.UART0.PSELTXD.Set(uint32(tx)) nrf.UART0.PSELRXD.Set(uint32(rx)) } func (i2c *I2C) setPins(scl, sda Pin) { i2c.Bus.PSELSCL.Set(uint32(scl)) i2c.Bus.PSELSDA.Set(uint32(sda)) } // PWM var ( PWM0 = &PWM{PWM: nrf.PWM0} PWM1 = &PWM{PWM: nrf.PWM1} PWM2 = &PWM{PWM: nrf.PWM2} ) const eraseBlockSizeValue = 4096 func eraseBlockSize() int64 { return eraseBlockSizeValue } const spiMaxBufferSize = 255 // from the datasheet: TXD.MAXCNT and RXD.MAXCNT ================================================ FILE: src/machine/machine_nrf52833.go ================================================ //go:build nrf52833 package machine import ( "device/nrf" ) // Hardware pins const ( P0_00 Pin = 0 P0_01 Pin = 1 P0_02 Pin = 2 P0_03 Pin = 3 P0_04 Pin = 4 P0_05 Pin = 5 P0_06 Pin = 6 P0_07 Pin = 7 P0_08 Pin = 8 P0_09 Pin = 9 P0_10 Pin = 10 P0_11 Pin = 11 P0_12 Pin = 12 P0_13 Pin = 13 P0_14 Pin = 14 P0_15 Pin = 15 P0_16 Pin = 16 P0_17 Pin = 17 P0_18 Pin = 18 P0_19 Pin = 19 P0_20 Pin = 20 P0_21 Pin = 21 P0_22 Pin = 22 P0_23 Pin = 23 P0_24 Pin = 24 P0_25 Pin = 25 P0_26 Pin = 26 P0_27 Pin = 27 P0_28 Pin = 28 P0_29 Pin = 29 P0_30 Pin = 30 P0_31 Pin = 31 P1_00 Pin = 32 P1_01 Pin = 33 P1_02 Pin = 34 P1_03 Pin = 35 P1_04 Pin = 36 P1_05 Pin = 37 P1_06 Pin = 38 P1_07 Pin = 39 P1_08 Pin = 40 P1_09 Pin = 41 P1_10 Pin = 42 P1_11 Pin = 43 P1_12 Pin = 44 P1_13 Pin = 45 P1_14 Pin = 46 P1_15 Pin = 47 ) // Get peripheral and pin number for this GPIO pin. func (p Pin) getPortPin() (*nrf.GPIO_Type, uint32) { if p >= 32 { return nrf.P1, uint32(p - 32) } else { return nrf.P0, uint32(p) } } func (uart *UART) setPins(tx, rx Pin) { nrf.UART0.PSEL.TXD.Set(uint32(tx)) nrf.UART0.PSEL.RXD.Set(uint32(rx)) } func (i2c *I2C) setPins(scl, sda Pin) { i2c.Bus.PSEL.SCL.Set(uint32(scl)) i2c.Bus.PSEL.SDA.Set(uint32(sda)) } // PWM var ( PWM0 = &PWM{PWM: nrf.PWM0} PWM1 = &PWM{PWM: nrf.PWM1} PWM2 = &PWM{PWM: nrf.PWM2} PWM3 = &PWM{PWM: nrf.PWM3} ) const eraseBlockSizeValue = 4096 func eraseBlockSize() int64 { return eraseBlockSizeValue } const spiMaxBufferSize = 0xffff // from the datasheet: TXD.MAXCNT and RXD.MAXCNT ================================================ FILE: src/machine/machine_nrf52840.go ================================================ //go:build nrf52840 package machine import ( "device/nrf" "errors" "unsafe" ) // Get peripheral and pin number for this GPIO pin. func (p Pin) getPortPin() (*nrf.GPIO_Type, uint32) { if p >= 32 { return nrf.P1, uint32(p - 32) } else { return nrf.P0, uint32(p) } } func (uart *UART) setPins(tx, rx Pin) { nrf.UART0.PSEL.TXD.Set(uint32(tx)) nrf.UART0.PSEL.RXD.Set(uint32(rx)) } func (i2c *I2C) setPins(scl, sda Pin) { i2c.Bus.PSEL.SCL.Set(uint32(scl)) i2c.Bus.PSEL.SDA.Set(uint32(sda)) } // PWM var ( PWM0 = &PWM{PWM: nrf.PWM0} PWM1 = &PWM{PWM: nrf.PWM1} PWM2 = &PWM{PWM: nrf.PWM2} PWM3 = &PWM{PWM: nrf.PWM3} ) // PDM represents a PDM device type PDM struct { device *nrf.PDM_Type defaultBuffer int16 } // Configure is intended to set up the PDM interface prior to use. func (pdm *PDM) Configure(config PDMConfig) error { if config.DIN == 0 { return errors.New("No DIN pin provided in configuration") } if config.CLK == 0 { return errors.New("No CLK pin provided in configuration") } config.DIN.Configure(PinConfig{Mode: PinInput}) config.CLK.Configure(PinConfig{Mode: PinOutput}) pdm.device = nrf.PDM pdm.device.PSEL.DIN.Set(uint32(config.DIN)) pdm.device.PSEL.CLK.Set(uint32(config.CLK)) pdm.device.PDMCLKCTRL.Set(nrf.PDM_PDMCLKCTRL_FREQ_Default) pdm.device.RATIO.Set(nrf.PDM_RATIO_RATIO_Ratio64) pdm.device.GAINL.Set(nrf.PDM_GAINL_GAINL_DefaultGain) pdm.device.GAINR.Set(nrf.PDM_GAINR_GAINR_DefaultGain) pdm.device.ENABLE.Set(nrf.PDM_ENABLE_ENABLE_Enabled) if config.Stereo { pdm.device.MODE.Set(nrf.PDM_MODE_OPERATION_Stereo | nrf.PDM_MODE_EDGE_LeftRising) } else { pdm.device.MODE.Set(nrf.PDM_MODE_OPERATION_Mono | nrf.PDM_MODE_EDGE_LeftRising) } pdm.device.SAMPLE.SetPTR(uint32(uintptr(unsafe.Pointer(&pdm.defaultBuffer)))) pdm.device.SAMPLE.SetMAXCNT_BUFFSIZE(1) pdm.device.SetTASKS_START(1) return nil } // Read stores a set of samples in the given target buffer. func (pdm *PDM) Read(buf []int16) (uint32, error) { pdm.device.SAMPLE.SetPTR(uint32(uintptr(unsafe.Pointer(&buf[0])))) pdm.device.SAMPLE.MAXCNT.Set(uint32(len(buf))) pdm.device.EVENTS_STARTED.Set(0) // Step 1: wait for new sampling to start for target buffer for !pdm.device.EVENTS_STARTED.HasBits(nrf.PDM_EVENTS_STARTED_EVENTS_STARTED) { } pdm.device.EVENTS_END.Set(0) // Step 2: swap out buffers for next recording so we don't continue to // write to the target buffer pdm.device.EVENTS_STARTED.Set(0) pdm.device.SAMPLE.SetPTR(uint32(uintptr(unsafe.Pointer(&pdm.defaultBuffer)))) pdm.device.SAMPLE.MAXCNT.Set(1) // Step 3: wait for original event to end for pdm.device.EVENTS_END.HasBits(nrf.PDM_EVENTS_STOPPED_EVENTS_STOPPED) { } // Step 4: wait for default buffer to start recording before proceeding // otherwise we see the contents of target buffer change later for !pdm.device.EVENTS_STARTED.HasBits(nrf.PDM_EVENTS_STARTED_EVENTS_STARTED) { } return uint32(len(buf)), nil } const eraseBlockSizeValue = 4096 func eraseBlockSize() int64 { return eraseBlockSizeValue } const spiMaxBufferSize = 0xffff // from the datasheet: TXD.MAXCNT and RXD.MAXCNT // ADC instance for the VDDH input pin. This pin is typically connected to USB // input voltage (~5V) or directly to a battery. var ADC_VDDH = ADC{adcVDDHPin} ================================================ FILE: src/machine/machine_nrf52840_enter_bootloader.go ================================================ //go:build nrf52840 package machine import ( "device/arm" "device/nrf" ) const ( dfuMagicSerialOnlyReset = 0x4e dfuMagicUF2Reset = 0x57 dfuMagicOTAReset = 0xA8 ) // EnterSerialBootloader resets the chip into the serial bootloader. After // reset, it can be flashed using serial/nrfutil. func EnterSerialBootloader() { arm.DisableInterrupts() nrf.POWER.GPREGRET.Set(dfuMagicSerialOnlyReset) arm.SystemReset() } // EnterUF2Bootloader resets the chip into the UF2 bootloader. After reset, it // can be flashed via nrfutil or by copying a UF2 file to the mass storage device func EnterUF2Bootloader() { arm.DisableInterrupts() nrf.POWER.GPREGRET.Set(dfuMagicUF2Reset) arm.SystemReset() } // EnterOTABootloader resets the chip into the bootloader so that it can be // flashed via an OTA update func EnterOTABootloader() { arm.DisableInterrupts() nrf.POWER.GPREGRET.Set(dfuMagicOTAReset) arm.SystemReset() } ================================================ FILE: src/machine/machine_nrf52840_lfxtal_false.go ================================================ //go:build nrf52840 && nrf52840_lfxtal_false package machine const HasLowFrequencyCrystal = false ================================================ FILE: src/machine/machine_nrf52840_lfxtal_true.go ================================================ //go:build nrf52840 && ((nrf52840_generic && !nrf52840_lfxtal_false) || nrf52840_lfxtal_true) package machine const HasLowFrequencyCrystal = true ================================================ FILE: src/machine/machine_nrf52840_simulator.go ================================================ //go:build !baremetal && (bluemicro840 || circuitplay_bluefruit || clue_alpha || feather_nrf52840_sense || feather_nrf52840 || itsybitsy_nrf52840 || mdbt50qrx || nano_33_ble || nicenano || nrf52840_mdk || particle_3rd_gen || pca10056 || pca10059 || rak4631 || reelboard || xiao_ble) // Simulator support for nrf52840 based boards. package machine // Channel values below are nil, so that they get filled in on the first use. // This is the same as what happens on baremetal. var PWM0 = &timerType{ instance: 0, frequency: 16e6, bits: 15, prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128}, channelPins: [][]Pin{ nil, // channel 0 nil, // channel 1 nil, // channel 2 nil, // channel 3 }, } var PWM1 = &timerType{ instance: 1, frequency: 16e6, bits: 15, prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128}, channelPins: [][]Pin{ nil, // channel 0 nil, // channel 1 nil, // channel 2 nil, // channel 3 }, } var PWM2 = &timerType{ instance: 2, frequency: 16e6, bits: 15, prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128}, channelPins: [][]Pin{ nil, // channel 0 nil, // channel 1 nil, // channel 2 nil, // channel 3 }, } var PWM3 = &timerType{ instance: 3, frequency: 16e6, bits: 15, prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128}, channelPins: [][]Pin{ nil, // channel 0 nil, // channel 1 nil, // channel 2 nil, // channel 3 }, } var I2C0 = &I2C{Bus: 0} var I2C1 = &I2C{Bus: 1} ================================================ FILE: src/machine/machine_nrf52840_usb.go ================================================ //go:build nrf52840 package machine import ( "device/arm" "device/nrf" "machine/usb" "runtime/interrupt" "runtime/volatile" "unsafe" ) const NumberOfUSBEndpoints = 8 var ( sendOnEP0DATADONE struct { ptr *byte count int offset int } epinen uint32 epouten uint32 easyDMABusy volatile.Register8 endPoints = []uint32{ usb.CONTROL_ENDPOINT: usb.ENDPOINT_TYPE_CONTROL, usb.CDC_ENDPOINT_ACM: (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn), usb.CDC_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut), usb.CDC_ENDPOINT_IN: (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn), usb.HID_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt In usb.HID_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt Out usb.MIDI_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Bulk In usb.MIDI_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Bulk Out } ) // enterCriticalSection is used to protect access to easyDMA - only one thing // can be done with it at a time func enterCriticalSection() { waitForEasyDMA() easyDMABusy.SetBits(1) } func waitForEasyDMA() { for easyDMABusy.HasBits(1) { arm.Asm("wfi") } } func exitCriticalSection() { easyDMABusy.ClearBits(1) } // Configure the USB peripheral. The config is here for compatibility with the UART interface. func (dev *USBDevice) Configure(config UARTConfig) { if dev.initcomplete { return } state := interrupt.Disable() defer interrupt.Restore(state) nrf.USBD.USBPULLUP.Set(0) // Enable IRQ. Make sure this is higher than the SWI2 interrupt handler so // that it is possible to print to the console from a BLE interrupt. You // shouldn't generally do that but it is useful for debugging and panic // logging. intr := interrupt.New(nrf.IRQ_USBD, handleUSBIRQ) intr.SetPriority(0x40) // interrupt priority 2 (lower number means more important) intr.Enable() // enable interrupt for end of reset and start of frame nrf.USBD.INTEN.Set(nrf.USBD_INTENSET_USBEVENT) // errata 187 // https://infocenter.nordicsemi.com/topic/errata_nRF52840_EngB/ERR/nRF52840/EngineeringB/latest/anomaly_840_187.html (*volatile.Register32)(unsafe.Pointer(uintptr(0x4006EC00))).Set(0x00009375) (*volatile.Register32)(unsafe.Pointer(uintptr(0x4006ED14))).Set(0x00000003) (*volatile.Register32)(unsafe.Pointer(uintptr(0x4006EC00))).Set(0x00009375) // enable USB nrf.USBD.ENABLE.Set(1) timeout := 300000 for !nrf.USBD.EVENTCAUSE.HasBits(nrf.USBD_EVENTCAUSE_READY) { timeout-- if timeout == 0 { return } } nrf.USBD.EVENTCAUSE.ClearBits(nrf.USBD_EVENTCAUSE_READY) // errata 187 (*volatile.Register32)(unsafe.Pointer(uintptr(0x4006EC00))).Set(0x00009375) (*volatile.Register32)(unsafe.Pointer(uintptr(0x4006ED14))).Set(0x00000000) (*volatile.Register32)(unsafe.Pointer(uintptr(0x4006EC00))).Set(0x00009375) dev.initcomplete = true } func handleUSBIRQ(interrupt.Interrupt) { if nrf.USBD.EVENTS_SOF.Get() == 1 { nrf.USBD.EVENTS_SOF.Set(0) // if you want to blink LED showing traffic, this would be the place... } // USBD ready event if nrf.USBD.EVENTS_USBEVENT.Get() == 1 { nrf.USBD.EVENTS_USBEVENT.Set(0) if (nrf.USBD.EVENTCAUSE.Get() & nrf.USBD_EVENTCAUSE_READY) > 0 { // Configure control endpoint initEndpoint(0, usb.ENDPOINT_TYPE_CONTROL) nrf.USBD.USBPULLUP.Set(1) usbConfiguration = 0 } nrf.USBD.EVENTCAUSE.Set(0) } if nrf.USBD.EVENTS_EP0DATADONE.Get() == 1 { // done sending packet - either need to send another or enter status stage nrf.USBD.EVENTS_EP0DATADONE.Set(0) if sendOnEP0DATADONE.ptr != nil { // previous data was too big for one packet, so send a second ptr := sendOnEP0DATADONE.ptr count := sendOnEP0DATADONE.count if count > usb.EndpointPacketSize { sendOnEP0DATADONE.offset += usb.EndpointPacketSize sendOnEP0DATADONE.ptr = &udd_ep_control_cache_buffer[sendOnEP0DATADONE.offset] count = usb.EndpointPacketSize } sendOnEP0DATADONE.count -= count sendViaEPIn( 0, ptr, count, ) // clear, so we know we're done if sendOnEP0DATADONE.count == 0 { sendOnEP0DATADONE.ptr = nil sendOnEP0DATADONE.offset = 0 } } else { // no more data, so set status stage SendZlp() // nrf.USBD.TASKS_EP0STATUS.Set(1) } return } // Endpoint 0 Setup interrupt if nrf.USBD.EVENTS_EP0SETUP.Get() == 1 { // ack setup received nrf.USBD.EVENTS_EP0SETUP.Set(0) // parse setup setup := parseUSBSetupRegisters() ok := false if (setup.BmRequestType & usb.REQUEST_TYPE) == usb.REQUEST_STANDARD { // Standard Requests ok = handleStandardSetup(setup) } else { // Class Interface Requests if setup.WIndex < uint16(len(usbSetupHandler)) && usbSetupHandler[setup.WIndex] != nil { ok = usbSetupHandler[setup.WIndex](setup) } } if !ok { // Stall endpoint nrf.USBD.TASKS_EP0STALL.Set(1) } } // Now the actual transfer handlers, ignore endpoint number 0 (setup) if nrf.USBD.EVENTS_EPDATA.Get() > 0 { nrf.USBD.EVENTS_EPDATA.Set(0) epDataStatus := nrf.USBD.EPDATASTATUS.Get() nrf.USBD.EPDATASTATUS.Set(epDataStatus) var i uint32 for i = 1; i < uint32(len(endPoints)); i++ { // Check if endpoint has a pending interrupt inDataDone := epDataStatus&(nrf.USBD_EPDATASTATUS_EPIN1<<(i-1)) > 0 outDataDone := epDataStatus&(nrf.USBD_EPDATASTATUS_EPOUT1<<(i-1)) > 0 if inDataDone { if usbTxHandler[i] != nil { usbTxHandler[i]() } } else if outDataDone { enterCriticalSection() nrf.USBD.EPOUT[i].PTR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[i])))) count := nrf.USBD.SIZE.EPOUT[i].Get() nrf.USBD.EPOUT[i].MAXCNT.Set(count) nrf.USBD.TASKS_STARTEPOUT[i].Set(1) } } } // ENDEPOUT[n] events for i := 0; i < len(endPoints); i++ { if nrf.USBD.EVENTS_ENDEPOUT[i].Get() > 0 { nrf.USBD.EVENTS_ENDEPOUT[i].Set(0) buf := handleEndpointRx(uint32(i)) if usbRxHandler[i] == nil || usbRxHandler[i](buf) { AckUsbOutTransfer(uint32(i)) } exitCriticalSection() } } } func parseUSBSetupRegisters() usb.Setup { return usb.Setup{ BmRequestType: uint8(nrf.USBD.BMREQUESTTYPE.Get()), BRequest: uint8(nrf.USBD.BREQUEST.Get()), WValueL: uint8(nrf.USBD.WVALUEL.Get()), WValueH: uint8(nrf.USBD.WVALUEH.Get()), WIndex: uint16((nrf.USBD.WINDEXH.Get() << 8) | nrf.USBD.WINDEXL.Get()), WLength: uint16(((nrf.USBD.WLENGTHH.Get() & 0xff) << 8) | (nrf.USBD.WLENGTHL.Get() & 0xff)), } } func initEndpoint(ep, config uint32) { switch config { case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn: enableEPIn(ep) case usb.ENDPOINT_TYPE_BULK | usb.EndpointOut: nrf.USBD.INTENSET.Set(nrf.USBD_INTENSET_ENDEPOUT0 << ep) nrf.USBD.SIZE.EPOUT[ep].Set(0) enableEPOut(ep) case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut: nrf.USBD.INTENSET.Set(nrf.USBD_INTENSET_ENDEPOUT0 << ep) nrf.USBD.SIZE.EPOUT[ep].Set(0) enableEPOut(ep) case usb.ENDPOINT_TYPE_BULK | usb.EndpointIn: enableEPIn(ep) case usb.ENDPOINT_TYPE_CONTROL: enableEPIn(0) enableEPOut(0) nrf.USBD.INTENSET.Set(nrf.USBD_INTENSET_ENDEPOUT0 | nrf.USBD_INTENSET_EP0SETUP | nrf.USBD_INTENSET_EPDATA | nrf.USBD_INTENSET_EP0DATADONE) SendZlp() // nrf.USBD.TASKS_EP0STATUS.Set(1) } } // SendUSBInPacket sends a packet for USBHID (interrupt in / bulk in). func SendUSBInPacket(ep uint32, data []byte) bool { sendUSBPacket(ep, data, 0) // clear transfer complete flag nrf.USBD.INTENCLR.Set(nrf.USBD_INTENCLR_ENDEPOUT0 << 4) return true } // Prevent file size increases: https://github.com/tinygo-org/tinygo/pull/998 // //go:noinline func sendUSBPacket(ep uint32, data []byte, maxsize uint16) { count := len(data) if 0 < int(maxsize) && int(maxsize) < count { count = int(maxsize) } if ep == 0 { copy(udd_ep_control_cache_buffer[:], data[:count]) if count > usb.EndpointPacketSize { sendOnEP0DATADONE.offset = usb.EndpointPacketSize sendOnEP0DATADONE.ptr = &udd_ep_control_cache_buffer[sendOnEP0DATADONE.offset] sendOnEP0DATADONE.count = count - usb.EndpointPacketSize count = usb.EndpointPacketSize } sendViaEPIn( ep, &udd_ep_control_cache_buffer[0], count, ) } else { copy(udd_ep_in_cache_buffer[ep][:], data[:count]) sendViaEPIn( ep, &udd_ep_in_cache_buffer[ep][0], count, ) } } func handleEndpointRx(ep uint32) []byte { // get data count := int(nrf.USBD.EPOUT[ep].AMOUNT.Get()) return udd_ep_out_cache_buffer[ep][:count] } // AckUsbOutTransfer is called to acknowledge the completion of a USB OUT transfer. func AckUsbOutTransfer(ep uint32) { // set ready for next data nrf.USBD.SIZE.EPOUT[ep].Set(0) } func SendZlp() { nrf.USBD.TASKS_EP0STATUS.Set(1) } func sendViaEPIn(ep uint32, ptr *byte, count int) { nrf.USBD.EPIN[ep].PTR.Set( uint32(uintptr(unsafe.Pointer(ptr))), ) nrf.USBD.EPIN[ep].MAXCNT.Set(uint32(count)) nrf.USBD.TASKS_STARTEPIN[ep].Set(1) } func enableEPOut(ep uint32) { epouten = epouten | (nrf.USBD_EPOUTEN_OUT0 << ep) nrf.USBD.EPOUTEN.Set(epouten) } func enableEPIn(ep uint32) { epinen = epinen | (nrf.USBD_EPINEN_IN0 << ep) nrf.USBD.EPINEN.Set(epinen) } func handleUSBSetAddress(setup usb.Setup) bool { // nrf USBD handles this return true } func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) { var b [cdcLineInfoSize]byte nrf.USBD.TASKS_EP0RCVOUT.Set(1) nrf.USBD.EPOUT[0].PTR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[0])))) nrf.USBD.EPOUT[0].MAXCNT.Set(64) timeout := 300000 count := 0 for { if nrf.USBD.EVENTS_EP0DATADONE.Get() == 1 { nrf.USBD.EVENTS_EP0DATADONE.Set(0) count = int(nrf.USBD.SIZE.EPOUT[0].Get()) nrf.USBD.TASKS_STARTEPOUT[0].Set(1) break } timeout-- if timeout == 0 { return b, ErrUSBReadTimeout } } timeout = 300000 for { if nrf.USBD.EVENTS_ENDEPOUT[0].Get() == 1 { nrf.USBD.EVENTS_ENDEPOUT[0].Set(0) break } timeout-- if timeout == 0 { return b, ErrUSBReadTimeout } } nrf.USBD.TASKS_EP0STATUS.Set(1) nrf.USBD.TASKS_EP0RCVOUT.Set(0) copy(b[:7], udd_ep_out_cache_buffer[0][:count]) return b, nil } ================================================ FILE: src/machine/machine_nrf52840_usb_reset_bossa.go ================================================ //go:build nrf52840 && nrf52840_reset_bossa package machine // EnterBootloader resets the chip into the serial bootloader. After // reset, it can be flashed using serial/nrfutil. func EnterBootloader() { EnterSerialBootloader() } ================================================ FILE: src/machine/machine_nrf52840_usb_reset_none.go ================================================ //go:build nrf52840 && !nrf52840_reset_uf2 && !nrf52840_reset_bossa package machine // EnterBootloader resets the chip into the serial bootloader. func EnterBootloader() { // skip } ================================================ FILE: src/machine/machine_nrf52840_usb_reset_uf2.go ================================================ //go:build nrf52840 && nrf52840_reset_uf2 package machine // EnterBootloader resets the chip into the UF2 bootloader. After reset, it // can be flashed via nrfutil or by copying a UF2 file to the mass storage device func EnterBootloader() { EnterUF2Bootloader() } ================================================ FILE: src/machine/machine_nrf528xx.go ================================================ //go:build nrf52840 || nrf52833 package machine import ( "device/nrf" "unsafe" ) // I2C on the NRF528xx. type I2C struct { Bus *nrf.TWIM_Type // Called Bus to align with Bus field in nrf51 BusT *nrf.TWIS_Type mode I2CMode } // There are 2 I2C interfaces on the NRF. var ( I2C0 = &I2C{Bus: nrf.TWIM0, BusT: nrf.TWIS0} I2C1 = &I2C{Bus: nrf.TWIM1, BusT: nrf.TWIS1} ) func (i2c *I2C) enableAsController() { i2c.Bus.ENABLE.Set(nrf.TWIM_ENABLE_ENABLE_Enabled) } func (i2c *I2C) enableAsTarget() { i2c.BusT.ENABLE.Set(nrf.TWIS_ENABLE_ENABLE_Enabled) } func (i2c *I2C) disable() { i2c.Bus.ENABLE.Set(0) } // Tx does a single I2C transaction at the specified address (when in controller mode). // // It clocks out the given address, writes the bytes in w, reads back len(r) // bytes and stores them in r, and generates a stop condition on the bus. func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) { i2c.Bus.ADDRESS.Set(uint32(addr)) i2c.Bus.EVENTS_STOPPED.Set(0) i2c.Bus.EVENTS_ERROR.Set(0) i2c.Bus.EVENTS_RXSTARTED.Set(0) i2c.Bus.EVENTS_TXSTARTED.Set(0) i2c.Bus.EVENTS_LASTRX.Set(0) i2c.Bus.EVENTS_LASTTX.Set(0) i2c.Bus.EVENTS_SUSPENDED.Set(0) // Configure for a single shot to perform both write and read (as applicable) if len(w) != 0 { i2c.Bus.TXD.PTR.Set(uint32(unsafeNoEscape(unsafe.Pointer(unsafe.SliceData(w))))) i2c.Bus.TXD.MAXCNT.Set(uint32(len(w))) // If no read, immediately signal stop after TX if len(r) == 0 { i2c.Bus.SHORTS.Set(nrf.TWIM_SHORTS_LASTTX_STOP) } } if len(r) != 0 { i2c.Bus.RXD.PTR.Set(uint32(unsafeNoEscape(unsafe.Pointer(unsafe.SliceData(r))))) i2c.Bus.RXD.MAXCNT.Set(uint32(len(r))) // Auto-start Rx after Tx and Stop after Rx i2c.Bus.SHORTS.Set(nrf.TWIM_SHORTS_LASTTX_STARTRX | nrf.TWIM_SHORTS_LASTRX_STOP) } // Fire the transaction i2c.Bus.TASKS_RESUME.Set(1) if len(w) != 0 { i2c.Bus.TASKS_STARTTX.Set(1) } else if len(r) != 0 { i2c.Bus.TASKS_STARTRX.Set(1) } // Wait until transaction stopped to ensure buffers fully processed for i2c.Bus.EVENTS_STOPPED.Get() == 0 { // Allow scheduler to run gosched() // Handle first occurrence of error by ensuring STOP sent on bus if err == nil && i2c.Bus.EVENTS_ERROR.Get() != 0 { err = twiCError(i2c.Bus.ERRORSRC.Get()) if i2c.Bus.EVENTS_STOPPED.Get() == 0 { // STOP cannot be sent during SUSPEND i2c.Bus.TASKS_RESUME.Set(1) i2c.Bus.TASKS_STOP.Set(1) } } } // Make sure the w and r buffers stay alive until this point, so they won't // be garbage collected while the buffers are used by the hardware. keepAliveNoEscape(unsafe.Pointer(unsafe.SliceData(w))) keepAliveNoEscape(unsafe.Pointer(unsafe.SliceData(r))) return } // Listen starts listening for I2C requests sent to specified address // // addr is the address to listen to func (i2c *I2C) Listen(addr uint8) error { i2c.BusT.ADDRESS[0].Set(uint32(addr)) i2c.BusT.CONFIG.Set(nrf.TWIS_CONFIG_ADDRESS0_Enabled) i2c.BusT.EVENTS_STOPPED.Set(0) i2c.BusT.EVENTS_ERROR.Set(0) i2c.BusT.EVENTS_RXSTARTED.Set(0) i2c.BusT.EVENTS_TXSTARTED.Set(0) i2c.BusT.EVENTS_WRITE.Set(0) i2c.BusT.EVENTS_READ.Set(0) return nil } // WaitForEvent blocks the current go-routine until an I2C event is received (when in Target mode). // // The passed buffer will be populated for receive events, with the number of bytes // received returned in count. For other event types, buf is not modified and a count // of zero is returned. // // For request events, the caller MUST call `Reply` to avoid hanging the i2c bus indefinitely. func (i2c *I2C) WaitForEvent(buf []byte) (evt I2CTargetEvent, count int, err error) { i2c.BusT.RXD.PTR.Set(uint32(unsafeNoEscape(unsafe.Pointer(unsafe.SliceData(buf))))) i2c.BusT.RXD.MAXCNT.Set(uint32(len(buf))) i2c.BusT.TASKS_PREPARERX.Set(nrf.TWIS_TASKS_PREPARERX_TASKS_PREPARERX_Trigger) i2c.Bus.TASKS_RESUME.Set(1) for i2c.BusT.EVENTS_STOPPED.Get() == 0 && i2c.BusT.EVENTS_READ.Get() == 0 { gosched() if i2c.BusT.EVENTS_ERROR.Get() != 0 { i2c.BusT.EVENTS_ERROR.Set(0) return I2CReceive, 0, twisError(i2c.BusT.ERRORSRC.Get()) } } // Make sure buf stays alive until this point, so it won't be garbage // collected while it is used by the hardware. keepAliveNoEscape(unsafe.Pointer(unsafe.SliceData(buf))) count = 0 evt = I2CFinish err = nil if i2c.BusT.EVENTS_WRITE.Get() != 0 { i2c.BusT.EVENTS_WRITE.Set(0) // Data was sent to this target. We've waited for // READ or STOPPED event, so transmission should be // complete. count = int(i2c.BusT.RXD.AMOUNT.Get()) evt = I2CReceive } else if i2c.BusT.EVENTS_READ.Get() != 0 { i2c.BusT.EVENTS_READ.Set(0) // Data is requested from this target, hw will stretch // the controller's clock until there is a reply to // send evt = I2CRequest } else if i2c.BusT.EVENTS_STOPPED.Get() != 0 { i2c.BusT.EVENTS_STOPPED.Set(0) evt = I2CFinish } return } // Reply supplies the response data the controller. func (i2c *I2C) Reply(buf []byte) error { i2c.BusT.TXD.PTR.Set(uint32(unsafeNoEscape(unsafe.Pointer(unsafe.SliceData(buf))))) i2c.BusT.TXD.MAXCNT.Set(uint32(len(buf))) i2c.BusT.EVENTS_STOPPED.Set(0) // Trigger Tx i2c.BusT.TASKS_PREPARETX.Set(nrf.TWIS_TASKS_PREPARETX_TASKS_PREPARETX_Trigger) // Block, waiting for Tx to complete for i2c.BusT.EVENTS_STOPPED.Get() == 0 { gosched() if i2c.BusT.EVENTS_ERROR.Get() != 0 { return twisError(i2c.BusT.ERRORSRC.Get()) } } // Make sure the buffer stays alive until this point, so it won't be garbage // collected while it is used by the hardware. keepAliveNoEscape(unsafe.Pointer(unsafe.SliceData(buf))) i2c.BusT.EVENTS_STOPPED.Set(0) return nil } // twiCError converts an I2C controller error to Go func twiCError(val uint32) error { if val == 0 { return nil } else if val&nrf.TWIM_ERRORSRC_OVERRUN_Msk == nrf.TWIM_ERRORSRC_OVERRUN { return errI2CBusError } else if val&nrf.TWIM_ERRORSRC_ANACK_Msk == nrf.TWIM_ERRORSRC_ANACK { return errI2CAckExpected } else if val&nrf.TWIM_ERRORSRC_DNACK_Msk == nrf.TWIM_ERRORSRC_DNACK { return errI2CAckExpected } return errI2CBusError } // twisError converts an I2C target error to Go func twisError(val uint32) error { if val == 0 { return nil } else if val&nrf.TWIS_ERRORSRC_OVERFLOW_Msk == nrf.TWIS_ERRORSRC_OVERFLOW { return errI2COverflow } else if val&nrf.TWIS_ERRORSRC_DNACK_Msk == nrf.TWIS_ERRORSRC_DNACK { return errI2CAckExpected } else if val&nrf.TWIS_ERRORSRC_OVERREAD_Msk == nrf.TWIS_ERRORSRC_OVERREAD { return errI2COverread } return errI2CBusError } var ( Watchdog = &watchdogImpl{} ) const ( // WatchdogMaxTimeout in milliseconds (approx 36h) WatchdogMaxTimeout = (0xffffffff * 1000) / 32768 ) type watchdogImpl struct { } // Configure the watchdog. // // This method should not be called after the watchdog is started and on // some platforms attempting to reconfigure after starting the watchdog // is explicitly forbidden / will not work. func (wd *watchdogImpl) Configure(config WatchdogConfig) error { // 32.768kHz counter crv := int32((int64(config.TimeoutMillis) * 32768) / 1000) nrf.WDT.CRV.Set(uint32(crv)) // One source nrf.WDT.RREN.Set(0x1) // Run during sleep nrf.WDT.CONFIG.Set(nrf.WDT_CONFIG_SLEEP_Run) return nil } // Starts the watchdog. func (wd *watchdogImpl) Start() error { nrf.WDT.TASKS_START.Set(nrf.WDT_TASKS_START_TASKS_START) return nil } // Update the watchdog, indicating that `source` is healthy. func (wd *watchdogImpl) Update() { // 0x6E524635 = magic value from datasheet nrf.WDT.RR[0].Set(0x6E524635) } ================================================ FILE: src/machine/machine_nrf52xxx.go ================================================ //go:build nrf52 || nrf52840 || nrf52833 package machine import ( "device/nrf" "runtime/volatile" "unsafe" ) func CPUFrequency() uint32 { return 64000000 } var adcVDDHPin = Pin(254) // special pin number for VDDH on the nrf52840 // InitADC initializes the registers needed for ADC. func InitADC() { // Enable ADC. // The ADC does not consume a noticeable amount of current by being enabled. nrf.SAADC.ENABLE.Set(nrf.SAADC_ENABLE_ENABLE_Enabled << nrf.SAADC_ENABLE_ENABLE_Pos) } // Configure configures an ADC pin to be able to read analog data. // Reference voltage can be 150, 300, 600, 1200, 1800, 2400, 3000(default), 3600 mV // Resolution can be 8, 10, 12(default), 14 bits // SampleTime will be ceiled to 3(default), 5, 10, 15, 20 or 40(max) µS respectively // Samples can be 1(default), 2, 4, 8, 16, 32, 64, 128, 256 samples func (a *ADC) Configure(config ADCConfig) { var configVal uint32 = nrf.SAADC_CH_CONFIG_RESP_Bypass<= 8000000: freq = nrf.SPIM_FREQUENCY_FREQUENCY_M8 case config.Frequency >= 4000000: freq = nrf.SPIM_FREQUENCY_FREQUENCY_M4 case config.Frequency >= 2000000: freq = nrf.SPIM_FREQUENCY_FREQUENCY_M2 case config.Frequency >= 1000000: freq = nrf.SPIM_FREQUENCY_FREQUENCY_M1 case config.Frequency >= 500000: freq = nrf.SPIM_FREQUENCY_FREQUENCY_K500 case config.Frequency >= 250000: freq = nrf.SPIM_FREQUENCY_FREQUENCY_K250 default: // below 250kHz, default to the lowest speed available freq = nrf.SPIM_FREQUENCY_FREQUENCY_K125 } spi.Bus.FREQUENCY.Set(freq) var conf uint32 // set bit transfer order if config.LSBFirst { conf = (nrf.SPIM_CONFIG_ORDER_LsbFirst << nrf.SPIM_CONFIG_ORDER_Pos) } // set mode switch config.Mode { case 0: conf &^= (nrf.SPIM_CONFIG_CPOL_ActiveHigh << nrf.SPIM_CONFIG_CPOL_Pos) conf &^= (nrf.SPIM_CONFIG_CPHA_Leading << nrf.SPIM_CONFIG_CPHA_Pos) case 1: conf &^= (nrf.SPIM_CONFIG_CPOL_ActiveHigh << nrf.SPIM_CONFIG_CPOL_Pos) conf |= (nrf.SPIM_CONFIG_CPHA_Trailing << nrf.SPIM_CONFIG_CPHA_Pos) case 2: conf |= (nrf.SPIM_CONFIG_CPOL_ActiveLow << nrf.SPIM_CONFIG_CPOL_Pos) conf &^= (nrf.SPIM_CONFIG_CPHA_Leading << nrf.SPIM_CONFIG_CPHA_Pos) case 3: conf |= (nrf.SPIM_CONFIG_CPOL_ActiveLow << nrf.SPIM_CONFIG_CPOL_Pos) conf |= (nrf.SPIM_CONFIG_CPHA_Trailing << nrf.SPIM_CONFIG_CPHA_Pos) default: // to mode conf &^= (nrf.SPIM_CONFIG_CPOL_ActiveHigh << nrf.SPIM_CONFIG_CPOL_Pos) conf &^= (nrf.SPIM_CONFIG_CPHA_Leading << nrf.SPIM_CONFIG_CPHA_Pos) } spi.Bus.CONFIG.Set(conf) // set pins if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { config.SCK = SPI0_SCK_PIN config.SDO = SPI0_SDO_PIN config.SDI = SPI0_SDI_PIN } spi.Bus.PSEL.SCK.Set(uint32(config.SCK)) spi.Bus.PSEL.MOSI.Set(uint32(config.SDO)) spi.Bus.PSEL.MISO.Set(uint32(config.SDI)) // Re-enable bus now that it is configured. spi.Bus.ENABLE.Set(nrf.SPIM_ENABLE_ENABLE_Enabled) return nil } // Transfer writes/reads a single byte using the SPI interface. func (spi *SPI) Transfer(w byte) (byte, error) { buf := spi.buf[:] buf[0] = w err := spi.Tx(buf[:], buf[:]) return buf[0], err } // Tx handles read/write operation for SPI interface. Since SPI is a synchronous // write/read interface, there must always be the same number of bytes written // as bytes read. Therefore, if the number of bytes don't match it will be // padded until they fit: if len(w) > len(r) the extra bytes received will be // dropped and if len(w) < len(r) extra 0 bytes will be sent. func (spi *SPI) Tx(w, r []byte) error { // Unfortunately the hardware (on the nrf52832) only supports a limited // amount of bytes in the buffers (depending on the chip), so if either w or // r is longer than that the transfer needs to be broken up in pieces. for len(r) != 0 || len(w) != 0 { // Prepare the SPI transfer: set the DMA pointers and lengths. // read buffer nr := uint32(len(r)) if nr > 0 { if nr > spiMaxBufferSize { nr = spiMaxBufferSize } spi.Bus.RXD.PTR.Set(uint32(unsafeNoEscape(unsafe.Pointer(unsafe.SliceData(r))))) r = r[nr:] } spi.Bus.RXD.MAXCNT.Set(nr) // write buffer nw := uint32(len(w)) if nw > 0 { if nw > spiMaxBufferSize { nw = spiMaxBufferSize } spi.Bus.TXD.PTR.Set(uint32(unsafeNoEscape(unsafe.Pointer(unsafe.SliceData(w))))) w = w[nw:] } spi.Bus.TXD.MAXCNT.Set(nw) // Do the transfer. // Note: this can be improved by not waiting until the transfer is // finished if the transfer is send-only (a common case). spi.Bus.TASKS_START.Set(1) for spi.Bus.EVENTS_END.Get() == 0 { gosched() } spi.Bus.EVENTS_END.Set(0) } // Make sure the w and r buffers stay alive for the GC until this point, // since they are used by the hardware but not otherwise visible. keepAliveNoEscape(unsafe.Pointer(unsafe.SliceData(r))) keepAliveNoEscape(unsafe.Pointer(unsafe.SliceData(w))) return nil } // PWM is one PWM peripheral, which consists of a counter and multiple output // channels (that can be connected to actual pins). You can set the frequency // using SetPeriod, but only for all the channels in this PWM peripheral at // once. type PWM struct { PWM *nrf.PWM_Type channelValues [4]volatile.Register16 } // Configure enables and configures this PWM. // On the nRF52 series, the maximum period is around 0.26s. func (pwm *PWM) Configure(config PWMConfig) error { // Enable the peripheral. pwm.PWM.ENABLE.Set(nrf.PWM_ENABLE_ENABLE_Enabled << nrf.PWM_ENABLE_ENABLE_Pos) // Use up counting only. TODO: allow configuring as up-and-down. pwm.PWM.MODE.Set(nrf.PWM_MODE_UPDOWN_Up << nrf.PWM_MODE_UPDOWN_Pos) // Indicate there are four channels that each have a different value. pwm.PWM.DECODER.Set(nrf.PWM_DECODER_LOAD_Individual< maxTop { return ErrPWMPeriodTooLong } } pwm.PWM.COUNTERTOP.Set(uint32(top)) // Apparently this is needed to apply the new COUNTERTOP. pwm.PWM.TASKS_SEQSTART[0].Set(1) return nil } // Top returns the current counter top, for use in duty cycle calculation. It // will only change with a call to Configure or SetPeriod, otherwise it is // constant. // // The value returned here is hardware dependent. In general, it's best to treat // it as an opaque value that can be divided by some number and passed to // pwm.Set (see pwm.Set for more information). func (pwm *PWM) Top() uint32 { return pwm.PWM.COUNTERTOP.Get() } // Channel returns a PWM channel for the given pin. func (pwm *PWM) Channel(pin Pin) (uint8, error) { config := uint32(pin) for ch := uint8(0); ch < 4; ch++ { channelConfig := pwm.PWM.PSEL.OUT[ch].Get() if channelConfig == 0xffffffff { // Unused channel. Configure it. pwm.PWM.PSEL.OUT[ch].Set(config) // Configure the pin (required by the reference manual). pin.Configure(PinConfig{Mode: PinOutput}) // Set channel to zero and non-inverting. pwm.channelValues[ch].Set(0x8000) return ch, nil } else if channelConfig == config { // This channel is already configured for this pin. return ch, nil } } // All four pins are already in use with other pins. return 0, ErrInvalidOutputPin } // SetInverting sets whether to invert the output of this channel. // Without inverting, a 25% duty cycle would mean the output is high for 25% of // the time and low for the rest. Inverting flips the output as if a NOT gate // was placed at the output, meaning that the output would be 25% low and 75% // high with a duty cycle of 25%. func (pwm *PWM) SetInverting(channel uint8, inverting bool) { ptr := &pwm.channelValues[channel] if inverting { ptr.Set(ptr.Get() &^ 0x8000) } else { ptr.Set(ptr.Get() | 0x8000) } } // Set updates the channel value. This is used to control the channel duty // cycle. For example, to set it to a 25% duty cycle, use: // // ch.Set(ch.Top() / 4) // // ch.Set(0) will set the output to low and ch.Set(ch.Top()) will set the output // to high, assuming the output isn't inverted. func (pwm *PWM) Set(channel uint8, value uint32) { // Update the channel value while retaining the polarity bit. ptr := &pwm.channelValues[channel] ptr.Set(ptr.Get()&0x8000 | uint16(value)&0x7fff) // Start the PWM, if it isn't already running. pwm.PWM.TASKS_SEQSTART[0].Set(1) } ================================================ FILE: src/machine/machine_nrf5x.go ================================================ //go:build nrf51 || nrf52 package machine import "device/nrf" // I2C on the NRF51 and NRF52. type I2C struct { Bus *nrf.TWI_Type mode I2CMode } // There are 2 I2C interfaces on the NRF. var ( I2C0 = &I2C{Bus: nrf.TWI0} I2C1 = &I2C{Bus: nrf.TWI1} ) func (i2c *I2C) enableAsController() { i2c.Bus.ENABLE.Set(nrf.TWI_ENABLE_ENABLE_Enabled) } func (i2c *I2C) enableAsTarget() { // Not supported on this hardware } func (i2c *I2C) disable() { i2c.Bus.ENABLE.Set(0) } // Tx does a single I2C transaction at the specified address. // It clocks out the given address, writes the bytes in w, reads back len(r) // bytes and stores them in r, and generates a stop condition on the bus. func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) { // Tricky stop condition. // After reads, the stop condition is generated implicitly with a shortcut. // After writes not followed by reads and in the case of errors, stop must be generated explicitly. i2c.Bus.ADDRESS.Set(uint32(addr)) if len(w) != 0 { i2c.Bus.TASKS_STARTTX.Set(1) // start transmission for writing for _, b := range w { if err = i2c.writeByte(b); err != nil { i2c.signalStop() return } } } if len(r) != 0 { // To trigger suspend task when a byte is received i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND) i2c.Bus.TASKS_STARTRX.Set(1) // re-start transmission for reading for i := range r { // read each char if i+1 == len(r) { // To trigger stop task when last byte is received, set before resume task. i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_STOP) } if i > 0 { i2c.Bus.TASKS_RESUME.Set(1) // re-start transmission for reading } if r[i], err = i2c.readByte(); err != nil { i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND_Disabled) i2c.signalStop() return } } i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND_Disabled) } if len(r) == 0 { // Stop the I2C transaction after the write. err = i2c.signalStop() } else { // The last byte read has already stopped the transaction, via // TWI_SHORTS_BB_STOP. But we still need to wait until we receive the // STOPPED event. tries := 0 for i2c.Bus.EVENTS_STOPPED.Get() == 0 { tries++ if tries >= i2cTimeout { return errI2CSignalStopTimeout } } i2c.Bus.EVENTS_STOPPED.Set(0) } return } // writeByte writes a single byte to the I2C bus and waits for confirmation. func (i2c *I2C) writeByte(data byte) error { tries := 0 i2c.Bus.TXD.Set(uint32(data)) for i2c.Bus.EVENTS_TXDSENT.Get() == 0 { if e := i2c.Bus.EVENTS_ERROR.Get(); e != 0 { i2c.Bus.EVENTS_ERROR.Set(0) return errI2CBusError } tries++ if tries >= i2cTimeout { return errI2CWriteTimeout } } i2c.Bus.EVENTS_TXDSENT.Set(0) return nil } // readByte reads a single byte from the I2C bus when it is ready. func (i2c *I2C) readByte() (byte, error) { tries := 0 for i2c.Bus.EVENTS_RXDREADY.Get() == 0 { if e := i2c.Bus.EVENTS_ERROR.Get(); e != 0 { i2c.Bus.EVENTS_ERROR.Set(0) return 0, errI2CBusError } tries++ if tries >= i2cTimeout { return 0, errI2CReadTimeout } } i2c.Bus.EVENTS_RXDREADY.Set(0) return byte(i2c.Bus.RXD.Get()), nil } ================================================ FILE: src/machine/machine_nrf_bare.go ================================================ //go:build nrf && !softdevice package machine // GetRNG returns 32 bits of non-deterministic random data based on internal thermal noise. // According to Nordic's documentation, the random output is suitable for cryptographic purposes. func GetRNG() (ret uint32, err error) { return getRNG() } func isSoftDeviceEnabled() bool { return false } ================================================ FILE: src/machine/machine_nrf_sd.go ================================================ //go:build nrf && softdevice package machine import ( "device/arm" "device/nrf" "errors" ) // avoid a heap allocation in GetRNG. var ( bytesAvailable uint8 buf [4]uint8 errNoSoftDeviceSupport = errors.New("rng: softdevice not supported on this device") errNotEnoughRandomData = errors.New("rng: not enough random data available") ) // GetRNG returns 32 bits of non-deterministic random data based on internal thermal noise. // According to Nordic's documentation, the random output is suitable for cryptographic purposes. func GetRNG() (ret uint32, err error) { // First check whether the SoftDevice is enabled. // sd_rand_application_bytes_available_get cannot be called when the SoftDevice is not enabled. if !isSoftDeviceEnabled() { return getRNG() } // call into the SoftDevice to get random data bytes available switch nrf.Device { case "nrf51": // sd_rand_application_bytes_available_get: SOC_SVC_BASE_NOT_AVAILABLE + 4 arm.SVCall1(0x2B+4, &bytesAvailable) case "nrf52", "nrf52840", "nrf52833": // sd_rand_application_bytes_available_get: SOC_SVC_BASE_NOT_AVAILABLE + 4 arm.SVCall1(0x2C+4, &bytesAvailable) default: return 0, errNoSoftDeviceSupport } if bytesAvailable < 4 { return 0, errNotEnoughRandomData } switch nrf.Device { case "nrf51": // sd_rand_application_vector_get: SOC_SVC_BASE_NOT_AVAILABLE + 5 arm.SVCall2(0x2B+5, &buf, 4) case "nrf52", "nrf52840", "nrf52833": // sd_rand_application_vector_get: SOC_SVC_BASE_NOT_AVAILABLE + 5 arm.SVCall2(0x2C+5, &buf, 4) } return uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24, nil } // This function is defined in the runtime, but we need it too. // //go:linkname isSoftDeviceEnabled runtime.isSoftDeviceEnabled func isSoftDeviceEnabled() bool ================================================ FILE: src/machine/machine_nxpmk66f18.go ================================================ // Derivative work of Teensyduino Core Library // http://www.pjrc.com/teensy/ // Copyright (c) 2017 PJRC.COM, LLC. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // 1. The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // 2. If the Software is incorporated into a build system that allows // selection among a list of target devices, then similar target // devices manufactured by PJRC.COM must be included in the list of // target devices and selectable in the same manner. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS // BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. //go:build nxp && mk66f18 package machine import ( "device/nxp" "runtime/volatile" "unsafe" ) const deviceName = nxp.Device const ( PinInput PinMode = iota PinInputPullup PinInputPulldown PinOutput PinOutputOpenDrain PinDisable ) // Deprecated: use PinInputPullup and PinInputPulldown instead. const ( PinInputPullUp = PinInputPullup PinInputPullDown = PinInputPulldown ) const ( PA00 Pin = iota PA01 PA02 PA03 PA04 PA05 PA06 PA07 PA08 PA09 PA10 PA11 PA12 PA13 PA14 PA15 PA16 PA17 PA18 PA19 PA20 PA21 PA22 PA23 PA24 PA25 PA26 PA27 PA28 PA29 ) const ( PB00 Pin = iota + 32 PB01 PB02 PB03 PB04 PB05 PB06 PB07 PB08 PB09 PB10 PB11 _ _ _ _ PB16 PB17 PB18 PB19 PB20 PB21 PB22 PB23 ) const ( PC00 Pin = iota + 64 PC01 PC02 PC03 PC04 PC05 PC06 PC07 PC08 PC09 PC10 PC11 PC12 PC13 PC14 PC15 PC16 PC17 PC18 PC19 ) const ( PD00 Pin = iota + 96 PD01 PD02 PD03 PD04 PD05 PD06 PD07 PD08 PD09 PD10 PD11 PD12 PD13 PD14 PD15 ) const ( PE00 Pin = iota + 128 PE01 PE02 PE03 PE04 PE05 PE06 PE07 PE08 PE09 PE10 PE11 PE12 PE13 PE14 PE15 PE16 PE17 PE18 PE19 PE20 PE21 PE22 PE23 PE24 PE25 PE26 PE27 PE28 ) //go:inline func (p Pin) reg() (*nxp.GPIO_Type, *volatile.Register32, uint8) { var gpio *nxp.GPIO_Type var pcr *nxp.PORT_Type switch p / 32 { case 0: gpio, pcr = nxp.GPIOA, nxp.PORTA case 1: gpio, pcr = nxp.GPIOB, nxp.PORTB case 2: gpio, pcr = nxp.GPIOC, nxp.PORTC case 3: gpio, pcr = nxp.GPIOD, nxp.PORTD case 5: gpio, pcr = nxp.GPIOE, nxp.PORTE default: panic("invalid pin number") } return gpio, &(*[32]volatile.Register32)(unsafe.Pointer(pcr))[p%32], uint8(p % 32) } // Configure this pin with the given configuration. func (p Pin) Configure(config PinConfig) { gpio, pcr, pos := p.reg() switch config.Mode { case PinOutput: gpio.PDDR.SetBits(1 << pos) pcr.Set((1 << nxp.PORT_PCR0_MUX_Pos) | nxp.PORT_PCR0_SRE | nxp.PORT_PCR0_DSE) case PinOutputOpenDrain: gpio.PDDR.SetBits(1 << pos) pcr.Set((1 << nxp.PORT_PCR0_MUX_Pos) | nxp.PORT_PCR0_SRE | nxp.PORT_PCR0_DSE | nxp.PORT_PCR0_ODE) case PinInput: gpio.PDDR.ClearBits(1 << pos) pcr.Set((1 << nxp.PORT_PCR0_MUX_Pos)) case PinInputPullup: gpio.PDDR.ClearBits(1 << pos) pcr.Set((1 << nxp.PORT_PCR0_MUX_Pos) | nxp.PORT_PCR0_PE | nxp.PORT_PCR0_PS) case PinInputPulldown: gpio.PDDR.ClearBits(1 << pos) pcr.Set((1 << nxp.PORT_PCR0_MUX_Pos) | nxp.PORT_PCR0_PE) case PinDisable: gpio.PDDR.ClearBits(1 << pos) pcr.Set((0 << nxp.PORT_PCR0_MUX_Pos)) } } // Set changes the value of the GPIO pin. The pin must be configured as output. func (p Pin) Set(value bool) { gpio, _, pos := p.reg() if value { gpio.PSOR.Set(1 << pos) } else { gpio.PCOR.Set(1 << pos) } } // Get returns the current value of a GPIO pin. func (p Pin) Get() bool { gpio, _, pos := p.reg() return gpio.PDIR.HasBits(1 << pos) } func (p Pin) Control() *volatile.Register32 { _, pcr, _ := p.reg() return pcr } func (p Pin) Fast() FastPin { gpio, _, pos := p.reg() return FastPin{ PDOR: gpio.PDOR.Bit(pos), PSOR: gpio.PSOR.Bit(pos), PCOR: gpio.PCOR.Bit(pos), PTOR: gpio.PTOR.Bit(pos), PDIR: gpio.PDIR.Bit(pos), PDDR: gpio.PDDR.Bit(pos), } } type FastPin struct { PDOR *volatile.BitRegister PSOR *volatile.BitRegister PCOR *volatile.BitRegister PTOR *volatile.BitRegister PDIR *volatile.BitRegister PDDR *volatile.BitRegister } func (p FastPin) Set() { p.PSOR.Set(true) } func (p FastPin) Clear() { p.PCOR.Set(true) } func (p FastPin) Toggle() { p.PTOR.Set(true) } func (p FastPin) Write(v bool) { p.PDOR.Set(v) } func (p FastPin) Read() bool { return p.PDIR.Get() } ================================================ FILE: src/machine/machine_nxpmk66f18_uart.go ================================================ // Derivative work of Teensyduino Core Library // http://www.pjrc.com/teensy/ // Copyright (c) 2017 PJRC.COM, LLC. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // 1. The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // 2. If the Software is incorporated into a build system that allows // selection among a list of target devices, then similar target // devices manufactured by PJRC.COM must be included in the list of // target devices and selectable in the same manner. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS // BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. //go:build nxp && mk66f18 package machine import ( "device/arm" "device/nxp" "errors" "runtime/interrupt" "runtime/volatile" _ "unsafe" // for go:linkname ) const ( uartC2Enable = nxp.UART_C2_TE | nxp.UART_C2_RE | nxp.UART_C2_RIE | nxp.UART_C2_ILIE uartC2TXActive = uartC2Enable | nxp.UART_C2_TIE uartC2TXCompleting = uartC2Enable | nxp.UART_C2_TCIE uartC2TXInactive = uartC2Enable uartIRQPriority = 64 // determined from UARTx_PFIFO uartRXFIFODepth = 8 uartTXFIFODepth = 8 ) var ( ErrNotImplemented = errors.New("device has not been implemented") ErrNotConfigured = errors.New("device has not been configured") ) // PutcharUART writes a byte to the UART synchronously, without using interrupts // or calling the scheduler func PutcharUART(u *UART, c byte) { // ensure the UART has been configured if !u.SCGC.HasBits(u.SCGCMask) { u.configure(UARTConfig{}, false) } for u.TCFIFO.Get() > 0 { // busy wait } u.D.Set(c) u.C2.Set(uartC2TXActive) } // PollUART manually checks a UART status and calls the ISR. This should only be // called by runtime.abort. func PollUART(u *UART) { if u.SCGC.HasBits(u.SCGCMask) { u.handleStatusInterrupt(u.Interrupt) } } type UART struct { *nxp.UART_Type SCGC *volatile.Register32 SCGCMask uint32 DefaultRX Pin DefaultTX Pin // state Buffer RingBuffer // RX Buffer TXBuffer RingBuffer Configured bool Transmitting volatile.Register8 Interrupt interrupt.Interrupt } var ( UART0 = &_UART0 UART1 = &_UART1 UART2 = &_UART2 UART3 = &_UART3 UART4 = &_UART4 _UART0 = UART{UART_Type: nxp.UART0, SCGC: &nxp.SIM.SCGC4, SCGCMask: nxp.SIM_SCGC4_UART0, DefaultRX: defaultUART0RX, DefaultTX: defaultUART0TX} _UART1 = UART{UART_Type: nxp.UART1, SCGC: &nxp.SIM.SCGC4, SCGCMask: nxp.SIM_SCGC4_UART1, DefaultRX: defaultUART1RX, DefaultTX: defaultUART1TX} _UART2 = UART{UART_Type: nxp.UART2, SCGC: &nxp.SIM.SCGC4, SCGCMask: nxp.SIM_SCGC4_UART2, DefaultRX: defaultUART2RX, DefaultTX: defaultUART2TX} _UART3 = UART{UART_Type: nxp.UART3, SCGC: &nxp.SIM.SCGC4, SCGCMask: nxp.SIM_SCGC4_UART3, DefaultRX: defaultUART3RX, DefaultTX: defaultUART3TX} _UART4 = UART{UART_Type: nxp.UART4, SCGC: &nxp.SIM.SCGC1, SCGCMask: nxp.SIM_SCGC1_UART4, DefaultRX: defaultUART4RX, DefaultTX: defaultUART4TX} ) func init() { UART0.Interrupt = interrupt.New(nxp.IRQ_UART0_RX_TX, _UART0.handleStatusInterrupt) UART1.Interrupt = interrupt.New(nxp.IRQ_UART1_RX_TX, _UART1.handleStatusInterrupt) UART2.Interrupt = interrupt.New(nxp.IRQ_UART2_RX_TX, _UART2.handleStatusInterrupt) UART3.Interrupt = interrupt.New(nxp.IRQ_UART3_RX_TX, _UART3.handleStatusInterrupt) UART4.Interrupt = interrupt.New(nxp.IRQ_UART4_RX_TX, _UART4.handleStatusInterrupt) } // Configure the UART. func (u *UART) Configure(config UARTConfig) { u.configure(config, true) } func (u *UART) configure(config UARTConfig, canSched bool) { // from: serial_begin if !u.Configured { u.Transmitting.Set(0) // turn on the clock u.SCGC.Set(u.SCGCMask) // configure pins u.DefaultRX.Control().Set(nxp.PORT_PCR0_PE | nxp.PORT_PCR0_PS | nxp.PORT_PCR0_PFE | (3 << nxp.PORT_PCR0_MUX_Pos)) u.DefaultTX.Control().Set(nxp.PORT_PCR0_DSE | nxp.PORT_PCR0_SRE | (3 << nxp.PORT_PCR0_MUX_Pos)) u.C1.Set(nxp.UART_C1_ILT) } // default to 115200 baud if config.BaudRate == 0 { config.BaudRate = 115200 } // copied from teensy core's BAUD2DIV macro divisor := ((CPUFrequency() * 2) + (config.BaudRate >> 1)) / config.BaudRate if divisor < 32 { divisor = 32 } if u.Configured { // don't change baud rate mid transmit if canSched { u.Flush() } else { for u.Transmitting.Get() != 0 { // busy wait flush } } } // set the divisor u.BDH.Set(uint8((divisor >> 13) & 0x1F)) u.BDL.Set(uint8((divisor >> 5) & 0xFF)) u.C4.Set(uint8(divisor & 0x1F)) if !u.Configured { u.Configured = true u.C1.Set(nxp.UART_C1_ILT) // configure TX and RX watermark u.TWFIFO.Set(2) // causes bit TDRE of S1 to set u.RWFIFO.Set(4) // causes bit RDRF of S1 to set // enable FIFOs u.PFIFO.Set(nxp.UART_PFIFO_TXFE | nxp.UART_PFIFO_RXFE) // setup interrupts u.C2.Set(uartC2TXInactive) u.Interrupt.SetPriority(uartIRQPriority) u.Interrupt.Enable() } } func (u *UART) Disable() { // from: serial_end // check if the device has been enabled already if !u.SCGC.HasBits(u.SCGCMask) { return } u.Flush() u.Interrupt.Disable() u.C2.Set(0) // reconfigure pin u.DefaultRX.Configure(PinConfig{Mode: PinInputPullup}) u.DefaultTX.Configure(PinConfig{Mode: PinInputPullup}) // clear flags u.S1.Get() u.D.Get() u.Buffer.Clear() } func (u *UART) Flush() { for u.Transmitting.Get() != 0 { gosched() } } func (u *UART) handleStatusInterrupt(interrupt.Interrupt) { // from: uart0_status_isr // receive if u.S1.HasBits(nxp.UART_S1_RDRF | nxp.UART_S1_IDLE) { intrs := arm.DisableInterrupts() avail := u.RCFIFO.Get() if avail == 0 { // The only way to clear the IDLE interrupt flag is // to read the data register. But reading with no // data causes a FIFO underrun, which causes the // FIFO to return corrupted data. If anyone from // Freescale reads this, what a poor design! There // write should be a write-1-to-clear for IDLE. u.D.Get() // flushing the fifo recovers from the underrun, // but there's a possible race condition where a // new character could be received between reading // RCFIFO == 0 and flushing the FIFO. To minimize // the chance, interrupts are disabled so a higher // priority interrupt (hopefully) doesn't delay. // TODO: change this to disabling the IDLE interrupt // which won't be simple, since we already manage // which transmit interrupts are enabled. u.CFIFO.Set(nxp.UART_CFIFO_RXFLUSH) arm.EnableInterrupts(intrs) } else { arm.EnableInterrupts(intrs) for { u.Buffer.Put(u.D.Get()) avail-- if avail <= 0 { break } } } } // transmit if u.C2.HasBits(nxp.UART_C2_TIE) && u.S1.HasBits(nxp.UART_S1_TDRE) { data := make([]byte, 0, uartTXFIFODepth) avail := uartTXFIFODepth - u.TCFIFO.Get() // get avail bytes from ring buffer for len(data) < int(avail) { if b, ok := u.TXBuffer.Get(); ok { data = append(data, b) } else { break } } // write data to FIFO l := len(data) for i, b := range data { if i == l-1 { // only clear TDRE on last write, per the manual u.S1.Get() } u.D.Set(b) } // if FIFO still has room, disable TIE, enable TCIE if u.S1.HasBits(nxp.UART_S1_TDRE) { u.C2.Set(uartC2TXCompleting) } } // transmit complete if u.C2.HasBits(nxp.UART_C2_TCIE) && u.S1.HasBits(nxp.UART_S1_TC) { u.Transmitting.Set(0) u.C2.Set(uartC2TXInactive) } } // WriteByte writes a byte of data to the UART. func (u *UART) writeByte(c byte) error { if !u.Configured { return ErrNotConfigured } for !u.TXBuffer.Put(c) { gosched() } u.Transmitting.Set(1) u.C2.Set(uartC2TXActive) return nil } func (uart *UART) flush() {} ================================================ FILE: src/machine/machine_rp2.go ================================================ //go:build rp2040 || rp2350 package machine import ( "device/rp" "runtime/interrupt" "runtime/volatile" "unsafe" ) const deviceName = rp.Device const ( // Number of spin locks available // Note: On RP2350, most spinlocks are unusable due to Errata 2 _NUMSPINLOCKS = 32 _PICO_SPINLOCK_ID_IRQ = 9 // is48Pin notes whether the chip is RP2040 with 32 pins or RP2350 with 48 pins. is48Pin = _NUMBANK0_GPIOS == 48 ) // UART on the RP2040 var ( UART0 = &_UART0 _UART0 = UART{ Buffer: NewRingBuffer(), Bus: rp.UART0, } UART1 = &_UART1 _UART1 = UART{ Buffer: NewRingBuffer(), Bus: rp.UART1, } ) func init() { UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) } //go:linkname machineInit runtime.machineInit func machineInit() { // Reset all peripherals to put system into a known state, // except for QSPI pads and the XIP IO bank, as this is fatal if running from flash // and the PLLs, as this is fatal if clock muxing has not been reset on this boot // and USB, syscfg, as this disturbs USB-to-SWD on core 1 bits := ^uint32(initDontReset) resetBlock(bits) // Remove reset from peripherals which are clocked only by clkSys and // clkRef. Other peripherals stay in reset until we've configured clocks. bits = ^uint32(initUnreset) unresetBlockWait(bits) clocks.init() // Peripheral clocks should now all be running unresetBlockWait(RESETS_RESET_Msk) // DBGPAUSE pauses the timer when a debugger is connected. This prevents // sleep functions from ever returning, so disable it. timer.setDbgPause(false) } //go:linkname ticks runtime.machineTicks func ticks() uint64 { return timer.timeElapsed() } //go:linkname lightSleep runtime.machineLightSleep func lightSleep(ticks uint64) { timer.lightSleep(ticks) } // CurrentCore returns the core number the call was made from. func CurrentCore() int { return int(rp.SIO.CPUID.Get()) } // NumCores returns number of cores available on the device. func NumCores() int { return 2 } // ChipVersion returns the version of the chip. 1 is returned for B0 and B1 // chip. func ChipVersion() uint8 { const ( SYSINFO_BASE = 0x40000000 SYSINFO_CHIP_ID_OFFSET = 0x00000000 SYSINFO_CHIP_ID_REVISION_BITS = 0xf0000000 SYSINFO_CHIP_ID_REVISION_LSB = 28 ) // First register of sysinfo is chip id chipID := *(*uint32)(unsafe.Pointer(uintptr(SYSINFO_BASE + SYSINFO_CHIP_ID_OFFSET))) // Version 1 == B0/B1 version := (chipID & SYSINFO_CHIP_ID_REVISION_BITS) >> SYSINFO_CHIP_ID_REVISION_LSB return uint8(version) } // Single DMA channel. See rp.DMA_Type. type dmaChannel struct { READ_ADDR volatile.Register32 WRITE_ADDR volatile.Register32 TRANS_COUNT volatile.Register32 CTRL_TRIG volatile.Register32 _ [12]volatile.Register32 // aliases } // Static assignment of DMA channels to peripherals. // Allocating them statically is good enough for now. If lots of peripherals use // DMA, these might need to be assigned at runtime. const ( spi0DMAChannel = iota spi1DMAChannel ) // DMA channels usable on the RP2040. var dmaChannels = (*[12 + 4*rp2350ExtraReg]dmaChannel)(unsafe.Pointer(rp.DMA)) //go:inline func boolToBit(a bool) uint32 { if a { return 1 } return 0 } //go:inline func u32max(a, b uint32) uint32 { if a > b { return a } return b } //go:inline func isReservedI2CAddr(addr uint8) bool { return (addr&0x78) == 0 || (addr&0x78) == 0x78 } ================================================ FILE: src/machine/machine_rp2040_rom.go ================================================ //go:build tinygo && rp2040 package machine import ( "runtime/interrupt" "unsafe" ) /* // https://github.com/raspberrypi/pico-sdk // src/rp2_common/pico_bootrom/include/pico/bootrom.h #define ROM_FUNC_POPCOUNT32 ROM_TABLE_CODE('P', '3') #define ROM_FUNC_REVERSE32 ROM_TABLE_CODE('R', '3') #define ROM_FUNC_CLZ32 ROM_TABLE_CODE('L', '3') #define ROM_FUNC_CTZ32 ROM_TABLE_CODE('T', '3') #define ROM_FUNC_MEMSET ROM_TABLE_CODE('M', 'S') #define ROM_FUNC_MEMSET4 ROM_TABLE_CODE('S', '4') #define ROM_FUNC_MEMCPY ROM_TABLE_CODE('M', 'C') #define ROM_FUNC_MEMCPY44 ROM_TABLE_CODE('C', '4') #define ROM_FUNC_RESET_USB_BOOT ROM_TABLE_CODE('U', 'B') #define ROM_FUNC_CONNECT_INTERNAL_FLASH ROM_TABLE_CODE('I', 'F') #define ROM_FUNC_FLASH_EXIT_XIP ROM_TABLE_CODE('E', 'X') #define ROM_FUNC_FLASH_RANGE_ERASE ROM_TABLE_CODE('R', 'E') #define ROM_FUNC_FLASH_RANGE_PROGRAM ROM_TABLE_CODE('R', 'P') #define ROM_FUNC_FLASH_FLUSH_CACHE ROM_TABLE_CODE('F', 'C') #define ROM_FUNC_FLASH_ENTER_CMD_XIP ROM_TABLE_CODE('C', 'X') #define ROM_TABLE_CODE(c1, c2) ((c1) | ((c2) << 8)) typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned long uint32_t; typedef unsigned long size_t; typedef unsigned long uintptr_t; #define false 0 #define true 1 typedef int bool; #define ram_func __attribute__((section(".ramfuncs"),noinline)) typedef void *(*rom_table_lookup_fn)(uint16_t *table, uint32_t code); typedef void __attribute__((noreturn)) (*rom_reset_usb_boot_fn)(uint32_t, uint32_t); typedef void (*flash_init_boot2_copyout_fn)(void); typedef void (*flash_enable_xip_via_boot2_fn)(void); typedef void (*flash_exit_xip_fn)(void); typedef void (*flash_flush_cache_fn)(void); typedef void (*flash_connect_internal_fn)(void); typedef void (*flash_range_erase_fn)(uint32_t, size_t, uint32_t, uint16_t); typedef void (*flash_range_program_fn)(uint32_t, const uint8_t*, size_t); static inline __attribute__((always_inline)) void __compiler_memory_barrier(void) { __asm__ volatile ("" : : : "memory"); } #define rom_hword_as_ptr(rom_address) (void *)(uintptr_t)(*(uint16_t *)(uintptr_t)(rom_address)) void *rom_func_lookup(uint32_t code) { rom_table_lookup_fn rom_table_lookup = (rom_table_lookup_fn) rom_hword_as_ptr(0x18); uint16_t *func_table = (uint16_t *) rom_hword_as_ptr(0x14); return rom_table_lookup(func_table, code); } void reset_usb_boot(uint32_t usb_activity_gpio_pin_mask, uint32_t disable_interface_mask) { rom_reset_usb_boot_fn func = (rom_reset_usb_boot_fn) rom_func_lookup(ROM_FUNC_RESET_USB_BOOT); func(usb_activity_gpio_pin_mask, disable_interface_mask); } #define FLASH_BLOCK_ERASE_CMD 0xd8 #define FLASH_PAGE_SIZE (1u << 8) #define FLASH_SECTOR_SIZE (1u << 12) #define FLASH_BLOCK_SIZE (1u << 16) #define BOOT2_SIZE_WORDS 64 #define XIP_BASE 0x10000000 static uint32_t boot2_copyout[BOOT2_SIZE_WORDS]; static bool boot2_copyout_valid = false; static ram_func void flash_init_boot2_copyout() { if (boot2_copyout_valid) return; for (int i = 0; i < BOOT2_SIZE_WORDS; ++i) boot2_copyout[i] = ((uint32_t *)XIP_BASE)[i]; __compiler_memory_barrier(); boot2_copyout_valid = true; } static ram_func void flash_enable_xip_via_boot2() { ((void (*)(void))boot2_copyout+1)(); } #define IO_QSPI_BASE 0x40018000 #define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS 0x00000300 #define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_MSB 9 #define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_LSB 8 #define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_LOW 0x2 #define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_HIGH 0x3 #define XIP_SSI_BASE 0x18000000 #define ssi_hw ((ssi_hw_t *)XIP_SSI_BASE) #define SSI_SR_OFFSET 0x00000028 #define SSI_DR0_OFFSET 0x00000060 #define SSI_SR_TFNF_BITS 0x00000002 #define SSI_SR_RFNE_BITS 0x00000008 void ram_func flash_cs_force(bool high) { uint32_t field_val = high ? IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_HIGH : IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_LOW; // &ioqspi_hw->io[1].ctrl uint32_t *addr = (uint32_t*)(IO_QSPI_BASE + (1 * 8) + 4); *addr = ((*addr) & !IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS) | (field_val << IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_LSB); } // See https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_flash/flash.c#L86 void ram_func flash_range_write(uint32_t offset, const uint8_t *data, size_t count) { flash_range_program_fn flash_range_program_func = (flash_range_program_fn) rom_func_lookup(ROM_FUNC_FLASH_RANGE_PROGRAM); flash_connect_internal_fn flash_connect_internal_func = (flash_connect_internal_fn) rom_func_lookup(ROM_FUNC_CONNECT_INTERNAL_FLASH); flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn) rom_func_lookup(ROM_FUNC_FLASH_EXIT_XIP); flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn) rom_func_lookup(ROM_FUNC_FLASH_FLUSH_CACHE); flash_init_boot2_copyout(); __compiler_memory_barrier(); flash_connect_internal_func(); flash_exit_xip_func(); flash_range_program_func(offset, data, count); flash_flush_cache_func(); flash_enable_xip_via_boot2(); } void ram_func flash_erase_blocks(uint32_t offset, size_t count) { flash_range_erase_fn flash_range_erase_func = (flash_range_erase_fn) rom_func_lookup(ROM_FUNC_FLASH_RANGE_ERASE); flash_connect_internal_fn flash_connect_internal_func = (flash_connect_internal_fn) rom_func_lookup(ROM_FUNC_CONNECT_INTERNAL_FLASH); flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn) rom_func_lookup(ROM_FUNC_FLASH_EXIT_XIP); flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn) rom_func_lookup(ROM_FUNC_FLASH_FLUSH_CACHE); flash_init_boot2_copyout(); __compiler_memory_barrier(); flash_connect_internal_func(); flash_exit_xip_func(); flash_range_erase_func(offset, count, FLASH_BLOCK_SIZE, FLASH_BLOCK_ERASE_CMD); flash_flush_cache_func(); flash_enable_xip_via_boot2(); } void ram_func flash_do_cmd(const uint8_t *txbuf, uint8_t *rxbuf, size_t count) { flash_connect_internal_fn flash_connect_internal_func = (flash_connect_internal_fn) rom_func_lookup(ROM_FUNC_CONNECT_INTERNAL_FLASH); flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn) rom_func_lookup(ROM_FUNC_FLASH_EXIT_XIP); flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn) rom_func_lookup(ROM_FUNC_FLASH_FLUSH_CACHE); flash_init_boot2_copyout(); __compiler_memory_barrier(); flash_connect_internal_func(); flash_exit_xip_func(); flash_cs_force(0); size_t tx_remaining = count; size_t rx_remaining = count; // We may be interrupted -- don't want FIFO to overflow if we're distracted. const size_t max_in_flight = 16 - 2; while (tx_remaining || rx_remaining) { uint32_t flags = *(uint32_t*)(XIP_SSI_BASE + SSI_SR_OFFSET); bool can_put = !!(flags & SSI_SR_TFNF_BITS); bool can_get = !!(flags & SSI_SR_RFNE_BITS); if (can_put && tx_remaining && rx_remaining - tx_remaining < max_in_flight) { *(uint32_t*)(XIP_SSI_BASE + SSI_DR0_OFFSET) = *txbuf++; --tx_remaining; } if (can_get && rx_remaining) { *rxbuf++ = (uint8_t)*(uint32_t*)(XIP_SSI_BASE + SSI_DR0_OFFSET); --rx_remaining; } } flash_cs_force(1); flash_flush_cache_func(); flash_enable_xip_via_boot2(); } */ import "C" func enterBootloader() { C.reset_usb_boot(0, 0) } func doFlashCommand(tx []byte, rx []byte) error { if len(tx) != len(rx) { return errFlashInvalidWriteLength } C.flash_do_cmd( (*C.uint8_t)(unsafe.Pointer(&tx[0])), (*C.uint8_t)(unsafe.Pointer(&rx[0])), C.ulong(len(tx))) return nil } // Flash related code const memoryStart = C.XIP_BASE // memory start for purpose of erase func (f flashBlockDevice) writeAt(p []byte, off int64) (n int, err error) { if writeAddress(off)+uintptr(C.XIP_BASE) > FlashDataEnd() { return 0, errFlashCannotWritePastEOF } state := interrupt.Disable() defer interrupt.Restore(state) // rp2040 writes to offset, not actual address // e.g. real address 0x10003000 is written to at // 0x00003000 address := writeAddress(off) padded := flashPad(p, int(f.WriteBlockSize())) C.flash_range_write(C.uint32_t(address), (*C.uint8_t)(unsafe.Pointer(&padded[0])), C.ulong(len(padded))) return len(padded), nil } func (f flashBlockDevice) eraseBlocks(start, length int64) error { address := writeAddress(start * f.EraseBlockSize()) if address+uintptr(C.XIP_BASE) > FlashDataEnd() { return errFlashCannotErasePastEOF } state := interrupt.Disable() defer interrupt.Restore(state) C.flash_erase_blocks(C.uint32_t(address), C.ulong(length*f.EraseBlockSize())) return nil } ================================================ FILE: src/machine/machine_rp2040_rtc.go ================================================ //go:build rp2040 // Implementation based on code located here: // https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_rtc/rtc.c package machine import ( "device/rp" "errors" "runtime/interrupt" "unsafe" ) type rtcType rp.RTC_Type type rtcTime struct { Year int16 Month int8 Day int8 Dotw int8 Hour int8 Min int8 Sec int8 } var RTC = (*rtcType)(unsafe.Pointer(rp.RTC)) const ( second = 1 minute = 60 * second hour = 60 * minute day = 24 * hour ) var ( rtcAlarmRepeats bool rtcCallback func() rtcEpoch = rtcTime{ Year: 1970, Month: 1, Day: 1, Dotw: 4, Hour: 0, Min: 0, Sec: 0, } ) var ( ErrRtcDelayTooSmall = errors.New("RTC interrupt deplay is too small, shall be at least 1 second") ErrRtcDelayTooLarge = errors.New("RTC interrupt deplay is too large, shall be no more than 1 day") ) // SetInterrupt configures delayed and optionally recurring interrupt by real time clock. // // Delay is specified in whole seconds, allowed range depends on platform. // Zero delay disables previously configured interrupt, if any. // // RP2040 implementation allows delay to be up to 1 day, otherwise a respective error is emitted. func (rtc *rtcType) SetInterrupt(delay uint32, repeat bool, callback func()) error { // Verify delay range if delay > day { return ErrRtcDelayTooLarge } // De-configure delayed interrupt if delay is zero if delay == 0 { rtc.disableInterruptMatch() return nil } // Configure delayed interrupt rtc.setDivider() rtcAlarmRepeats = repeat rtcCallback = callback err := rtc.setTime(rtcEpoch) if err != nil { return err } rtc.setAlarm(toAlarmTime(delay), callback) return nil } func toAlarmTime(delay uint32) rtcTime { result := rtcEpoch remainder := delay + 1 // needed "+1", otherwise alarm fires one second too early if remainder >= hour { result.Hour = int8(remainder / hour) remainder %= hour } if remainder >= minute { result.Min = int8(remainder / minute) remainder %= minute } result.Sec = int8(remainder) return result } func (rtc *rtcType) setDivider() { // Get clk_rtc freq and make sure it is running rtcFreq := configuredFreq[clkRTC] if rtcFreq == 0 { panic("can not set RTC divider, clock is not running") } // Take rtc out of reset now that we know clk_rtc is running resetBlock(rp.RESETS_RESET_RTC) unresetBlockWait(rp.RESETS_RESET_RTC) // Set up the 1 second divider. // If rtc_freq is 400 then clkdiv_m1 should be 399 rtcFreq -= 1 // Check the freq is not too big to divide if rtcFreq > rp.RTC_CLKDIV_M1_CLKDIV_M1_Msk { panic("can not set RTC divider, clock frequency is too big to divide") } // Write divide value rtc.CLKDIV_M1.Set(rtcFreq) } // setTime configures RTC with supplied time, initialises and activates it. func (rtc *rtcType) setTime(t rtcTime) error { // Disable RTC and wait while it is still running rtc.CTRL.Set(0) for rtc.isActive() { } rtc.SETUP_0.Set((uint32(t.Year) << rp.RTC_SETUP_0_YEAR_Pos) | (uint32(t.Month) << rp.RTC_SETUP_0_MONTH_Pos) | (uint32(t.Day) << rp.RTC_SETUP_0_DAY_Pos)) rtc.SETUP_1.Set((uint32(t.Dotw) << rp.RTC_SETUP_1_DOTW_Pos) | (uint32(t.Hour) << rp.RTC_SETUP_1_HOUR_Pos) | (uint32(t.Min) << rp.RTC_SETUP_1_MIN_Pos) | (uint32(t.Sec) << rp.RTC_SETUP_1_SEC_Pos)) // Load setup values into RTC clock domain rtc.CTRL.SetBits(rp.RTC_CTRL_LOAD) // Enable RTC and wait for it to be running rtc.CTRL.SetBits(rp.RTC_CTRL_RTC_ENABLE) for !rtc.isActive() { } return nil } func (rtc *rtcType) isActive() bool { return rtc.CTRL.HasBits(rp.RTC_CTRL_RTC_ACTIVE) } // setAlarm configures alarm in RTC and arms it. // The callback is executed in the context of an interrupt handler, // so regular restructions for this sort of code apply: no blocking, no memory allocation, etc. func (rtc *rtcType) setAlarm(t rtcTime, callback func()) { rtc.disableInterruptMatch() // Clear all match enable bits rtc.IRQ_SETUP_0.ClearBits(rp.RTC_IRQ_SETUP_0_YEAR_ENA | rp.RTC_IRQ_SETUP_0_MONTH_ENA | rp.RTC_IRQ_SETUP_0_DAY_ENA) rtc.IRQ_SETUP_1.ClearBits(rp.RTC_IRQ_SETUP_1_DOTW_ENA | rp.RTC_IRQ_SETUP_1_HOUR_ENA | rp.RTC_IRQ_SETUP_1_MIN_ENA | rp.RTC_IRQ_SETUP_1_SEC_ENA) // Only add to setup if it isn't -1 and set the match enable bits for things we care about if t.Year >= 0 { rtc.IRQ_SETUP_0.SetBits(uint32(t.Year) << rp.RTC_SETUP_0_YEAR_Pos) rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_YEAR_ENA) } if t.Month >= 0 { rtc.IRQ_SETUP_0.SetBits(uint32(t.Month) << rp.RTC_SETUP_0_MONTH_Pos) rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_MONTH_ENA) } if t.Day >= 0 { rtc.IRQ_SETUP_0.SetBits(uint32(t.Day) << rp.RTC_SETUP_0_DAY_Pos) rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_DAY_ENA) } if t.Dotw >= 0 { rtc.IRQ_SETUP_1.SetBits(uint32(t.Dotw) << rp.RTC_SETUP_1_DOTW_Pos) rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_DOTW_ENA) } if t.Hour >= 0 { rtc.IRQ_SETUP_1.SetBits(uint32(t.Hour) << rp.RTC_SETUP_1_HOUR_Pos) rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_HOUR_ENA) } if t.Min >= 0 { rtc.IRQ_SETUP_1.SetBits(uint32(t.Min) << rp.RTC_SETUP_1_MIN_Pos) rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_MIN_ENA) } if t.Sec >= 0 { rtc.IRQ_SETUP_1.SetBits(uint32(t.Sec) << rp.RTC_SETUP_1_SEC_Pos) rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_SEC_ENA) } // Enable the IRQ at the proc interrupt.New(rp.IRQ_RTC_IRQ, rtcHandleInterrupt).Enable() // Enable the IRQ at the peri rtc.INTE.Set(rp.RTC_INTE_RTC) rtc.enableInterruptMatch() } func (rtc *rtcType) enableInterruptMatch() { // Set matching and wait for it to be enabled rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_MATCH_ENA) for !rtc.IRQ_SETUP_0.HasBits(rp.RTC_IRQ_SETUP_0_MATCH_ACTIVE) { } } func (rtc *rtcType) disableInterruptMatch() { // Disable matching and wait for it to stop being active rtc.IRQ_SETUP_0.ClearBits(rp.RTC_IRQ_SETUP_0_MATCH_ENA) for rtc.IRQ_SETUP_0.HasBits(rp.RTC_IRQ_SETUP_0_MATCH_ACTIVE) { } } func rtcHandleInterrupt(itr interrupt.Interrupt) { // Always disable the alarm to clear the current IRQ. // Even if it is a repeatable alarm, we don't want it to keep firing. // If it matches on a second it can keep firing for that second. RTC.disableInterruptMatch() // Call user callback function if rtcCallback != nil { rtcCallback() } if rtcAlarmRepeats { // If it is a repeatable alarm, reset time and re-enable the alarm. RTC.setTime(rtcEpoch) RTC.enableInterruptMatch() } } ================================================ FILE: src/machine/machine_rp2040_simulator.go ================================================ //go:build !baremetal && (ae_rp2040 || badger2040_w || badger2040 || challenger_rp2040 || elecrow_rp2040 || feather_rp2040 || gopher_badge || kb2040 || macropad_rp2040 || nano_rp2040 || pico || qtpy_rp2040 || thingplus_rp2040 || thumby || trinkey_qt2040 || tufty2040 || waveshare_rp2040_tiny || waveshare_rp2040_zero || xiao_rp2040) // Simulator support for the RP2040. // // This is *only* for the RP2040. RP2350 is a different chip with slightly // different characteristics. package machine var PWM0 = &timerType{ instance: 0, frequency: 200e6, bits: 16, prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128, 256}, // actually a continuing range, TODO channelPins: [][]Pin{ {GPIO0, GPIO16}, // channel A (0) {GPIO1, GPIO17}, // channel B (1) }, } var PWM1 = &timerType{ instance: 0, frequency: 200e6, bits: 16, prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128, 256}, channelPins: [][]Pin{ {GPIO2, GPIO18}, // channel A (0) {GPIO3, GPIO19}, // channel B (1) }, } var PWM2 = &timerType{ instance: 0, frequency: 200e6, bits: 16, prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128, 256}, channelPins: [][]Pin{ {GPIO4, GPIO20}, // channel A (0) {GPIO5, GPIO21}, // channel B (1) }, } var PWM3 = &timerType{ instance: 0, frequency: 200e6, bits: 16, prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128, 256}, channelPins: [][]Pin{ {GPIO6, GPIO22}, // channel A (0) {GPIO7, GPIO23}, // channel B (1) }, } var PWM4 = &timerType{ instance: 0, frequency: 200e6, bits: 16, prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128, 256}, channelPins: [][]Pin{ {GPIO8, GPIO24}, // channel A (0) {GPIO9, GPIO25}, // channel B (1) }, } var PWM5 = &timerType{ instance: 0, frequency: 200e6, bits: 16, prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128, 256}, channelPins: [][]Pin{ {GPIO10, GPIO26}, // channel A (0) {GPIO11, GPIO27}, // channel B (1) }, } var PWM6 = &timerType{ instance: 0, frequency: 200e6, bits: 16, prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128, 256}, channelPins: [][]Pin{ {GPIO12, GPIO28}, // channel A (0) {GPIO13, GPIO29}, // channel B (1) }, } var PWM7 = &timerType{ instance: 0, frequency: 200e6, bits: 16, prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128, 256}, channelPins: [][]Pin{ {GPIO14}, // channel A (0) {GPIO15}, // channel B (1) }, } var I2C0 = &I2C{ Bus: 0, PinsSCL: []Pin{GPIO1, GPIO5, GPIO9, GPIO13, GPIO17, GPIO21}, PinsSDA: []Pin{GPIO0, GPIO4, GPIO8, GPIO12, GPIO16, GPIO20}, } var I2C1 = &I2C{ Bus: 0, PinsSCL: []Pin{GPIO3, GPIO7, GPIO11, GPIO15, GPIO19, GPIO27}, PinsSDA: []Pin{GPIO2, GPIO6, GPIO10, GPIO14, GPIO18, GPIO26}, } ================================================ FILE: src/machine/machine_rp2040_usb.go ================================================ //go:build rp2040 package machine import ( "device/rp" "machine/usb" "runtime/interrupt" ) // Configure the USB peripheral. The config is here for compatibility with the UART interface. func (dev *USBDevice) Configure(config UARTConfig) { // Reset usb controller resetBlock(rp.RESETS_RESET_USBCTRL) unresetBlockWait(rp.RESETS_RESET_USBCTRL) // Clear any previous state in dpram just in case _usbDPSRAM.clear() // Enable USB interrupt at processor rp.USBCTRL_REGS.INTE.Set(0) intr := interrupt.New(rp.IRQ_USBCTRL_IRQ, handleUSBIRQ) intr.SetPriority(0x00) intr.Enable() irqSet(rp.IRQ_USBCTRL_IRQ, true) // Mux the controller to the onboard usb phy rp.USBCTRL_REGS.USB_MUXING.Set(rp.USBCTRL_REGS_USB_MUXING_TO_PHY | rp.USBCTRL_REGS_USB_MUXING_SOFTCON) // Force VBUS detect so the device thinks it is plugged into a host rp.USBCTRL_REGS.USB_PWR.Set(rp.USBCTRL_REGS_USB_PWR_VBUS_DETECT | rp.USBCTRL_REGS_USB_PWR_VBUS_DETECT_OVERRIDE_EN) // Enable the USB controller in device mode. rp.USBCTRL_REGS.MAIN_CTRL.Set(rp.USBCTRL_REGS_MAIN_CTRL_CONTROLLER_EN) // Enable an interrupt per EP0 transaction rp.USBCTRL_REGS.SIE_CTRL.Set(rp.USBCTRL_REGS_SIE_CTRL_EP0_INT_1BUF) // Enable interrupts for when a buffer is done, when the bus is reset, // and when a setup packet is received rp.USBCTRL_REGS.INTE.Set(rp.USBCTRL_REGS_INTE_BUFF_STATUS | rp.USBCTRL_REGS_INTE_BUS_RESET | rp.USBCTRL_REGS_INTE_SETUP_REQ) // Present full speed device by enabling pull up on DP rp.USBCTRL_REGS.SIE_CTRL.SetBits(rp.USBCTRL_REGS_SIE_CTRL_PULLUP_EN) } func handleUSBIRQ(intr interrupt.Interrupt) { status := rp.USBCTRL_REGS.INTS.Get() // Setup packet received if (status & rp.USBCTRL_REGS_INTS_SETUP_REQ) > 0 { rp.USBCTRL_REGS.SIE_STATUS.Set(rp.USBCTRL_REGS_SIE_STATUS_SETUP_REC) setup := usb.NewSetup(_usbDPSRAM.setupBytes()) ok := false if (setup.BmRequestType & usb.REQUEST_TYPE) == usb.REQUEST_STANDARD { // Standard Requests ok = handleStandardSetup(setup) } else { // Class Interface Requests if setup.WIndex < uint16(len(usbSetupHandler)) && usbSetupHandler[setup.WIndex] != nil { ok = usbSetupHandler[setup.WIndex](setup) } } if !ok { // Stall endpoint? USBDev.SetStallEPIn(0) } } // Buffer status, one or more buffers have completed if (status & rp.USBCTRL_REGS_INTS_BUFF_STATUS) > 0 { if sendOnEP0DATADONE.offset > 0 { ep := uint32(0) data := sendOnEP0DATADONE.data count := len(data) - sendOnEP0DATADONE.offset if ep == 0 && count > usb.EndpointPacketSize { count = usb.EndpointPacketSize } sendViaEPIn(ep, data[sendOnEP0DATADONE.offset:], count) sendOnEP0DATADONE.offset += count if sendOnEP0DATADONE.offset == len(data) { sendOnEP0DATADONE.offset = 0 } } s2 := rp.USBCTRL_REGS.BUFF_STATUS.Get() // OUT (PC -> rp2040) for i := 0; i < 16; i++ { if s2&(1<<(i*2+1)) > 0 { buf := handleEndpointRx(uint32(i)) if usbRxHandler[i] == nil || usbRxHandler[i](buf) { AckUsbOutTransfer(uint32(i)) } } } // IN (rp2040 -> PC) for i := 0; i < 16; i++ { if s2&(1<<(i*2)) > 0 { if usbTxHandler[i] != nil { usbTxHandler[i]() } } } rp.USBCTRL_REGS.BUFF_STATUS.Set(s2) } // Bus is reset if (status & rp.USBCTRL_REGS_INTS_BUS_RESET) > 0 { rp.USBCTRL_REGS.SIE_STATUS.Set(rp.USBCTRL_REGS_SIE_STATUS_BUS_RESET) fixRP2040UsbDeviceEnumeration() rp.USBCTRL_REGS.ADDR_ENDP.Set(0) initEndpoint(0, usb.ENDPOINT_TYPE_CONTROL) } } func handleUSBSetAddress(setup usb.Setup) bool { // Using 570μs timeout which is exactly the same as SAMD21. const ackTimeout = 570 rp.USBCTRL_REGS.SIE_STATUS.Set(rp.USBCTRL_REGS_SIE_STATUS_ACK_REC) sendUSBPacket(0, []byte{}, 0) // Wait for transfer to complete with a timeout. t := timer.timeElapsed() for (rp.USBCTRL_REGS.SIE_STATUS.Get() & rp.USBCTRL_REGS_SIE_STATUS_ACK_REC) == 0 { if dt := timer.timeElapsed() - t; dt >= ackTimeout { return false } } // Set the device address to that requested by host. rp.USBCTRL_REGS.ADDR_ENDP.Set(uint32(setup.WValueL) & rp.USBCTRL_REGS_ADDR_ENDP_ADDRESS_Msk) return true } func armEPZeroStall() { rp.USBCTRL_REGS.EP_STALL_ARM.Set(rp.USBCTRL_REGS_EP_STALL_ARM_EP0_IN) } ================================================ FILE: src/machine/machine_rp2040_usb_fix_usb_device_enumeration.go ================================================ //go:build rp2040 package machine import ( "device/arm" "device/rp" ) // https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/rp2040_usb_device_enumeration.c // According to errata RP2040-E5: // "It is safe (and inexpensive) to enable the software workaround even when using versions of RP2040 // which include the fix in hardware." // So let us always use the software fix. func fixRP2040UsbDeviceEnumeration() { // After coming out of reset, the hardware expects 800us of LS_J (linestate J) time // before it will move to the connected state. However on a hub that broadcasts packets // for other devices this isn't the case. The plan here is to wait for the end of the bus // reset, force an LS_J for 1ms and then switch control back to the USB phy. Unfortunately // this requires us to use GPIO15 as there is no other way to force the input path. // We only need to force DP as DM can be left at zero. It will be gated off by GPIO // logic if it isn't func selected. // Wait SE0 phase will call force ls_j phase which will call finish phase hw_enumeration_fix_wait_se0() } func hw_enumeration_fix_wait_se0() { // Wait for SE0 to end (i.e. the host to stop resetting). This reset can last quite long. // 10-15ms so we are going to set a timer callback. // if timer pool disabled, or no timer available, have to busy wait. hw_enumeration_fix_busy_wait_se0() } const ( LS_SE0 = 0b00 LS_J = 0b01 LS_K = 0b10 LS_SE1 = 0b11 ) const ( dp = 15 ) var ( gpioCtrlPrev uint32 padCtrlPrev uint32 ) func hw_enumeration_fix_busy_wait_se0() { for ((rp.USBCTRL_REGS.SIE_STATUS.Get() & rp.USBCTRL_REGS_SIE_STATUS_LINE_STATE_Msk) >> rp.USBCTRL_REGS_SIE_STATUS_LINE_STATE_Pos) == LS_SE0 { } // Now force LS_J (next stage of fix) hw_enumeration_fix_force_ls_j() } func hw_enumeration_fix_force_ls_j() { // DM must be 0 for this to work. This is true if it is selected // to any other function. fn 8 on this pin is only for debug so shouldn't // be selected // Before changing any pin state, take a copy of the current gpio control register gpioCtrlPrev = ioBank0.io[dp].ctrl.Get() // Also take a copy of the pads register padCtrlPrev = padsBank0.io[dp].Get() // Enable bus keep and force pin to tristate, so USB DP muxing doesn't affect // pin state padsBank0.io[dp].SetBits(rp.PADS_BANK0_GPIO0_PUE | rp.PADS_BANK0_GPIO0_PDE) ioBank0.io[dp].ctrl.ReplaceBits(rp.IO_BANK0_GPIO0_CTRL_OEOVER_DISABLE, rp.IO_BANK0_GPIO0_CTRL_OEOVER_Msk>>rp.IO_BANK0_GPIO0_CTRL_OEOVER_Pos, rp.IO_BANK0_GPIO0_CTRL_OEOVER_Pos) // Select function 8 (USB debug muxing) without disturbing other controls ioBank0.io[dp].ctrl.ReplaceBits(8, rp.IO_BANK0_GPIO0_CTRL_FUNCSEL_Msk>>rp.IO_BANK0_GPIO0_CTRL_FUNCSEL_Pos, rp.IO_BANK0_GPIO0_CTRL_FUNCSEL_Pos) // J state is a differential 1 for a full speed device so // DP = 1 and DM = 0. Don't actually need to set DM low as it // is already gated assuming it isn't funcseld. ioBank0.io[dp].ctrl.ReplaceBits(rp.IO_BANK0_GPIO1_CTRL_INOVER_HIGH, rp.IO_BANK0_GPIO1_CTRL_INOVER_Msk>>rp.IO_BANK0_GPIO1_CTRL_INOVER_Pos, rp.IO_BANK0_GPIO1_CTRL_INOVER_Pos) // Force PHY pull up to stay before switching away from the phy rp.USBCTRL_REGS.USBPHY_DIRECT.SetBits(rp.USBCTRL_REGS_USBPHY_DIRECT_DP_PULLUP_EN) rp.USBCTRL_REGS.USBPHY_DIRECT_OVERRIDE.SetBits(rp.USBCTRL_REGS_USBPHY_DIRECT_OVERRIDE_DP_PULLUP_EN_OVERRIDE_EN) // Switch to GPIO phy with LS_J forced rp.USBCTRL_REGS.USB_MUXING.Set(rp.USBCTRL_REGS_USB_MUXING_TO_DIGITAL_PAD | rp.USBCTRL_REGS_USB_MUXING_SOFTCON) // LS_J is now forced but while loop to wait ~800us here just to check waitCycles(25000) // if timer pool disabled, or no timer available, have to busy wait. hw_enumeration_fix_finish() } func hw_enumeration_fix_finish() { // Should think we are connected now for (rp.USBCTRL_REGS.SIE_STATUS.Get() & rp.USBCTRL_REGS_SIE_STATUS_CONNECTED) != rp.USBCTRL_REGS_SIE_STATUS_CONNECTED { } // Switch back to USB phy rp.USBCTRL_REGS.USB_MUXING.Set(rp.USBCTRL_REGS_USB_MUXING_TO_PHY | rp.USBCTRL_REGS_USB_MUXING_SOFTCON) // Get rid of DP pullup override rp.USBCTRL_REGS.USBPHY_DIRECT_OVERRIDE.ClearBits(rp.USBCTRL_REGS_USBPHY_DIRECT_OVERRIDE_DP_PULLUP_EN_OVERRIDE_EN) // Finally, restore the gpio ctrl value back to GPIO15 ioBank0.io[dp].ctrl.Set(gpioCtrlPrev) // Restore the pad ctrl value padsBank0.io[dp].Set(padCtrlPrev) } func waitCycles(n int) { for n > 0 { arm.Asm("nop") n-- } } ================================================ FILE: src/machine/machine_rp2350_rom.go ================================================ //go:build tinygo && rp2350 package machine import ( "runtime/interrupt" "unsafe" ) /* typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned long uint32_t; typedef unsigned long size_t; typedef unsigned long uintptr_t; typedef long int intptr_t; typedef const volatile uint16_t io_ro_16; typedef const volatile uint32_t io_ro_32; typedef volatile uint16_t io_rw_16; typedef volatile uint32_t io_rw_32; typedef volatile uint32_t io_wo_32; #define false 0 #define true 1 typedef int bool; #define ram_func __attribute__((section(".ramfuncs"),noinline)) typedef void (*flash_exit_xip_fn)(void); typedef void (*flash_flush_cache_fn)(void); typedef void (*flash_connect_internal_fn)(void); typedef void (*flash_range_erase_fn)(uint32_t, size_t, uint32_t, uint16_t); typedef void (*flash_range_program_fn)(uint32_t, const uint8_t*, size_t); static inline __attribute__((always_inline)) void __compiler_memory_barrier(void) { __asm__ volatile ("" : : : "memory"); } // https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf // 13.9. Predefined OTP Data Locations // OTP_DATA: FLASH_DEVINFO Register #define OTP_DATA_FLASH_DEVINFO_CS0_SIZE_BITS 0x0F00 #define OTP_DATA_FLASH_DEVINFO_CS0_SIZE_LSB 8 #define OTP_DATA_FLASH_DEVINFO_CS1_SIZE_BITS 0xF000 #define OTP_DATA_FLASH_DEVINFO_CS1_SIZE_LSB 12 // https://github.com/raspberrypi/pico-sdk // src/rp2350/hardware_regs/include/hardware/regs/addressmap.h #define REG_ALIAS_RW_BITS (0x0 << 12) #define REG_ALIAS_XOR_BITS (0x1 << 12) #define REG_ALIAS_SET_BITS (0x2 << 12) #define REG_ALIAS_CLR_BITS (0x3 << 12) #define XIP_BASE 0x10000000 #define XIP_QMI_BASE 0x400d0000 #define IO_QSPI_BASE 0x40030000 #define BOOTRAM_BASE 0x400e0000 // https://github.com/raspberrypi/pico-sdk // src/rp2_common/hardware_base/include/hardware/address_mapped.h #define hw_alias_check_addr(addr) ((uintptr_t)(addr)) #define hw_set_alias_untyped(addr) ((void *)(REG_ALIAS_SET_BITS + hw_alias_check_addr(addr))) #define hw_clear_alias_untyped(addr) ((void *)(REG_ALIAS_CLR_BITS + hw_alias_check_addr(addr))) #define hw_xor_alias_untyped(addr) ((void *)(REG_ALIAS_XOR_BITS + hw_alias_check_addr(addr))) __attribute__((always_inline)) static void hw_set_bits(io_rw_32 *addr, uint32_t mask) { *(io_rw_32 *) hw_set_alias_untyped((volatile void *) addr) = mask; } __attribute__((always_inline)) static void hw_clear_bits(io_rw_32 *addr, uint32_t mask) { *(io_rw_32 *) hw_clear_alias_untyped((volatile void *) addr) = mask; } __attribute__((always_inline)) static void hw_xor_bits(io_rw_32 *addr, uint32_t mask) { *(io_rw_32 *) hw_xor_alias_untyped((volatile void *) addr) = mask; } __attribute__((always_inline)) static void hw_write_masked(io_rw_32 *addr, uint32_t values, uint32_t write_mask) { hw_xor_bits(addr, (*addr ^ values) & write_mask); } // https://github.com/raspberrypi/pico-sdk // src/rp2_common/pico_platform_compiler/include/pico/platform/compiler.h #define pico_default_asm_volatile(...) __asm volatile (".syntax unified\n" __VA_ARGS__) // https://github.com/raspberrypi/pico-sdk // src/rp2350/pico_platform/include/pico/platform.h static bool pico_processor_state_is_nonsecure(void) { // // todo add a define to disable NS checking at all? // // IDAU-Exempt addresses return S=1 when tested in the Secure state, // // whereas executing a tt in the NonSecure state will always return S=0. // uint32_t tt; // pico_default_asm_volatile ( // "movs %0, #0\n" // "tt %0, %0\n" // : "=r" (tt) : : "cc" // ); // return !(tt & (1u << 22)); return false; } // https://github.com/raspberrypi/pico-sdk // src/rp2_common/pico_bootrom/include/pico/bootrom_constants.h // RP2040 & RP2350 #define ROM_DATA_SOFTWARE_GIT_REVISION ROM_TABLE_CODE('G', 'R') #define ROM_FUNC_FLASH_ENTER_CMD_XIP ROM_TABLE_CODE('C', 'X') #define ROM_FUNC_FLASH_EXIT_XIP ROM_TABLE_CODE('E', 'X') #define ROM_FUNC_FLASH_FLUSH_CACHE ROM_TABLE_CODE('F', 'C') #define ROM_FUNC_CONNECT_INTERNAL_FLASH ROM_TABLE_CODE('I', 'F') #define ROM_FUNC_FLASH_RANGE_ERASE ROM_TABLE_CODE('R', 'E') #define ROM_FUNC_FLASH_RANGE_PROGRAM ROM_TABLE_CODE('R', 'P') // RP2350 only #define ROM_FUNC_PICK_AB_PARTITION ROM_TABLE_CODE('A', 'B') #define ROM_FUNC_CHAIN_IMAGE ROM_TABLE_CODE('C', 'I') #define ROM_FUNC_EXPLICIT_BUY ROM_TABLE_CODE('E', 'B') #define ROM_FUNC_FLASH_RUNTIME_TO_STORAGE_ADDR ROM_TABLE_CODE('F', 'A') #define ROM_DATA_FLASH_DEVINFO16_PTR ROM_TABLE_CODE('F', 'D') #define ROM_FUNC_FLASH_OP ROM_TABLE_CODE('F', 'O') #define ROM_FUNC_GET_B_PARTITION ROM_TABLE_CODE('G', 'B') #define ROM_FUNC_GET_PARTITION_TABLE_INFO ROM_TABLE_CODE('G', 'P') #define ROM_FUNC_GET_SYS_INFO ROM_TABLE_CODE('G', 'S') #define ROM_FUNC_GET_UF2_TARGET_PARTITION ROM_TABLE_CODE('G', 'U') #define ROM_FUNC_LOAD_PARTITION_TABLE ROM_TABLE_CODE('L', 'P') #define ROM_FUNC_OTP_ACCESS ROM_TABLE_CODE('O', 'A') #define ROM_DATA_PARTITION_TABLE_PTR ROM_TABLE_CODE('P', 'T') #define ROM_FUNC_FLASH_RESET_ADDRESS_TRANS ROM_TABLE_CODE('R', 'A') #define ROM_FUNC_REBOOT ROM_TABLE_CODE('R', 'B') #define ROM_FUNC_SET_ROM_CALLBACK ROM_TABLE_CODE('R', 'C') #define ROM_FUNC_SECURE_CALL ROM_TABLE_CODE('S', 'C') #define ROM_FUNC_SET_NS_API_PERMISSION ROM_TABLE_CODE('S', 'P') #define ROM_FUNC_BOOTROM_STATE_RESET ROM_TABLE_CODE('S', 'R') #define ROM_FUNC_SET_BOOTROM_STACK ROM_TABLE_CODE('S', 'S') #define ROM_DATA_SAVED_XIP_SETUP_FUNC_PTR ROM_TABLE_CODE('X', 'F') #define ROM_FUNC_FLASH_SELECT_XIP_READ_MODE ROM_TABLE_CODE('X', 'M') #define ROM_FUNC_VALIDATE_NS_BUFFER ROM_TABLE_CODE('V', 'B') #define BOOTSEL_FLAG_GPIO_PIN_SPECIFIED 0x20 #define BOOTROM_FUNC_TABLE_OFFSET 0x14 // todo remove this (or #ifdef it for A1/A2) #define BOOTROM_IS_A2() ((*(volatile uint8_t *)0x13) == 2) #define BOOTROM_WELL_KNOWN_PTR_SIZE (BOOTROM_IS_A2() ? 2 : 4) #define BOOTROM_VTABLE_OFFSET 0x00 #define BOOTROM_TABLE_LOOKUP_OFFSET (BOOTROM_FUNC_TABLE_OFFSET + BOOTROM_WELL_KNOWN_PTR_SIZE) // https://github.com/raspberrypi/pico-sdk // src/common/boot_picoboot_headers/include/boot/picoboot_constants.h // values 0-7 are secure/non-secure #define REBOOT2_FLAG_REBOOT_TYPE_NORMAL 0x0 // param0 = diagnostic partition #define REBOOT2_FLAG_REBOOT_TYPE_BOOTSEL 0x2 // param0 = bootsel_flags, param1 = gpio_config #define REBOOT2_FLAG_REBOOT_TYPE_RAM_IMAGE 0x3 // param0 = image_base, param1 = image_end #define REBOOT2_FLAG_REBOOT_TYPE_FLASH_UPDATE 0x4 // param0 = update_base #define REBOOT2_FLAG_NO_RETURN_ON_SUCCESS 0x100 #define RT_FLAG_FUNC_ARM_SEC 0x0004 #define RT_FLAG_FUNC_ARM_NONSEC 0x0010 #define RT_FLAG_DATA 0x0040 // https://github.com/raspberrypi/pico-sdk // src/rp2_common/pico_bootrom/include/pico/bootrom.h #define ROM_TABLE_CODE(c1, c2) ((c1) | ((c2) << 8)) typedef void *(*rom_table_lookup_fn)(uint32_t code, uint32_t mask); __attribute__((always_inline)) static void *rom_func_lookup_inline(uint32_t code) { rom_table_lookup_fn rom_table_lookup = (rom_table_lookup_fn) (uintptr_t)*(uint16_t*)(BOOTROM_TABLE_LOOKUP_OFFSET); if (pico_processor_state_is_nonsecure()) { return rom_table_lookup(code, RT_FLAG_FUNC_ARM_NONSEC); } else { return rom_table_lookup(code, RT_FLAG_FUNC_ARM_SEC); } } __attribute__((always_inline)) static void *rom_data_lookup_inline(uint32_t code) { rom_table_lookup_fn rom_table_lookup = (rom_table_lookup_fn) (uintptr_t)*(uint16_t*)(BOOTROM_TABLE_LOOKUP_OFFSET); return rom_table_lookup(code, RT_FLAG_DATA); } typedef int (*rom_reboot_fn)(uint32_t flags, uint32_t delay_ms, uint32_t p0, uint32_t p1); __attribute__((always_inline)) int rom_reboot(uint32_t flags, uint32_t delay_ms, uint32_t p0, uint32_t p1) { rom_reboot_fn func = (rom_reboot_fn) rom_func_lookup_inline(ROM_FUNC_REBOOT); return func(flags, delay_ms, p0, p1); } // https://github.com/raspberrypi/pico-sdk // src/rp2_common/pico_bootrom/bootrom.c void reset_usb_boot(uint32_t usb_activity_gpio_pin_mask, uint32_t disable_interface_mask) { uint32_t flags = disable_interface_mask; if (usb_activity_gpio_pin_mask) { flags |= BOOTSEL_FLAG_GPIO_PIN_SPECIFIED; // the parameter is actually the gpio number, but we only care if BOOTSEL_FLAG_GPIO_PIN_SPECIFIED usb_activity_gpio_pin_mask = (uint32_t)__builtin_ctz(usb_activity_gpio_pin_mask); } rom_reboot(REBOOT2_FLAG_REBOOT_TYPE_BOOTSEL | REBOOT2_FLAG_NO_RETURN_ON_SUCCESS, 10, flags, usb_activity_gpio_pin_mask); __builtin_unreachable(); } // https://github.com/raspberrypi/pico-sdk // src/rp2350/hardware_regs/include/hardware/regs/qmi.h #define QMI_DIRECT_CSR_EN_BITS 0x00000001 #define QMI_DIRECT_CSR_RXEMPTY_BITS 0x00010000 #define QMI_DIRECT_CSR_TXFULL_BITS 0x00000400 #define QMI_M1_WFMT_RESET 0x00001000 #define QMI_M1_WCMD_RESET 0x0000a002 // https://github.com/raspberrypi/pico-sdk // src/rp2350/hardware_regs/include/hardware/regs/io_qspi.h #define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS 0x00003000 #define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_LSB 12 #define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_LOW 0x2 #define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_HIGH 0x3 // https://github.com/raspberrypi/pico-sdk // src/rp2350/hardware_structs/include/hardware/structs/io_qspi.h typedef struct { io_rw_32 inte; // IO_QSPI_PROC0_INTE io_rw_32 intf; // IO_QSPI_PROC0_INTF io_ro_32 ints; // IO_QSPI_PROC0_INTS } io_qspi_irq_ctrl_hw_t; typedef struct { io_ro_32 status; // IO_QSPI_GPIO_QSPI_SCLK_STATUS io_rw_32 ctrl; // IO_QSPI_GPIO_QSPI_SCLK_CTRL } io_qspi_status_ctrl_hw_t; typedef struct { io_ro_32 usbphy_dp_status; // IO_QSPI_USBPHY_DP_STATUS io_rw_32 usbphy_dp_ctrl; // IO_QSPI_USBPHY_DP_CTRL io_ro_32 usbphy_dm_status; // IO_QSPI_USBPHY_DM_STATUS io_rw_32 usbphy_dm_ctrl; // IO_QSPI_USBPHY_DM_CTRL io_qspi_status_ctrl_hw_t io[6]; uint32_t _pad0[112]; io_ro_32 irqsummary_proc0_secure; // IO_QSPI_IRQSUMMARY_PROC0_SECURE io_ro_32 irqsummary_proc0_nonsecure; // IO_QSPI_IRQSUMMARY_PROC0_NONSECURE io_ro_32 irqsummary_proc1_secure; // IO_QSPI_IRQSUMMARY_PROC1_SECURE io_ro_32 irqsummary_proc1_nonsecure; // IO_QSPI_IRQSUMMARY_PROC1_NONSECURE io_ro_32 irqsummary_dormant_wake_secure; // IO_QSPI_IRQSUMMARY_DORMANT_WAKE_SECURE io_ro_32 irqsummary_dormant_wake_nonsecure; // IO_QSPI_IRQSUMMARY_DORMANT_WAKE_NONSECURE io_rw_32 intr; // IO_QSPI_INTR union { struct { io_qspi_irq_ctrl_hw_t proc0_irq_ctrl; io_qspi_irq_ctrl_hw_t proc1_irq_ctrl; io_qspi_irq_ctrl_hw_t dormant_wake_irq_ctrl; }; io_qspi_irq_ctrl_hw_t irq_ctrl[3]; }; } io_qspi_hw_t; #define io_qspi_hw ((io_qspi_hw_t *)IO_QSPI_BASE) // https://github.com/raspberrypi/pico-sdk // src/rp2350/hardware_structs/include/hardware/structs/qmi.h typedef struct { io_rw_32 timing; // QMI_M0_TIMING io_rw_32 rfmt; // QMI_M0_RFMT io_rw_32 rcmd; // QMI_M0_RCMD io_rw_32 wfmt; // QMI_M0_WFMT io_rw_32 wcmd; // QMI_M0_WCMD } qmi_mem_hw_t; typedef struct { io_rw_32 direct_csr; // QMI_DIRECT_CSR io_wo_32 direct_tx; // QMI_DIRECT_TX io_ro_32 direct_rx; // QMI_DIRECT_RX qmi_mem_hw_t m[2]; io_rw_32 atrans[8]; // QMI_ATRANS0 } qmi_hw_t; #define qmi_hw ((qmi_hw_t *)XIP_QMI_BASE) // https://github.com/raspberrypi/pico-sdk // src/rp2_common/hardware_xip_cache/include/hardware/xip_cache.h // Noop unless using XIP Cache-as-SRAM // Non-noop version in src/rp2_common/hardware_xip_cache/xip_cache.c static inline void xip_cache_clean_all(void) {} // https://github.com/raspberrypi/pico-sdk // src/rp2_common/hardware_flash/include/hardware/flash.h #define FLASH_PAGE_SIZE (1u << 8) #define FLASH_SECTOR_SIZE (1u << 12) #define FLASH_BLOCK_SIZE (1u << 16) // https://github.com/raspberrypi/pico-sdk // src/rp2_common/hardware_flash/flash.c #define BOOT2_SIZE_WORDS 64 #define FLASH_BLOCK_ERASE_CMD 0xd8 static uint32_t boot2_copyout[BOOT2_SIZE_WORDS]; static bool boot2_copyout_valid = false; static ram_func void flash_init_boot2_copyout(void) { if (boot2_copyout_valid) return; for (int i = 0; i < BOOT2_SIZE_WORDS; ++i) boot2_copyout[i] = ((uint32_t *)BOOTRAM_BASE)[i]; __compiler_memory_barrier(); boot2_copyout_valid = true; } static ram_func void flash_enable_xip_via_boot2(void) { ((void (*)(void))((intptr_t)boot2_copyout+1))(); } // This is a static symbol because the layout of FLASH_DEVINFO is liable to change from device to // device, so fields must have getters/setters. static io_rw_16 * ram_func flash_devinfo_ptr(void) { // Note the lookup returns a pointer to a 32-bit pointer literal in the ROM io_rw_16 **p = (io_rw_16 **) rom_data_lookup_inline(ROM_DATA_FLASH_DEVINFO16_PTR); return *p; } // This is a RAM function because may be called during flash programming to enable save/restore of // QMI window 1 registers on RP2350: uint8_t ram_func flash_devinfo_get_cs_size(uint8_t cs) { io_ro_16 *devinfo = (io_ro_16 *) flash_devinfo_ptr(); if (cs == 0u) { return (uint8_t) ( (*devinfo & OTP_DATA_FLASH_DEVINFO_CS0_SIZE_BITS) >> OTP_DATA_FLASH_DEVINFO_CS0_SIZE_LSB ); } else { return (uint8_t) ( (*devinfo & OTP_DATA_FLASH_DEVINFO_CS1_SIZE_BITS) >> OTP_DATA_FLASH_DEVINFO_CS1_SIZE_LSB ); } } // This is specifically for saving/restoring the registers modified by RP2350 // flash_exit_xip() ROM func, not the entirety of the QMI window state. typedef struct flash_rp2350_qmi_save_state { uint32_t timing; uint32_t rcmd; uint32_t rfmt; } flash_rp2350_qmi_save_state_t; static ram_func void flash_rp2350_save_qmi_cs1(flash_rp2350_qmi_save_state_t *state) { state->timing = qmi_hw->m[1].timing; state->rcmd = qmi_hw->m[1].rcmd; state->rfmt = qmi_hw->m[1].rfmt; } static ram_func void flash_rp2350_restore_qmi_cs1(const flash_rp2350_qmi_save_state_t *state) { if (flash_devinfo_get_cs_size(1) == 0) { // Case 1: The RP2350 ROM sets QMI to a clean (03h read) configuration // during flash_exit_xip(), even though when CS1 is not enabled via // FLASH_DEVINFO it does not issue an XIP exit sequence to CS1. In // this case, restore the original register config for CS1 as it is // still the correct config. qmi_hw->m[1].timing = state->timing; qmi_hw->m[1].rcmd = state->rcmd; qmi_hw->m[1].rfmt = state->rfmt; } else { // Case 2: If RAM is attached to CS1, and the ROM has issued an XIP // exit sequence to it, then the ROM re-initialisation of the QMI // registers has actually not gone far enough. The old XIP write mode // is no longer valid when the QSPI RAM is returned to a serial // command state. Restore the default 02h serial write command config. qmi_hw->m[1].wfmt = QMI_M1_WFMT_RESET; qmi_hw->m[1].wcmd = QMI_M1_WCMD_RESET; } } void ram_func flash_cs_force(bool high) { uint32_t field_val = high ? IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_HIGH : IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_LOW; hw_write_masked(&io_qspi_hw->io[1].ctrl, field_val << IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_LSB, IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS ); } // Adapted from flash_range_program() void ram_func flash_range_write(uint32_t offset, const uint8_t *data, size_t count) { flash_connect_internal_fn flash_connect_internal_func = (flash_connect_internal_fn)rom_func_lookup_inline(ROM_FUNC_CONNECT_INTERNAL_FLASH); flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_EXIT_XIP); flash_range_program_fn flash_range_program_func = (flash_range_program_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_RANGE_PROGRAM); flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); flash_init_boot2_copyout(); xip_cache_clean_all(); flash_rp2350_qmi_save_state_t qmi_save; flash_rp2350_save_qmi_cs1(&qmi_save); __compiler_memory_barrier(); flash_connect_internal_func(); flash_exit_xip_func(); flash_range_program_func(offset, data, count); flash_flush_cache_func(); // Note this is needed to remove CSn IO force as well as cache flushing flash_enable_xip_via_boot2(); flash_rp2350_restore_qmi_cs1(&qmi_save); } // Adapted from flash_range_erase() void ram_func flash_erase_blocks(uint32_t offset, size_t count) { flash_connect_internal_fn flash_connect_internal_func = (flash_connect_internal_fn)rom_func_lookup_inline(ROM_FUNC_CONNECT_INTERNAL_FLASH); flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_EXIT_XIP); flash_range_erase_fn flash_range_erase_func = (flash_range_erase_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_RANGE_ERASE); flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); flash_init_boot2_copyout(); // Commit any pending writes to external RAM, to avoid losing them in the subsequent flush: xip_cache_clean_all(); flash_rp2350_qmi_save_state_t qmi_save; flash_rp2350_save_qmi_cs1(&qmi_save); // No flash accesses after this point __compiler_memory_barrier(); flash_connect_internal_func(); flash_exit_xip_func(); flash_range_erase_func(offset, count, FLASH_BLOCK_SIZE, FLASH_BLOCK_ERASE_CMD); flash_flush_cache_func(); // Note this is needed to remove CSn IO force as well as cache flushing flash_enable_xip_via_boot2(); flash_rp2350_restore_qmi_cs1(&qmi_save); } void ram_func flash_do_cmd(const uint8_t *txbuf, uint8_t *rxbuf, size_t count) { flash_connect_internal_fn flash_connect_internal_func = (flash_connect_internal_fn)rom_func_lookup_inline(ROM_FUNC_CONNECT_INTERNAL_FLASH); flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_EXIT_XIP); flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); flash_init_boot2_copyout(); xip_cache_clean_all(); flash_rp2350_qmi_save_state_t qmi_save; flash_rp2350_save_qmi_cs1(&qmi_save); __compiler_memory_barrier(); flash_connect_internal_func(); flash_exit_xip_func(); flash_cs_force(0); size_t tx_remaining = count; size_t rx_remaining = count; // QMI version -- no need to bound FIFO contents as QMI stalls on full DIRECT_RX. hw_set_bits(&qmi_hw->direct_csr, QMI_DIRECT_CSR_EN_BITS); while (tx_remaining || rx_remaining) { uint32_t flags = qmi_hw->direct_csr; bool can_put = !(flags & QMI_DIRECT_CSR_TXFULL_BITS); bool can_get = !(flags & QMI_DIRECT_CSR_RXEMPTY_BITS); if (can_put && tx_remaining) { qmi_hw->direct_tx = *txbuf++; --tx_remaining; } if (can_get && rx_remaining) { *rxbuf++ = (uint8_t)qmi_hw->direct_rx; --rx_remaining; } } hw_clear_bits(&qmi_hw->direct_csr, QMI_DIRECT_CSR_EN_BITS); flash_cs_force(1); flash_flush_cache_func(); flash_enable_xip_via_boot2(); flash_rp2350_restore_qmi_cs1(&qmi_save); } */ import "C" func enterBootloader() { C.reset_usb_boot(0, 0) } func doFlashCommand(tx []byte, rx []byte) error { if len(tx) != len(rx) { return errFlashInvalidWriteLength } C.flash_do_cmd( (*C.uint8_t)(unsafe.Pointer(&tx[0])), (*C.uint8_t)(unsafe.Pointer(&rx[0])), C.ulong(len(tx))) return nil } // Flash related code const memoryStart = C.XIP_BASE // memory start for purpose of erase func (f flashBlockDevice) writeAt(p []byte, off int64) (n int, err error) { if writeAddress(off)+uintptr(C.XIP_BASE) > FlashDataEnd() { return 0, errFlashCannotWritePastEOF } state := interrupt.Disable() defer interrupt.Restore(state) // rp2350 writes to offset, not actual address // e.g. real address 0x10003000 is written to at // 0x00003000 address := writeAddress(off) padded := flashPad(p, int(f.WriteBlockSize())) C.flash_range_write(C.uint32_t(address), (*C.uint8_t)(unsafe.Pointer(&padded[0])), C.ulong(len(padded))) return len(padded), nil } func (f flashBlockDevice) eraseBlocks(start, length int64) error { address := writeAddress(start * f.EraseBlockSize()) if address+uintptr(C.XIP_BASE) > FlashDataEnd() { return errFlashCannotErasePastEOF } state := interrupt.Disable() defer interrupt.Restore(state) C.flash_erase_blocks(C.uint32_t(address), C.ulong(length*f.EraseBlockSize())) return nil } ================================================ FILE: src/machine/machine_rp2350_usb.go ================================================ //go:build rp2350 package machine import ( "device/rp" "machine/usb" "runtime/interrupt" ) // Configure the USB peripheral. The config is here for compatibility with the UART interface. func (dev *USBDevice) Configure(config UARTConfig) { // Reset usb controller resetBlock(rp.RESETS_RESET_USBCTRL) unresetBlockWait(rp.RESETS_RESET_USBCTRL) // Clear any previous state in dpram just in case _usbDPSRAM.clear() // Enable USB interrupt at processor rp.USB.INTE.Set(0) intr := interrupt.New(rp.IRQ_USBCTRL_IRQ, handleUSBIRQ) intr.SetPriority(0x00) intr.Enable() irqSet(rp.IRQ_USBCTRL_IRQ, true) // Mux the controller to the onboard usb phy rp.USB.USB_MUXING.Set(rp.USB_USB_MUXING_TO_PHY | rp.USB_USB_MUXING_SOFTCON) // Force VBUS detect so the device thinks it is plugged into a host rp.USB.USB_PWR.Set(rp.USB_USB_PWR_VBUS_DETECT | rp.USB_USB_PWR_VBUS_DETECT_OVERRIDE_EN) // Enable the USB controller in device mode. rp.USB.MAIN_CTRL.Set(rp.USB_MAIN_CTRL_CONTROLLER_EN) // Enable an interrupt per EP0 transaction rp.USB.SIE_CTRL.Set(rp.USB_SIE_CTRL_EP0_INT_1BUF) // Enable interrupts for when a buffer is done, when the bus is reset, // and when a setup packet is received rp.USB.INTE.Set(rp.USB_INTE_BUFF_STATUS | rp.USB_INTE_BUS_RESET | rp.USB_INTE_SETUP_REQ) // Present full speed device by enabling pull up on DP rp.USB.SIE_CTRL.SetBits(rp.USB_SIE_CTRL_PULLUP_EN) // 12.7.2 Disable phy isolation rp.USB.SetMAIN_CTRL_PHY_ISO(0x0) } func handleUSBIRQ(intr interrupt.Interrupt) { status := rp.USB.INTS.Get() // Setup packet received if (status & rp.USB_INTS_SETUP_REQ) > 0 { rp.USB.SIE_STATUS.Set(rp.USB_SIE_STATUS_SETUP_REC) setup := usb.NewSetup(_usbDPSRAM.setupBytes()) ok := false if (setup.BmRequestType & usb.REQUEST_TYPE) == usb.REQUEST_STANDARD { // Standard Requests ok = handleStandardSetup(setup) } else { // Class Interface Requests if setup.WIndex < uint16(len(usbSetupHandler)) && usbSetupHandler[setup.WIndex] != nil { ok = usbSetupHandler[setup.WIndex](setup) } } if !ok { // Stall endpoint? USBDev.SetStallEPIn(0) } } // Buffer status, one or more buffers have completed if (status & rp.USB_INTS_BUFF_STATUS) > 0 { if sendOnEP0DATADONE.offset > 0 { ep := uint32(0) data := sendOnEP0DATADONE.data count := len(data) - sendOnEP0DATADONE.offset if ep == 0 && count > usb.EndpointPacketSize { count = usb.EndpointPacketSize } sendViaEPIn(ep, data[sendOnEP0DATADONE.offset:], count) sendOnEP0DATADONE.offset += count if sendOnEP0DATADONE.offset == len(data) { sendOnEP0DATADONE.offset = 0 } } s2 := rp.USB.BUFF_STATUS.Get() // OUT (PC -> rp2350) for i := 0; i < 16; i++ { if s2&(1<<(i*2+1)) > 0 { buf := handleEndpointRx(uint32(i)) if usbRxHandler[i] == nil || usbRxHandler[i](buf) { AckUsbOutTransfer(uint32(i)) } } } // IN (rp2350 -> PC) for i := 0; i < 16; i++ { if s2&(1<<(i*2)) > 0 { if usbTxHandler[i] != nil { usbTxHandler[i]() } } } rp.USB.BUFF_STATUS.Set(s2) } // Bus is reset if (status & rp.USB_INTS_BUS_RESET) > 0 { rp.USB.SIE_STATUS.Set(rp.USB_SIE_STATUS_BUS_RESET) //fixRP2040UsbDeviceEnumeration() rp.USB.ADDR_ENDP.Set(0) initEndpoint(0, usb.ENDPOINT_TYPE_CONTROL) } } func handleUSBSetAddress(setup usb.Setup) bool { // Using 570μs timeout which is exactly the same as SAMD21. const ackTimeout = 570 rp.USB.SIE_STATUS.Set(rp.USB_SIE_STATUS_ACK_REC) sendUSBPacket(0, []byte{}, 0) // Wait for transfer to complete with a timeout. t := timer.timeElapsed() for (rp.USB.SIE_STATUS.Get() & rp.USB_SIE_STATUS_ACK_REC) == 0 { if dt := timer.timeElapsed() - t; dt >= ackTimeout { return false } } // Set the device address to that requested by host. rp.USB.ADDR_ENDP.Set(uint32(setup.WValueL) & rp.USB_ADDR_ENDP_ADDRESS_Msk) return true } func armEPZeroStall() { rp.USB.EP_STALL_ARM.Set(rp.USB_EP_STALL_ARM_EP0_IN) } ================================================ FILE: src/machine/machine_rp2_2040.go ================================================ //go:build rp2040 package machine import ( "device/rp" "runtime/volatile" "unsafe" ) const ( cpuFreq = 200 * MHz _NUMBANK0_GPIOS = 30 _NUMBANK0_IRQS = 4 _NUMIRQ = 32 rp2350ExtraReg = 0 RESETS_RESET_Msk = 0x01ffffff initUnreset = rp.RESETS_RESET_ADC | rp.RESETS_RESET_RTC | rp.RESETS_RESET_SPI0 | rp.RESETS_RESET_SPI1 | rp.RESETS_RESET_UART0 | rp.RESETS_RESET_UART1 | rp.RESETS_RESET_USBCTRL initDontReset = rp.RESETS_RESET_IO_QSPI | rp.RESETS_RESET_PADS_QSPI | rp.RESETS_RESET_PLL_USB | rp.RESETS_RESET_USBCTRL | rp.RESETS_RESET_SYSCFG | rp.RESETS_RESET_PLL_SYS padEnableMask = rp.PADS_BANK0_GPIO0_IE_Msk | rp.PADS_BANK0_GPIO0_OD_Msk ) const ( PinOutput PinMode = iota PinInput PinInputPulldown PinInputPullup PinAnalog PinUART PinPWM PinI2C PinSPI PinPIO0 PinPIO1 ) // Analog pins on RP2040. const ( ADC0 Pin = GPIO26 ADC1 Pin = GPIO27 ADC2 Pin = GPIO28 ADC3 Pin = GPIO29 thermADC = 30 ) const ( clkGPOUT0 clockIndex = iota // GPIO Muxing 0 clkGPOUT1 // GPIO Muxing 1 clkGPOUT2 // GPIO Muxing 2 clkGPOUT3 // GPIO Muxing 3 clkRef // Watchdog and timers reference clock clkSys // Processors, bus fabric, memory, memory mapped registers clkPeri // Peripheral clock for UART and SPI clkUSB // USB clock clkADC // ADC clock clkRTC // Real time clock numClocks ) func calcClockDiv(srcFreq, freq uint32) uint32 { // Div register is 24.8 int.frac divider so multiply by 2^8 (left shift by 8) return uint32((uint64(srcFreq) << 8) / uint64(freq)) } type clocksType struct { clk [numClocks]clockType resus struct { ctrl volatile.Register32 status volatile.Register32 } fc0 fc wakeEN0 volatile.Register32 wakeEN1 volatile.Register32 sleepEN0 volatile.Register32 sleepEN1 volatile.Register32 enabled0 volatile.Register32 enabled1 volatile.Register32 intR volatile.Register32 intE volatile.Register32 intF volatile.Register32 intS volatile.Register32 } // GPIO function selectors const ( fnJTAG pinFunc = 0 fnSPI pinFunc = 1 // Connect one of the internal PL022 SPI peripherals to GPIO fnUART pinFunc = 2 fnI2C pinFunc = 3 // Connect a PWM slice to GPIO. There are eight PWM slices, // each with two outputchannels (A/B). The B pin can also be used as an input, // for frequency and duty cyclemeasurement fnPWM pinFunc = 4 // Software control of GPIO, from the single-cycle IO (SIO) block. // The SIO function (F5)must be selected for the processors to drive a GPIO, // but the input is always connected,so software can check the state of GPIOs at any time. fnSIO pinFunc = 5 // Connect one of the programmable IO blocks (PIO) to GPIO. PIO can implement a widevariety of interfaces, // and has its own internal pin mapping hardware, allowing flexibleplacement of digital interfaces on bank 0 GPIOs. // The PIO function (F6, F7) must beselected for PIO to drive a GPIO, but the input is always connected, // so the PIOs canalways see the state of all pins. fnPIO0, fnPIO1 pinFunc = 6, 7 // General purpose clock inputs/outputs. Can be routed to a number of internal clock domains onRP2040, // e.g. Input: to provide a 1 Hz clock for the RTC, or can be connected to an internalfrequency counter. // e.g. Output: optional integer divide fnGPCK pinFunc = 8 // USB power control signals to/from the internal USB controller fnUSB pinFunc = 9 fnNULL pinFunc = 0x1f fnXIP pinFunc = 0 ) // validPins confirms that the SPI pin selection is a legitimate one // for the the 2040 chip. func (spi *SPI) validPins(config SPIConfig) error { var okSDI, okSDO, okSCK bool switch spi.Bus { case rp.SPI0: okSDI = config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20 okSDO = config.SDO == 3 || config.SDO == 7 || config.SDO == 19 || config.SDO == 23 okSCK = config.SCK == 2 || config.SCK == 6 || config.SCK == 18 || config.SCK == 22 case rp.SPI1: okSDI = config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28 okSDO = config.SDO == 11 || config.SDO == 15 || config.SDO == 27 okSCK = config.SCK == 10 || config.SCK == 14 || config.SCK == 26 } switch { case !okSDI: return errSPIInvalidSDI case !okSDO: return errSPIInvalidSDO case !okSCK: return errSPIInvalidSCK } return nil } // Configure configures the gpio pin as per mode. func (p Pin) Configure(config PinConfig) { if p == NoPin { return } p.init() mask := uint32(1) << p switch config.Mode { case PinOutput: p.setFunc(fnSIO) rp.SIO.GPIO_OE_SET.Set(mask) case PinInput: p.setFunc(fnSIO) p.pulloff() case PinInputPulldown: p.setFunc(fnSIO) p.pulldown() case PinInputPullup: p.setFunc(fnSIO) p.pullup() case PinAnalog: p.setFunc(fnNULL) p.pulloff() case PinUART: p.setFunc(fnUART) case PinPWM: p.setFunc(fnPWM) case PinI2C: // IO config according to 4.3.1.3 of rp2040 datasheet. p.setFunc(fnI2C) p.pullup() p.setSchmitt(true) p.setSlew(false) case PinSPI: p.setFunc(fnSPI) case PinPIO0: p.setFunc(fnPIO0) case PinPIO1: p.setFunc(fnPIO1) } } var ( timer = (*timerType)(unsafe.Pointer(rp.TIMER)) ) // Enable or disable a specific interrupt on the executing core. // num is the interrupt number which must be in [0,31]. func irqSet(num uint32, enabled bool) { if num >= _NUMIRQ { return } irqSetMask(1<= 32 { mask := uint32(1) << (p % 32) rp.SIO.GPIO_HI_OE_SET.Set(mask) } else { mask := uint32(1) << p rp.SIO.GPIO_OE_SET.Set(mask) } case PinInput: p.setFunc(fnSIO) p.pulloff() case PinInputPulldown: p.setFunc(fnSIO) p.pulldown() case PinInputPullup: p.setFunc(fnSIO) p.pullup() case PinAnalog: p.setFunc(fnNULL) p.pulloff() // Disable digital input. p.padCtrl().ClearBits(rp.PADS_BANK0_GPIO0_IE) case PinUART: p.setFunc(fnUART) case PinPWM: p.setFunc(fnPWM) case PinI2C: // IO config according to 4.3.1.3 of rp2040 datasheet. p.setFunc(fnI2C) p.pullup() p.setSchmitt(true) p.setSlew(false) case PinSPI: p.setFunc(fnSPI) case PinPIO0: p.setFunc(fnPIO0) case PinPIO1: p.setFunc(fnPIO1) case PinPIO2: p.setFunc(fnPIO2) } } var ( timer = (*timerType)(unsafe.Pointer(rp.TIMER0)) ) // Enable or disable a specific interrupt on the executing core. // num is the interrupt number which must be in [0,_NUMIRQ). func irqSet(num uint32, enabled bool) { if num >= _NUMIRQ { return } register_index := num / 32 var mask uint32 = 1 << (num % 32) if enabled { // Clear pending before enable //(if IRQ is actually asserted, it will immediately re-pend) if register_index == 0 { rp.PPB.NVIC_ICPR0.Set(mask) rp.PPB.NVIC_ISER0.Set(mask) } else { rp.PPB.NVIC_ICPR1.Set(mask) rp.PPB.NVIC_ISER1.Set(mask) } } else { if register_index == 0 { rp.PPB.NVIC_ICER0.Set(mask) } else { rp.PPB.NVIC_ICER1.Set(mask) } } } func (clks *clocksType) initRTC() {} // No RTC on RP2350. func (clks *clocksType) initTicks() { rp.TICKS.SetTIMER0_CTRL_ENABLE(0) rp.TICKS.SetTIMER0_CYCLES(12) rp.TICKS.SetTIMER0_CTRL_ENABLE(1) } // startTick starts the watchdog tick. // On RP2040, the watchdog contained a tick generator used to generate a 1μs tick for the watchdog. This was also // distributed to the system timer. On RP2350, the watchdog instead takes a tick input from the system-level ticks block. See Section 8.5. func (wd *watchdogImpl) startTick(cycles uint32) { rp.TICKS.WATCHDOG_CTRL.SetBits(1) } func adjustCoreVoltage() bool { return false } ================================================ FILE: src/machine/machine_rp2_2350a.go ================================================ //go:build rp2350 && !rp2350b package machine import "device/rp" // Analog pins on RP2350a. const ( ADC0 Pin = GPIO26 ADC1 Pin = GPIO27 ADC2 Pin = GPIO28 ADC3 Pin = GPIO29 // fifth ADC channel. thermADC = 30 ) // validPins confirms that the SPI pin selection is a legitimate one // for the the 2350a chip. func (spi *SPI) validPins(config SPIConfig) error { var okSDI, okSDO, okSCK bool switch spi.Bus { case rp.SPI0: okSDI = config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20 okSDO = config.SDO == 3 || config.SDO == 7 || config.SDO == 19 || config.SDO == 23 okSCK = config.SCK == 2 || config.SCK == 6 || config.SCK == 18 || config.SCK == 22 case rp.SPI1: okSDI = config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28 okSDO = config.SDO == 11 || config.SDO == 15 || config.SDO == 27 okSCK = config.SCK == 10 || config.SCK == 14 || config.SCK == 26 } switch { case !okSDI: return errSPIInvalidSDI case !okSDO: return errSPIInvalidSDO case !okSCK: return errSPIInvalidSCK } return nil } ================================================ FILE: src/machine/machine_rp2_2350b.go ================================================ //go:build rp2350b package machine import "device/rp" // RP2350B has additional pins. const ( GPIO30 Pin = 30 // peripherals: PWM7 channel A, I2C1 SDA GPIO31 Pin = 31 // peripherals: PWM7 channel B, I2C1 SCL GPIO32 Pin = 32 // peripherals: PWM8 channel A, I2C0 SDA GPIO33 Pin = 33 // peripherals: PWM8 channel B, I2C0 SCL GPIO34 Pin = 34 // peripherals: PWM9 channel A, I2C1 SDA GPIO35 Pin = 35 // peripherals: PWM9 channel B, I2C1 SCL GPIO36 Pin = 36 // peripherals: PWM10 channel A, I2C0 SDA GPIO37 Pin = 37 // peripherals: PWM10 channel B, I2C0 SCL GPIO38 Pin = 38 // peripherals: PWM11 channel A, I2C1 SDA GPIO39 Pin = 39 // peripherals: PWM11 channel B, I2C1 SCL GPIO40 Pin = 40 // peripherals: PWM8 channel A, I2C0 SDA GPIO41 Pin = 41 // peripherals: PWM8 channel B, I2C0 SCL GPIO42 Pin = 42 // peripherals: PWM9 channel A, I2C1 SDA GPIO43 Pin = 43 // peripherals: PWM9 channel B, I2C1 SCL GPIO44 Pin = 44 // peripherals: PWM10 channel A, I2C0 SDA GPIO45 Pin = 45 // peripherals: PWM10 channel B, I2C0 SCL GPIO46 Pin = 46 // peripherals: PWM11 channel A, I2C1 SDA GPIO47 Pin = 47 // peripherals: PWM11 channel B, I2C1 SCL ) // Analog pins on 2350b. const ( ADC0 Pin = GPIO40 ADC1 Pin = GPIO41 ADC2 Pin = GPIO42 ADC3 Pin = GPIO43 ADC4 Pin = GPIO44 ADC5 Pin = GPIO45 ADC6 Pin = GPIO46 ADC7 Pin = GPIO47 // Ninth ADC channel. thermADC = 48 ) // Additional PWMs on the RP2350B. var ( PWM8 = getPWMGroup(8) PWM9 = getPWMGroup(9) PWM10 = getPWMGroup(10) PWM11 = getPWMGroup(11) ) // validPins confirms that the SPI pin selection is a legitimate one // for the the 2350b chip. func (spi *SPI) validPins(config SPIConfig) error { var okSDI, okSDO, okSCK bool switch spi.Bus { case rp.SPI0: okSDI = config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20 || config.SDI == 32 || config.SDI == 36 okSDO = config.SDO == 3 || config.SDO == 7 || config.SDO == 19 || config.SDO == 23 || config.SDO == 35 || config.SDO == 39 okSCK = config.SCK == 2 || config.SCK == 6 || config.SCK == 18 || config.SCK == 22 || config.SCK == 34 || config.SCK == 38 case rp.SPI1: okSDI = config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28 || config.SDI == 40 || config.SDI == 44 okSDO = config.SDO == 11 || config.SDO == 15 || config.SDO == 27 || config.SDO == 31 || config.SDO == 43 || config.SDO == 47 okSCK = config.SCK == 10 || config.SCK == 14 || config.SCK == 26 || config.SCK == 30 || config.SCK == 42 || config.SCK == 46 } switch { case !okSDI: return errSPIInvalidSDI case !okSDO: return errSPIInvalidSDO case !okSCK: return errSPIInvalidSCK } return nil } ================================================ FILE: src/machine/machine_rp2_adc.go ================================================ //go:build rp2040 || rp2350 package machine import ( "device/rp" "errors" "sync" ) // ADCChannel is the ADC peripheral mux channel. 0-4. type ADCChannel uint8 // Used to serialise ADC sampling var adcLock sync.Mutex // ADC peripheral reference voltage (mV) var adcAref uint32 // InitADC resets the ADC peripheral. func InitADC() { rp.RESETS.RESET.SetBits(rp.RESETS_RESET_ADC) rp.RESETS.RESET.ClearBits(rp.RESETS_RESET_ADC) for !rp.RESETS.RESET_DONE.HasBits(rp.RESETS_RESET_ADC) { } // enable ADC rp.ADC.CS.Set(rp.ADC_CS_EN) adcAref = 3300 waitForReady() } // Configure sets the ADC pin to analog input mode. func (a ADC) Configure(config ADCConfig) error { c, err := a.GetADCChannel() if err != nil { return err } return c.Configure(config) } // Get returns a one-shot ADC sample reading. func (a ADC) Get() uint16 { if c, err := a.GetADCChannel(); err == nil { return c.getOnce() } // Not an ADC pin! return 0 } // GetADCChannel returns the channel associated with the ADC pin. func (a ADC) GetADCChannel() (c ADCChannel, err error) { if a.Pin < ADC0 { return 0, errors.New("no ADC channel for pin value") } return ADCChannel(a.Pin - ADC0), nil } // Configure sets the channel's associated pin to analog input mode. // The powered on temperature sensor increases ADC_AVDD current by approximately 40 μA. func (c ADCChannel) Configure(config ADCConfig) error { if config.Reference != 0 { adcAref = config.Reference } p, err := c.Pin() if err != nil { return err } p.Configure(PinConfig{Mode: PinAnalog}) return nil } // getOnce returns a one-shot ADC sample reading from an ADC channel. func (c ADCChannel) getOnce() uint16 { // Make it safe to sample multiple ADC channels in separate go routines. adcLock.Lock() rp.ADC.CS.ReplaceBits(uint32(c)<>4) } // ReadTemperature does a one-shot sample of the internal temperature sensor and returns a milli-celsius reading. func ReadTemperature() (millicelsius int32) { if rp.ADC.CS.Get()&rp.ADC_CS_EN == 0 { InitADC() } thermChan, _ := ADC{Pin: thermADC}.GetADCChannel() // Enable temperature sensor bias source rp.ADC.CS.SetBits(rp.ADC_CS_TS_EN) // T = 27 - (ADC_voltage - 0.706)/0.001721 // 1/0.001721 ≈ 581 return int32(((int64(27000) << 16) - ((int64(thermChan.getVoltage()) - (int64(706) << 16)) * 581)) >> 16) } // waitForReady spins waiting for the ADC peripheral to become ready. func waitForReady() { for !rp.ADC.CS.HasBits(rp.ADC_CS_READY) { } } // The Pin method returns the GPIO Pin associated with the ADC mux channel, if it has one. func (c ADCChannel) Pin() (p Pin, err error) { return Pin(c) + ADC0, nil } ================================================ FILE: src/machine/machine_rp2_clocks.go ================================================ //go:build rp2040 || rp2350 package machine import ( "device/arm" "device/rp" "runtime/volatile" "unsafe" ) func CPUFrequency() uint32 { return cpuFreq } // clockIndex identifies a hardware clock type clockIndex uint8 type clockType struct { ctrl volatile.Register32 div volatile.Register32 selected volatile.Register32 } type fc struct { refKHz volatile.Register32 minKHz volatile.Register32 maxKHz volatile.Register32 delay volatile.Register32 interval volatile.Register32 src volatile.Register32 status volatile.Register32 result volatile.Register32 } var clocks = (*clocksType)(unsafe.Pointer(rp.CLOCKS)) var configuredFreq [numClocks]uint32 type clock struct { *clockType cix clockIndex } // The delay in seconds for core voltage adjustments to // settle. Taken from the Pico SDK. const _VREG_VOLTAGE_AUTO_ADJUST_DELAY = 1 / 1e3 // clock returns the clock identified by cix. func (clks *clocksType) clock(cix clockIndex) clock { return clock{ &clks.clk[cix], cix, } } // hasGlitchlessMux returns true if clock contains a glitchless multiplexer. // // Clock muxing consists of two components: // // A glitchless mux, which can be switched freely, but whose inputs must be // free-running. // // An auxiliary (glitchy) mux, whose output glitches when switched, but has // no constraints on its inputs. // // Not all clocks have both types of mux. func (clk *clock) hasGlitchlessMux() bool { return clk.cix == clkSys || clk.cix == clkRef } // configure configures the clock by selecting the main clock source src // and the auxiliary clock source auxsrc // and finally setting the clock frequency to freq // given the input clock source frequency srcFreq. func (clk *clock) configure(src, auxsrc, srcFreq, freq uint32) { if freq > srcFreq { panic("clock frequency cannot be greater than source frequency") } div := calcClockDiv(srcFreq, freq) // If increasing divisor, set divisor before source. Otherwise set source // before divisor. This avoids a momentary overspeed when e.g. switching // to a faster source and increasing divisor to compensate. if div > clk.div.Get() { clk.div.Set(div) } // If switching a glitchless slice (ref or sys) to an aux source, switch // away from aux *first* to avoid passing glitches when changing aux mux. // Assume (!!!) glitchless source 0 is no faster than the aux source. if clk.hasGlitchlessMux() && src == rp.CLOCKS_CLK_SYS_CTRL_SRC_CLKSRC_CLK_SYS_AUX { clk.ctrl.ClearBits(rp.CLOCKS_CLK_REF_CTRL_SRC_Msk) for !clk.selected.HasBits(1) { } } else // If no glitchless mux, cleanly stop the clock to avoid glitches // propagating when changing aux mux. Note it would be a really bad idea // to do this on one of the glitchless clocks (clkSys, clkRef). { // Disable clock. On clkRef and ClkSys this does nothing, // all other clocks have the ENABLE bit in the same position. clk.ctrl.ClearBits(rp.CLOCKS_CLK_GPOUT0_CTRL_ENABLE_Msk) if configuredFreq[clk.cix] > 0 { // Delay for 3 cycles of the target clock, for ENABLE propagation. // Note XOSC_COUNT is not helpful here because XOSC is not // necessarily running, nor is timer... so, 3 cycles per loop: delayCyc := configuredFreq[clkSys]/configuredFreq[clk.cix] + 1 for delayCyc != 0 { // This could be done more efficiently but TinyGo inline // assembly is not yet capable enough to express that. In the // meantime, this forces at least 3 cycles per loop. delayCyc-- arm.Asm("nop\nnop\nnop") } } } // Set aux mux first, and then glitchless mux if this clock has one. clk.ctrl.ReplaceBits(auxsrc< FlashDataEnd() { return 0, errFlashCannotReadPastEOF } data := unsafe.Slice((*byte)(unsafe.Pointer(readAddress(off))), len(p)) copy(p, data) return len(p), nil } // WriteAt writes the given number of bytes to the block device. // Only word (32 bits) length data can be programmed. // If the length of p is not long enough it will be padded with 0xFF bytes. // This method assumes that the destination is already erased. func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { return f.writeAt(p, off) } // Size returns the number of bytes in this block device. func (f flashBlockDevice) Size() int64 { return int64(FlashDataEnd() - FlashDataStart()) } const writeBlockSize = 1 << 8 // WriteBlockSize returns the block size in which data can be written to // memory. It can be used by a client to optimize writes, non-aligned writes // should always work correctly. func (f flashBlockDevice) WriteBlockSize() int64 { return writeBlockSize } const eraseBlockSizeValue = 1 << 12 func eraseBlockSize() int64 { return eraseBlockSizeValue } // EraseBlockSize returns the smallest erasable area on this particular chip // in bytes. This is used for the block size in EraseBlocks. func (f flashBlockDevice) EraseBlockSize() int64 { return eraseBlockSize() } // EraseBlocks erases the given number of blocks. An implementation may // transparently coalesce ranges of blocks into larger bundles if the chip // supports this. The start and len parameters are in block numbers, use // EraseBlockSize to map addresses to blocks. func (f flashBlockDevice) EraseBlocks(start, length int64) error { return f.eraseBlocks(start, length) } // return the correct address to be used for write func writeAddress(off int64) uintptr { return readAddress(off) - uintptr(memoryStart) } // return the correct address to be used for reads func readAddress(off int64) uintptr { return FlashDataStart() + uintptr(off) } ================================================ FILE: src/machine/machine_rp2_gpio.go ================================================ //go:build rp2040 || rp2350 package machine import ( "device/rp" "runtime/interrupt" "runtime/volatile" "unsafe" ) type ioType struct { status volatile.Register32 ctrl volatile.Register32 } type irqCtrl struct { intE [_NUMBANK0_IRQS]volatile.Register32 intF [_NUMBANK0_IRQS]volatile.Register32 intS [_NUMBANK0_IRQS]volatile.Register32 } type irqSummary struct { proc [2]struct { secure [2]volatile.Register32 nonsecure [2]volatile.Register32 } comaWake struct { secure [2]volatile.Register32 nonsecure [2]volatile.Register32 } } type ioBank0Type struct { io [_NUMBANK0_GPIOS]ioType _ [rp2350ExtraReg][128]byte irqsum [rp2350ExtraReg]irqSummary intR [_NUMBANK0_IRQS]volatile.Register32 proc0IRQctrl irqCtrl proc1IRQctrl irqCtrl dormantWakeIRQctrl irqCtrl } var ioBank0 = (*ioBank0Type)(unsafe.Pointer(rp.IO_BANK0)) type padsBank0Type struct { voltageSelect volatile.Register32 io [_NUMBANK0_GPIOS]volatile.Register32 } var padsBank0 = (*padsBank0Type)(unsafe.Pointer(rp.PADS_BANK0)) // pinFunc represents a GPIO function. // // Each GPIO can have one function selected at a time. // Likewise, each peripheral input (e.g. UART0 RX) should only be selected // on one GPIO at a time. If the same peripheral input is connected to multiple GPIOs, // the peripheral sees the logical OR of these GPIO inputs. type pinFunc uint8 func (p Pin) PortMaskSet() (*uint32, uint32) { return (*uint32)(unsafe.Pointer(&rp.SIO.GPIO_OUT_SET)), 1 << p } // set drives the pin high func (p Pin) set() { if is48Pin && p >= 32 { mask := uint32(1) << (p % 32) rp.SIO.GPIO_HI_OUT_SET.Set(mask) } else { mask := uint32(1) << p rp.SIO.GPIO_OUT_SET.Set(mask) } } func (p Pin) PortMaskClear() (*uint32, uint32) { return (*uint32)(unsafe.Pointer(&rp.SIO.GPIO_OUT_CLR)), 1 << p } // clr drives the pin low func (p Pin) clr() { if is48Pin && p >= 32 { mask := uint32(1) << (p % 32) rp.SIO.GPIO_HI_OUT_CLR.Set(mask) } else { mask := uint32(1) << p rp.SIO.GPIO_OUT_CLR.Set(mask) } } // xor toggles the pin func (p Pin) xor() { if is48Pin && p >= 32 { mask := uint32(1) << (p % 32) rp.SIO.GPIO_HI_OUT_XOR.Set(mask) } else { mask := uint32(1) << p rp.SIO.GPIO_OUT_XOR.Set(mask) } } // get returns the pin value func (p Pin) get() bool { if is48Pin && p >= 32 { return rp.SIO.GPIO_HI_IN.HasBits(1 << (p % 32)) } return rp.SIO.GPIO_IN.HasBits(1 << p) } func (p Pin) ioCtrl() *volatile.Register32 { return &ioBank0.io[p].ctrl } func (p Pin) padCtrl() *volatile.Register32 { return &padsBank0.io[p] } func (p Pin) pullup() { p.padCtrl().SetBits(rp.PADS_BANK0_GPIO0_PUE) p.padCtrl().ClearBits(rp.PADS_BANK0_GPIO0_PDE) } func (p Pin) pulldown() { p.padCtrl().SetBits(rp.PADS_BANK0_GPIO0_PDE) p.padCtrl().ClearBits(rp.PADS_BANK0_GPIO0_PUE) } func (p Pin) pulloff() { p.padCtrl().ClearBits(rp.PADS_BANK0_GPIO0_PDE) p.padCtrl().ClearBits(rp.PADS_BANK0_GPIO0_PUE) } // setSlew sets pad slew rate control. // true sets to fast. false sets to slow. func (p Pin) setSlew(sr bool) { p.padCtrl().ReplaceBits(boolToBit(sr)<= 32 { mask := uint32(1) << (p % 32) rp.SIO.GPIO_HI_OE_CLR.Set(mask) } else { mask := uint32(1) << p rp.SIO.GPIO_OE_CLR.Set(mask) } p.clr() } // Set drives the pin high if value is true else drives it low. func (p Pin) Set(value bool) { if p == NoPin { return } if value { p.set() } else { p.clr() } } // Get reads the pin value. func (p Pin) Get() bool { return p.get() } // PinChange represents one or more trigger events that can happen on a given GPIO pin // on the RP2040. ORed PinChanges are valid input to most IRQ functions. type PinChange uint8 // Pin change interrupt constants for SetInterrupt. const ( // Edge falling PinFalling PinChange = 4 << iota // Edge rising PinRising PinToggle = PinFalling | PinRising ) // Callbacks to be called for pins configured with SetInterrupt. var ( pinCallbacks [2][_NUMBANK0_GPIOS]func(Pin) setInt [2][_NUMBANK0_GPIOS]bool ) // SetInterrupt sets an interrupt to be executed when a particular pin changes // state. The pin should already be configured as an input, including a pull up // or down if no external pull is provided. // // This call will replace a previously set callback on this pin. You can pass a // nil func to unset the pin change interrupt. If you do so, the change // parameter is ignored and can be set to any value (such as 0). func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { if p == NoPin { return nil } if p > 31 || p < 0 { return ErrInvalidInputPin } core := CurrentCore() if callback == nil { // disable current interrupt p.setInterrupt(change, false) pinCallbacks[core][p] = nil return nil } if pinCallbacks[core][p] != nil { // Callback already configured. Should disable callback by passing a nil callback first. return ErrNoPinChangeChannel } p.setInterrupt(change, true) pinCallbacks[core][p] = callback if setInt[core][p] { // interrupt has already been set. Exit. return nil } interrupt.New(rp.IRQ_IO_IRQ_BANK0, gpioHandleInterrupt).Enable() irqSet(rp.IRQ_IO_IRQ_BANK0, true) return nil } // gpioHandleInterrupt finds the corresponding pin for the interrupt. // C SDK equivalent of gpio_irq_handler func gpioHandleInterrupt(intr interrupt.Interrupt) { core := CurrentCore() var gpio Pin for gpio = 0; gpio < _NUMBANK0_GPIOS; gpio++ { var base *irqCtrl switch core { case 0: base = &ioBank0.proc0IRQctrl case 1: base = &ioBank0.proc1IRQctrl } statreg := base.intS[gpio>>3].Get() change := getIntChange(gpio, statreg) if change != 0 { gpio.acknowledgeInterrupt(change) callback := pinCallbacks[core][gpio] if callback != nil { callback(gpio) } } } } // events returns the bit representation of the pin change for the rp2040. func (change PinChange) events() uint32 { return uint32(change) } // intBit is the bit storage form of a PinChange for a given Pin // in the IO_BANK0 interrupt registers (page 269 RP2040 Datasheet). func (p Pin) ioIntBit(change PinChange) uint32 { return change.events() << (4 * (p % 8)) } // Acquire interrupt data from a INT status register. func getIntChange(p Pin, status uint32) PinChange { return PinChange(status>>(4*(p%8))) & 0xf } ================================================ FILE: src/machine/machine_rp2_i2c.go ================================================ //go:build rp2040 || rp2350 package machine import ( "device/rp" "errors" "internal/itoa" ) // I2C on the RP2040/RP2350 var ( I2C0 = &_I2C0 _I2C0 = I2C{ Bus: rp.I2C0, } I2C1 = &_I2C1 _I2C1 = I2C{ Bus: rp.I2C1, } ) // The I2C target implementation is based on the C implementation from // here: https://github.com/vmilea/pico_i2c_slave // Features: Taken from datasheet. // Default controller mode, with target mode available (not simultaneously). // Default target address of RP2040: 0x055 // Supports 10-bit addressing in controller mode // 16-element transmit buffer // 16-element receive buffer // Can be driven from DMA // Can generate interrupts // Fast mode plus max transfer speed (1000kb/s) // GPIO config // Each controller must connect its clock SCL and data SDA to one pair of GPIOs. // The I2C standard requires that drivers drivea signal low, or when not driven the signal will be pulled high. // This applies to SCL and SDA. The GPIO pads should be configured for: // Pull-up enabled // Slew rate limited // Schmitt trigger enabled // Note: There should also be external pull-ups on the board as the internal pad pull-ups may not be strong enough to pull upexternal circuits. // I2CConfig is used to store config info for I2C. type I2CConfig struct { Frequency uint32 // SDA/SCL Serial Data and clock pins. Refer to datasheet to see // which pins match the desired bus. SDA, SCL Pin Mode I2CMode } type I2C struct { Bus *rp.I2C0_Type mode I2CMode txInProgress bool } var ( errInvalidI2CBaudrate = errors.New("i2c: invalid baudrate") errInvalidTgtAddr = errors.New("i2c: invalid target address: not in 0..0x80 or is reserved") errI2CGeneric = errors.New("i2c: generic error") errI2CDisable = errors.New("i2c: peripheral timeout in disable") errInvalidI2CSDA = errors.New("i2c: invalid SDA pin") errInvalidI2CSCL = errors.New("i2c: invalid SCL pin") errI2CAlreadyListening = errors.New("i2c: already listening") errI2CWrongMode = errors.New("i2c: wrong mode") errI2CUnderflow = errors.New("i2c: underflow") ) // Tx performs a write and then a read transfer placing the result in // in r. // // Passing a nil value for w or r skips the transfer corresponding to write // or read, respectively. // // i2c.Tx(addr, nil, r) // // Performs only a read transfer. // // i2c.Tx(addr, w, nil) // // Performs only a write transfer. func (i2c *I2C) Tx(addr uint16, w, r []byte) error { if i2c.mode != I2CModeController { return errI2CWrongMode } return i2c.tx(uint8(addr), w, r) } // Listen starts listening for I2C requests sent to specified address // // addr is the address to listen to func (i2c *I2C) Listen(addr uint16) error { if i2c.mode != I2CModeTarget { return errI2CWrongMode } return i2c.listen(uint8(addr)) } // Configure initializes i2c peripheral and configures I2C config's pins passed. // Here's a list of valid SDA and SCL GPIO pins on bus I2C0 of the rp2040: // // SDA: 0, 4, 8, 12, 16, 20 // SCL: 1, 5, 9, 13, 17, 21 // // Same as above for I2C1 bus: // // SDA: 2, 6, 10, 14, 18, 26 // SCL: 3, 7, 11, 15, 19, 27 func (i2c *I2C) Configure(config I2CConfig) error { const defaultBaud uint32 = 100_000 // 100kHz standard mode if config.SCL == 0 && config.SDA == 0 { // If config pins are zero valued or clock pin is invalid then we set default values. switch i2c.Bus { case rp.I2C0: config.SCL = I2C0_SCL_PIN config.SDA = I2C0_SDA_PIN case rp.I2C1: config.SCL = I2C1_SCL_PIN config.SDA = I2C1_SDA_PIN } } var okSCL, okSDA bool switch i2c.Bus { case rp.I2C0: okSCL = (config.SCL+3)%4 == 0 okSDA = (config.SDA+4)%4 == 0 case rp.I2C1: okSCL = (config.SCL+1)%4 == 0 okSDA = (config.SDA+2)%4 == 0 } switch { case !okSCL: return errInvalidI2CSCL case !okSDA: return errInvalidI2CSDA } if config.Frequency == 0 { config.Frequency = defaultBaud } config.SDA.Configure(PinConfig{PinI2C}) config.SCL.Configure(PinConfig{PinI2C}) return i2c.init(config) } // SetBaudRate sets the I2C frequency. It has the side effect of also // enabling the I2C hardware if disabled beforehand. // //go:inline func (i2c *I2C) SetBaudRate(br uint32) error { if br == 0 { return errInvalidI2CBaudrate } // I2C is synchronous design that runs from clk_sys freqin := CPUFrequency() // TODO there are some subtleties to I2C timing which we are completely ignoring here period := (freqin + br/2) / br lcnt := period * 3 / 5 // oof this one hurts hcnt := period - lcnt // Check for out-of-range divisors: if hcnt > rp.I2C0_IC_FS_SCL_HCNT_IC_FS_SCL_HCNT_Msk || hcnt < 8 || lcnt > rp.I2C0_IC_FS_SCL_LCNT_IC_FS_SCL_LCNT_Msk || lcnt < 8 { return errInvalidI2CBaudrate } // Per I2C-bus specification a device in standard or fast mode must // internally provide a hold time of at least 300ns for the SDA signal to // bridge the undefined region of the falling edge of SCL. A smaller hold // time of 120ns is used for fast mode plus. // sda_tx_hold_count = freq_in [cycles/s] * 300ns * (1s / 1e9ns) // Reduce 300/1e9 to 3/1e7 to avoid numbers that don't fit in uint. // Add 1 to avoid division truncation. sdaTxHoldCnt := ((freqin * 3) / 10000000) + 1 if br >= 1_000_000 { // sda_tx_hold_count = freq_in [cycles/s] * 120ns * (1s / 1e9ns) // Reduce 120/1e9 to 3/25e6 to avoid numbers that don't fit in uint. // Add 1 to avoid division truncation. sdaTxHoldCnt = ((freqin * 3) / 25000000) + 1 } if sdaTxHoldCnt > lcnt-2 { return errInvalidI2CBaudrate } err := i2c.disable() if err != nil { return err } // Always use "fast" mode (<= 400 kHz, works fine for standard mode too) i2c.Bus.IC_CON.ReplaceBits(rp.I2C0_IC_CON_SPEED_FAST< deadline { return errI2CDisable } } return nil } //go:inline func (i2c *I2C) init(config I2CConfig) error { i2c.reset() if err := i2c.disable(); err != nil { return err } i2c.mode = config.Mode // Configure as fast-mode with RepStart support, 7-bit addresses mode := uint32(rp.I2C0_IC_CON_SPEED_FAST<= 0x80 || isReservedI2CAddr(addr) { return errInvalidTgtAddr } txlen := len(tx) rxlen := len(rx) // Quick return if possible. if txlen == 0 && rxlen == 0 { return nil } err = i2c.disable() if err != nil { return err } i2c.Bus.IC_TAR.Set(uint32(addr)) i2c.enable() abort := false var abortReason i2cAbortError txStop := rxlen == 0 for txCtr := 0; txCtr < txlen; txCtr++ { if abort { break } first := txCtr == 0 last := txCtr == txlen-1 && rxlen == 0 i2c.Bus.IC_DATA_CMD.Set( (boolToBit(first) << rp.I2C0_IC_DATA_CMD_RESTART_Pos) | (boolToBit(last && txStop) << rp.I2C0_IC_DATA_CMD_STOP_Pos) | uint32(tx[txCtr])) // Wait until the transmission of the address/data from the internal // shift register has completed. For this to function correctly, the // TX_EMPTY_CTRL flag in IC_CON must be set. The TX_EMPTY_CTRL flag // was set in i2c_init. // IC_RAW_INTR_STAT_TX_EMPTY: This bit is set to 1 when the transmit buffer is at or below // the threshold value set in the IC_TX_TL register and the // transmission of the address/data from the internal shift // register for the most recently popped command is // completed. It is automatically cleared by hardware when // the buffer level goes above the threshold. When // IC_ENABLE[0] is set to 0, the TX FIFO is flushed and held // in reset. There the TX FIFO looks like it has no data within // it, so this bit is set to 1, provided there is activity in the // controller or target state machines. When there is no longer // any activity, then with ic_en=0, this bit is set to 0. for !i2c.interrupted(rp.I2C0_IC_RAW_INTR_STAT_TX_EMPTY) { if ticks() > deadline { return errI2CWriteTimeout // If there was a timeout, don't attempt to do anything else. } before := ticks() gosched() deadline += ticks() - before } abortReason = i2c.getAbortReason() if abortReason != 0 { i2c.clearAbortReason() abort = true } if abort || last { // If the transaction was aborted or if it completed // successfully wait until the STOP condition has occurred. // TODO Could there be an abort while waiting for the STOP // condition here? If so, additional code would be needed here // to take care of the abort. for !i2c.interrupted(rp.I2C0_IC_RAW_INTR_STAT_STOP_DET) { if ticks() > deadline { if abort { return abortReason } return errI2CWriteTimeout } before := ticks() gosched() deadline += ticks() - before } i2c.Bus.IC_CLR_STOP_DET.Get() } } // Midway check for abort. Related issue https://github.com/tinygo-org/tinygo/issues/3671. // The root cause for an abort after writing registers was "tx data no ack" (abort code=8). // If the abort code was not registered then the whole peripheral would remain in disabled state forever. abortReason = i2c.getAbortReason() if abortReason != 0 { i2c.clearAbortReason() abort = true } rxStart := txlen == 0 if rxlen > 0 && !abort { for rxCtr := 0; rxCtr < rxlen; rxCtr++ { first := rxCtr == 0 last := rxCtr == rxlen-1 for i2c.writeAvailable() == 0 { before := ticks() gosched() deadline += ticks() - before } i2c.Bus.IC_DATA_CMD.Set( boolToBit(first && rxStart)< 1 for read for !abort && i2c.readAvailable() == 0 { abortReason = i2c.getAbortReason() if abortReason != 0 { i2c.clearAbortReason() abort = true } if ticks() > deadline { return errI2CReadTimeout // If there was a timeout, don't attempt to do anything else. } before := ticks() gosched() deadline += ticks() - before } if abort { break } rx[rxCtr] = uint8(i2c.Bus.IC_DATA_CMD.Get()) } } // From Pico SDK: A lot of things could have just happened due to the ingenious and // creative design of I2C. Try to figure things out. if abort { switch { case abortReason == 0 || abortReason&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_7B_ADDR_NOACK != 0: // No reported errors - seems to happen if there is nothing connected to the bus. // Address byte not acknowledged err = errI2CGeneric case abortReason&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_TXDATA_NOACK != 0: // Address acknowledged, some data not acknowledged fallthrough default: err = abortReason } } return err } // listen sets up for async handling of requests on the I2C bus. func (i2c *I2C) listen(addr uint8) error { if addr >= 0x80 || isReservedI2CAddr(addr) { return errInvalidTgtAddr } err := i2c.disable() if err != nil { return err } i2c.Bus.IC_SAR.Set(uint32(addr)) i2c.enable() return nil } func (i2c *I2C) WaitForEvent(buf []byte) (evt I2CTargetEvent, count int, err error) { rxPtr := 0 for { stat := i2c.Bus.IC_RAW_INTR_STAT.Get() if stat&rp.I2C0_IC_INTR_MASK_M_RX_FULL != 0 { b := uint8(i2c.Bus.IC_DATA_CMD.Get()) if rxPtr < len(buf) { buf[rxPtr] = b rxPtr++ } } // Stop if stat&rp.I2C0_IC_INTR_MASK_M_STOP_DET != 0 { if rxPtr > 0 { return I2CReceive, rxPtr, nil } i2c.Bus.IC_CLR_STOP_DET.Get() // clear return I2CFinish, 0, nil } // Start or restart - ignore start, return on restart if stat&rp.I2C0_IC_INTR_MASK_M_START_DET != 0 { i2c.Bus.IC_CLR_START_DET.Get() // clear restart // Restart if rxPtr > 0 { return I2CReceive, rxPtr, nil } } // Read request - leave flag set until we start to reply. if stat&rp.I2C0_IC_INTR_MASK_M_RD_REQ != 0 { return I2CRequest, 0, nil } gosched() } } func (i2c *I2C) Reply(buf []byte) error { txPtr := 0 stat := i2c.Bus.IC_RAW_INTR_STAT.Get() if stat&rp.I2C0_IC_INTR_MASK_M_RD_REQ == 0 { return errI2CWrongMode } i2c.Bus.IC_CLR_RD_REQ.Get() // clear restart // Clear any dangling TX abort if stat&rp.I2C0_IC_INTR_MASK_M_TX_ABRT != 0 { i2c.Bus.IC_CLR_TX_ABRT.Get() } for txPtr < len(buf) { if i2c.Bus.GetIC_RAW_INTR_STAT_TX_EMPTY() != 0 { i2c.Bus.SetIC_DATA_CMD_DAT(uint32(buf[txPtr])) txPtr++ // The DW_apb_i2c flushes/resets/empties the // TX_FIFO and RX_FIFO whenever there is a transmit abort // caused by any of the events tracked by the // IC_TX_ABRT_SOURCE register. // In other words, it's safe to block until TX FIFO is // EMPTY--it will empty from being transmitted or on error. for i2c.Bus.GetIC_RAW_INTR_STAT_TX_EMPTY() == 0 { } } // This Tx abort is a normal case - we're sending more // data than controller wants to receive if i2c.Bus.GetIC_RAW_INTR_STAT_TX_ABRT() != 0 { i2c.Bus.GetIC_CLR_TX_ABRT_CLR_TX_ABRT() return nil } gosched() } return nil } // writeAvailable determines non-blocking write space available // //go:inline func (i2c *I2C) writeAvailable() uint32 { return rp.I2C0_IC_COMP_PARAM_1_TX_BUFFER_DEPTH_Pos - i2c.Bus.IC_TXFLR.Get() } // readAvailable determines number of bytes received // //go:inline func (i2c *I2C) readAvailable() uint32 { return i2c.Bus.IC_RXFLR.Get() } // Equivalent to IC_CLR_TX_ABRT.Get() (side effect clears ABORT_REASON) // //go:inline func (i2c *I2C) clearAbortReason() { // Note clearing the abort flag also clears the reason, and // this instance of flag is clear-on-read! Note also the // IC_CLR_TX_ABRT register always reads as 0. i2c.Bus.IC_CLR_TX_ABRT.Get() } // getAbortReason reads IC_TX_ABRT_SOURCE register. // //go:inline func (i2c *I2C) getAbortReason() i2cAbortError { return i2cAbortError(i2c.Bus.IC_TX_ABRT_SOURCE.Get()) } // returns true if RAW_INTR_STAT bits in mask are all set. performs: // // RAW_INTR_STAT & mask == mask // //go:inline func (i2c *I2C) interrupted(mask uint32) bool { reg := i2c.Bus.IC_RAW_INTR_STAT.Get() return reg&mask == mask } type i2cAbortError uint32 func (b i2cAbortError) Error() string { return "i2c abort, reason " + itoa.Uitoa(uint(b)) } func (b i2cAbortError) Reasons() (reasons []string) { if b == 0 { return nil } if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_7B_ADDR_NOACK != 0 { reasons = append(reasons, "7-bit address no ack") } if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_10ADDR1_NOACK != 0 { reasons = append(reasons, "10-bit address first byte no ack") } if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_10ADDR2_NOACK != 0 { reasons = append(reasons, "10-bit address second byte no ack") } if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_TXDATA_NOACK != 0 { reasons = append(reasons, "tx data no ack") } if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_GCALL_NOACK != 0 { reasons = append(reasons, "general call no ack") } if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_GCALL_READ != 0 { reasons = append(reasons, "general call read") } if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_HS_ACKDET != 0 { reasons = append(reasons, "high speed ack detect") } if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SBYTE_ACKDET != 0 { reasons = append(reasons, "start byte ack detect") } if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_HS_NORSTRT != 0 { reasons = append(reasons, "high speed no restart") } if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SBYTE_NORSTRT != 0 { reasons = append(reasons, "start byte no restart") } if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_10B_RD_NORSTRT != 0 { reasons = append(reasons, "10-bit read no restart") } if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_MASTER_DIS != 0 { reasons = append(reasons, "master disabled") } if b&rp.I2C0_IC_TX_ABRT_SOURCE_ARB_LOST != 0 { reasons = append(reasons, "arbitration lost") } if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SLVFLUSH_TXFIFO != 0 { reasons = append(reasons, "slave flush tx fifo") } if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SLV_ARBLOST != 0 { reasons = append(reasons, "slave arbitration lost") } if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SLVRD_INTX != 0 { reasons = append(reasons, "slave read while inactive") } if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_USER_ABRT != 0 { reasons = append(reasons, "user abort") } return reasons } ================================================ FILE: src/machine/machine_rp2_pins.go ================================================ //go:build rp2040 || rp2350 || gopher_badge || pico package machine const ( // GPIO pins GPIO0 Pin = 0 // peripherals: PWM0 channel A, I2C0 SDA GPIO1 Pin = 1 // peripherals: PWM0 channel B, I2C0 SCL GPIO2 Pin = 2 // peripherals: PWM1 channel A, I2C1 SDA GPIO3 Pin = 3 // peripherals: PWM1 channel B, I2C1 SCL GPIO4 Pin = 4 // peripherals: PWM2 channel A, I2C0 SDA GPIO5 Pin = 5 // peripherals: PWM2 channel B, I2C0 SCL GPIO6 Pin = 6 // peripherals: PWM3 channel A, I2C1 SDA GPIO7 Pin = 7 // peripherals: PWM3 channel B, I2C1 SCL GPIO8 Pin = 8 // peripherals: PWM4 channel A, I2C0 SDA GPIO9 Pin = 9 // peripherals: PWM4 channel B, I2C0 SCL GPIO10 Pin = 10 // peripherals: PWM5 channel A, I2C1 SDA GPIO11 Pin = 11 // peripherals: PWM5 channel B, I2C1 SCL GPIO12 Pin = 12 // peripherals: PWM6 channel A, I2C0 SDA GPIO13 Pin = 13 // peripherals: PWM6 channel B, I2C0 SCL GPIO14 Pin = 14 // peripherals: PWM7 channel A, I2C1 SDA GPIO15 Pin = 15 // peripherals: PWM7 channel B, I2C1 SCL GPIO16 Pin = 16 // peripherals: PWM0 channel A, I2C0 SDA GPIO17 Pin = 17 // peripherals: PWM0 channel B, I2C0 SCL GPIO18 Pin = 18 // peripherals: PWM1 channel A, I2C1 SDA GPIO19 Pin = 19 // peripherals: PWM1 channel B, I2C1 SCL GPIO20 Pin = 20 // peripherals: PWM2 channel A, I2C0 SDA GPIO21 Pin = 21 // peripherals: PWM2 channel B, I2C0 SCL GPIO22 Pin = 22 // peripherals: PWM3 channel A GPIO23 Pin = 23 // peripherals: PWM3 channel B GPIO24 Pin = 24 // peripherals: PWM4 channel A GPIO25 Pin = 25 // peripherals: PWM4 channel B GPIO26 Pin = 26 // peripherals: PWM5 channel A, I2C1 SDA GPIO27 Pin = 27 // peripherals: PWM5 channel B, I2C1 SCL GPIO28 Pin = 28 // peripherals: PWM6 channel A GPIO29 Pin = 29 // peripherals: PWM6 channel B ) ================================================ FILE: src/machine/machine_rp2_pll.go ================================================ //go:build rp2040 || rp2350 package machine import ( "device/rp" "errors" "math" "math/bits" "runtime/volatile" "unsafe" ) type pll struct { cs volatile.Register32 pwr volatile.Register32 fbDivInt volatile.Register32 prim volatile.Register32 } var ( pllSys = (*pll)(unsafe.Pointer(rp.PLL_SYS)) pllUSB = (*pll)(unsafe.Pointer(rp.PLL_USB)) ) // init initializes pll (Sys or USB) given the following parameters. // // Input clock divider, refdiv. // // Requested output frequency from the VCO (voltage controlled oscillator), vcoFreq. // // Post Divider 1, postDiv1 with range 1-7 and be >= postDiv2. // // Post Divider 2, postDiv2 with range 1-7. func (pll *pll) init(refdiv, fbdiv, postDiv1, postDiv2 uint32) { refFreq := xoscFreq / refdiv // What are we multiplying the reference clock by to get the vco freq // (The regs are called div, because you divide the vco output and compare it to the refclk) // Check fbdiv range if !(fbdiv >= 16 && fbdiv <= 320) { panic("fbdiv should be in the range [16,320]") } // Check divider ranges if !((postDiv1 >= 1 && postDiv1 <= 7) && (postDiv2 >= 1 && postDiv2 <= 7)) { panic("postdiv1, postdiv1 should be in the range [1,7]") } // postDiv1 should be >= postDiv2 // from appnote page 11 // postdiv1 is designed to operate with a higher input frequency // than postdiv2 if postDiv1 < postDiv2 { panic("postdiv1 should be greater than or equal to postdiv2") } // Check that reference frequency is no greater than vcoFreq / 16 vcoFreq := calcVCO(xoscFreq, fbdiv, refdiv) if refFreq > vcoFreq/16 { panic("reference frequency should not be greater than vco frequency divided by 16") } // div1 feeds into div2 so if div1 is 5 and div2 is 2 then you get a divide by 10 pdiv := uint32(postDiv1)< maxVCO { break } calcPD12 := vco / targetFreq if calcPD12 < 1 { calcPD12 = 1 } else if calcPD12 > 49 { calcPD12 = 49 } iters++ pd1 = pdTable[calcPD12].hivco[0] pd2 = pdTable[calcPD12].hivco[1] fout, err := pllFreqOutPostdiv(xoscRef, fbdiv, MHz, refdiv, pd1, pd2) found := false margin := abs(int64(fout) - int64(targetFreq)) if err == nil && margin <= bestMargin { found = true bestFreq = fout bestFbdiv = fbdiv bestpd1 = pd1 bestpd2 = pd2 bestRefdiv = refdiv bestMargin = margin } pd1 = pdTable[calcPD12].lovco[0] pd2 = pdTable[calcPD12].lovco[1] fout, err = pllFreqOutPostdiv(xoscRef, fbdiv, MHz, refdiv, pd1, pd2) margin = abs(int64(fout) - int64(targetFreq)) if err == nil && margin <= bestMargin { found = true bestFreq = fout bestFbdiv = fbdiv bestpd1 = pd1 bestpd2 = pd2 bestRefdiv = refdiv bestMargin = margin } if found && ps.LowerVCO { break } } } if bestFreq == 0 { return fbdiv, refdiv, pd1, pd2, errors.New("no best frequency found") } return bestFbdiv, bestRefdiv, bestpd1, bestpd2, nil } func abs(a int64) int64 { if a == math.MinInt64 { return math.MaxInt64 } else if a < 0 { return -a } return a } func pllFreqOutPostdiv(xosc, fbdiv, MHz uint64, refdiv, postdiv1, postdiv2 uint8) (foutpostdiv uint64, err error) { // testing grounds. const ( mhz = 1 cfref = 12 * mhz // given by crystal oscillator selection. crefd = 1 cfbdiv = 100 cvco = cfref * cfbdiv / crefd cpd1 = 6 cpd2 = 2 foutpd = (cfref / crefd) * cfbdiv / (cpd1 * cpd2) ) refFreq := xosc / uint64(refdiv) overflow, vco := bits.Mul64(xosc, fbdiv) vco /= uint64(refdiv) foutpostdiv = vco / uint64(postdiv1*postdiv2) switch { case refdiv < 1 || refdiv > 63: err = errors.New("reference divider out of range") case fbdiv < 16 || fbdiv > 320: err = errors.New("feedback divider out of range") case postdiv1 < 1 || postdiv1 > 7: err = errors.New("postdiv1 out of range") case postdiv2 < 1 || postdiv2 > 7: err = errors.New("postdiv2 out of range") case postdiv1 < postdiv2: err = errors.New("user error: use higher value for postdiv1 for lower power consumption") case vco < 750*MHz || vco > 1600*MHz: err = errors.New("VCO out of range") case refFreq < 5*MHz: err = errors.New("minimum reference frequency breach") case refFreq > vco/16: err = errors.New("maximum reference frequency breach") case vco > 1200*MHz && vco < 1600*MHz && xosc < 75*MHz && refdiv != 1: err = errors.New("refdiv should be 1 for given VCO and reference frequency") case overflow != 0: err = errVCOOverflow } if err != nil { return 0, err } return foutpostdiv, nil } func calcVCO(xoscFreq, fbdiv, refdiv uint32) uint32 { const maxXoscMHz = math.MaxUint32 / 320 / MHz // 13MHz maximum xosc apparently. if fbdiv > 320 || xoscFreq > math.MaxUint32/320 { panic("invalid VCO calculation args") } return xoscFreq * fbdiv / refdiv } var pdTable = [50]struct { hivco [2]uint8 lovco [2]uint8 }{} func genTable() { if pdTable[1].hivco[1] != 0 { return // Already generated. } for product := 1; product < len(pdTable); product++ { bestProdhi := 255 bestProdlo := 255 for pd1 := 7; pd1 > 0; pd1-- { for pd2 := pd1; pd2 > 0; pd2-- { gotprod := pd1 * pd2 if abs(int64(gotprod-product)) < abs(int64(bestProdlo-product)) { bestProdlo = gotprod pdTable[product].lovco[0] = uint8(pd1) pdTable[product].lovco[1] = uint8(pd2) } } } for pd1 := 1; pd1 < 8; pd1++ { for pd2 := 1; pd2 <= pd1; pd2++ { gotprod := pd1 * pd2 if abs(int64(gotprod-product)) < abs(int64(bestProdhi-product)) { bestProdhi = gotprod pdTable[product].hivco[0] = uint8(pd1) pdTable[product].hivco[1] = uint8(pd2) } } } } } ================================================ FILE: src/machine/machine_rp2_pwm.go ================================================ //go:build rp2040 || rp2350 package machine import ( "device/rp" "errors" "math" "runtime/volatile" "unsafe" ) var ( ErrBadPeriod = errors.New("period outside valid range 8ns..268ms") ) const ( maxPWMPins = _NUMBANK0_GPIOS - 1 ) // pwmGroup is one PWM peripheral, which consists of a counter and two output // channels. You can set the frequency using SetPeriod, // but only for all the channels in this PWM peripheral at once. // // div: integer value to reduce counting rate by. Must be greater than or equal to 1. // // cc: counter compare level. Contains 2 channel levels. The 16 LSBs are Channel A's level (Duty Cycle) // and the 16 MSBs are Channel B's level. // // top: Wrap. Highest number counter will reach before wrapping over. usually 0xffff. // // csr: Clock mode. PWM_CH0_CSR_DIVMODE_xxx registers have 4 possible modes, of which Free-running is used. // csr contains output polarity bit at PWM_CH0_CSR_x_INV where x is the channel. // csr contains phase correction bit at PWM_CH0_CSR_PH_CORRECT_Msk. // csr contains PWM enable bit at PWM_CH0_CSR_EN. If not enabled PWM will not be active. // // ctr: PWM counter value. type pwmGroup struct { CSR volatile.Register32 DIV volatile.Register32 CTR volatile.Register32 CC volatile.Register32 TOP volatile.Register32 } // Equivalent of // // var pwmSlice []pwmGroup = (*[8]pwmGroup)(unsafe.Pointer(rp.PWM))[:] // return &pwmSlice[index] // // 0x14 is the size of a pwmGroup. func getPWMGroup(index uintptr) *pwmGroup { return (*pwmGroup)(unsafe.Add(unsafe.Pointer(rp.PWM), 0x14*index)) } // Hardware Pulse Width Modulation (PWM) API // PWM peripherals available on RP2040. Each peripheral has 2 pins available for // a total of 16 available PWM outputs. Some pins may not be available on some boards. // // The RP2040 PWM block has 8 identical slices. Each slice can drive two PWM output signals, or // measure the frequency or duty cycle of an input signal. This gives a total of up to 16 controllable // PWM outputs. All 30 GPIOs can be driven by the PWM block // // The PWM hardware functions by continuously comparing the input value to a free-running counter. This produces a // toggling output where the amount of time spent at the high output level is proportional to the input value. The fraction of // time spent at the high signal level is known as the duty cycle of the signal. // // The default behaviour of a PWM slice is to count upward until the wrap value (\ref pwm_config_set_wrap) is reached, and then // immediately wrap to 0. PWM slices also offer a phase-correct mode, where the counter starts to count downward after // reaching TOP, until it reaches 0 again. var ( PWM0 = getPWMGroup(0) PWM1 = getPWMGroup(1) PWM2 = getPWMGroup(2) PWM3 = getPWMGroup(3) PWM4 = getPWMGroup(4) PWM5 = getPWMGroup(5) PWM6 = getPWMGroup(6) PWM7 = getPWMGroup(7) ) // Configure enables and configures this PWM. func (pwm *pwmGroup) Configure(config PWMConfig) error { return pwm.init(config, true) } // Channel returns a PWM channel for the given pin. If pin does // not belong to PWM peripheral ErrInvalidOutputPin error is returned. // It also configures pin as PWM output. func (pwm *pwmGroup) Channel(pin Pin) (channel uint8, err error) { if pin > maxPWMPins || pwmGPIOToSlice(pin) != pwm.peripheral() { return 3, ErrInvalidOutputPin } pin.Configure(PinConfig{PinPWM}) return pwmGPIOToChannel(pin), nil } // Peripheral returns the RP2040 PWM peripheral which ranges from 0 to 7. Each // PWM peripheral has 2 channels, A and B which correspond to 0 and 1 in the program. // This number corresponds to the package's PWM0 throughout PWM7 handles func PWMPeripheral(pin Pin) (sliceNum uint8, err error) { if pin > maxPWMPins { return 0, ErrInvalidOutputPin } return pwmGPIOToSlice(pin), nil } // returns the number of the pwm peripheral (0-7) func (pwm *pwmGroup) peripheral() uint8 { return uint8((uintptr(unsafe.Pointer(pwm)) - uintptr(unsafe.Pointer(rp.PWM))) / 0x14) } // SetPeriod updates the period of this PWM peripheral in nanoseconds. // To set a particular frequency, use the following formula: // // period = 1e9 / frequency // // Where frequency is in hertz. If you use a period of 0, a period // that works well for LEDs will be picked. // // SetPeriod will try not to modify TOP if possible to reach the target period. // If the period is unattainable with current TOP SetPeriod will modify TOP // by the bare minimum to reach the target period. It will also enable phase // correct to reach periods above 130ms. func (p *pwmGroup) SetPeriod(period uint64) error { if period == 0 { period = 1e5 } return p.setPeriod(period) } // Top returns the current counter top, for use in duty cycle calculation. // // The value returned here is hardware dependent. In general, it's best to treat // it as an opaque value that can be divided by some number and passed to Set // (see Set documentation for more information). func (p *pwmGroup) Top() uint32 { return p.getWrap() } // Counter returns the current counter value of the timer in this PWM // peripheral. It may be useful for debugging. func (p *pwmGroup) Counter() uint32 { return (p.CTR.Get() & rp.PWM_CH0_CTR_CH0_CTR_Msk) >> rp.PWM_CH0_CTR_CH0_CTR_Pos } // Period returns the used PWM period in nanoseconds. func (p *pwmGroup) Period() uint64 { // Lines below can overflow if operations done without care. // maxInt=255, maxFrac=15, maxTop=65536, maxPHC=1 => maxProduct= (16*255+15) * (65536*2*1e9) = 5.3673e17 < MaxUint64=1.8e19 (close call.) const compileTimeCheckPeriod uint64 = (255*16 + 15) * (65535 + 1) * 2 * 1e9 freq := uint64(CPUFrequency()) top := p.getWrap() phc := p.getPhaseCorrect() Int, frac := p.getClockDiv() return (16*uint64(Int) + uint64(frac)) * uint64((top+1)*(phc+1)*1e9) / (16 * freq) // cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16) } // SetInverting sets whether to invert the output of this channel. // Without inverting, a 25% duty cycle would mean the output is high for 25% of // the time and low for the rest. Inverting flips the output as if a NOT gate // was placed at the output, meaning that the output would be 25% low and 75% // high with a duty cycle of 25%. func (p *pwmGroup) SetInverting(channel uint8, inverting bool) { channel &= 1 p.setInverting(channel, inverting) } // Set updates the channel value. This is used to control the channel duty // cycle, in other words the fraction of time the channel output is high (or low // when inverted). For example, to set it to a 25% duty cycle, use: // // pwm.Set(channel, pwm.Top() / 4) // // pwm.Set(channel, 0) will set the output to low and pwm.Set(channel, // pwm.Top()) will set the output to high, assuming the output isn't inverted. func (p *pwmGroup) Set(channel uint8, value uint32) { val := uint16(value) channel &= 1 p.setChanLevel(channel, val) } // Get current level (last set by Set). Default value on initialization is 0. func (p *pwmGroup) Get(channel uint8) (value uint32) { channel &= 1 return uint32(p.getChanLevel(channel)) } // SetTop sets TOP control register. Max value is 16bit (0xffff). func (p *pwmGroup) SetTop(top uint32) { p.setWrap(uint16(top)) } // SetCounter sets counter control register. Max value is 16bit (0xffff). // Useful for synchronising two different PWM peripherals. func (p *pwmGroup) SetCounter(ctr uint32) { p.CTR.Set(ctr) } // Enable enables or disables PWM peripheral channels. func (p *pwmGroup) Enable(enable bool) { p.enable(enable) } // IsEnabled returns true if peripheral is enabled. func (p *pwmGroup) IsEnabled() (enabled bool) { return (p.CSR.Get()&rp.PWM_CH0_CSR_EN_Msk)>>rp.PWM_CH0_CSR_EN_Pos != 0 } // Initialise a PWM with settings from a configuration object. // If start is true then PWM starts on initialization. func (pwm *pwmGroup) init(config PWMConfig, start bool) error { // Not enable Phase correction pwm.setPhaseCorrect(false) // Clock mode set by default to Free running pwm.setDivMode(rp.PWM_CH0_CSR_DIVMODE_DIV) // Set Output polarity (false/false) pwm.setInverting(0, false) pwm.setInverting(1, false) // Set wrap. The highest value the counter will reach before returning to zero, also known as TOP. pwm.setWrap(0xffff) // period is set after TOP (Wrap). err := pwm.SetPeriod(config.Period) if err != nil { return err } // period already set beforea // Reset counter and compare (pwm level set to zero) pwm.CTR.ReplaceBits(0, rp.PWM_CH0_CTR_CH0_CTR_Msk, 0) // PWM_CH0_CTR_RESET pwm.CC.Set(0) // PWM_CH0_CC_RESET pwm.enable(start) return nil } func (pwm *pwmGroup) setPhaseCorrect(correct bool) { pwm.CSR.ReplaceBits(boolToBit(correct)< maxPeriod || period < 8 { return ErrBadPeriod } if period > maxPeriod/2 { pwm.setPhaseCorrect(true) // Must enable Phase correct to reach large periods. } // clearing above expression: // DIV_INT + DIV_FRAC/16 = cycles / ( (TOP+1) * (CSRPHCorrect+1) ) // DIV_FRAC/16 is always 0 in this equation // where cycles must be converted to time: // target_period = cycles * period_per_cycle ==> cycles = target_period/period_per_cycle var ( freq = uint64(CPUFrequency()) phc = uint64(pwm.getPhaseCorrect()) rhs = 16 * period * freq / ((1 + phc) * 1e9 * (1 + topStart)) // right-hand-side of equation, scaled so frac is not divided whole = rhs / 16 frac = rhs % 16 ) switch { case whole > 0xff: whole = 0xff case whole == 0: // whole calculation underflowed so setting to minimum // permissible value in DIV_INT register. whole = 1 frac = 0 } // Step 2 is acquiring a better top value. Clearing the equation: // TOP = cycles / ( (DIVINT+DIVFRAC/16) * (CSRPHCorrect+1) ) - 1 top := 16*period*freq/((1+phc)*1e9*(16*whole+frac)) - 1 if top > maxTop { top = maxTop } pwm.SetTop(uint32(top)) pwm.setClockDiv(uint8(whole), uint8(frac)) return nil } // Int is integer value to reduce counting rate by. Must be greater than or equal to 1. DIV_INT is bits 4:11 (8 bits). // frac's (DIV_FRAC) default value on reset is 0. Max value for frac is 15 (4 bits). This is known as a fixed-point // fractional number. // // cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16) func (pwm *pwmGroup) setClockDiv(Int, frac uint8) { pwm.DIV.ReplaceBits((uint32(frac)<> pos) return level } func (pwm *pwmGroup) getWrap() (top uint32) { return (pwm.TOP.Get() & rp.PWM_CH0_TOP_CH0_TOP_Msk) >> rp.PWM_CH0_TOP_CH0_TOP_Pos } func (pwm *pwmGroup) getPhaseCorrect() (phCorrect uint32) { return (pwm.CSR.Get() & rp.PWM_CH0_CSR_PH_CORRECT_Msk) >> rp.PWM_CH0_CSR_PH_CORRECT_Pos } func (pwm *pwmGroup) getClockDiv() (Int, frac uint8) { div := pwm.DIV.Get() return uint8((div & rp.PWM_CH0_DIV_INT_Msk) >> rp.PWM_CH0_DIV_INT_Pos), uint8((div & rp.PWM_CH0_DIV_FRAC_Msk) >> rp.PWM_CH0_DIV_FRAC_Pos) } // pwmGPIOToSlice Determine the PWM channel that is attached to the specified GPIO. // gpio must be less than 30. Returns the PWM slice number that controls the specified GPIO. func pwmGPIOToSlice(gpio Pin) (slicenum uint8) { if is48Pin && gpio >= 32 { return uint8(8 + ((gpio-32)/2)%4) } return (uint8(gpio) >> 1) & 7 } // Determine the PWM channel that is attached to the specified GPIO. // Each slice 0 to 7 has two channels, A and B. func pwmGPIOToChannel(gpio Pin) (channel uint8) { return uint8(gpio) & 1 } ================================================ FILE: src/machine/machine_rp2_resets.go ================================================ //go:build rp2040 || rp2350 package machine import ( "device/rp" "unsafe" ) var resets = (*rp.RESETS_Type)(unsafe.Pointer(rp.RESETS)) // resetBlock resets hardware blocks specified // by the bit pattern in bits. func resetBlock(bits uint32) { resets.RESET.SetBits(bits) } // unresetBlock brings hardware blocks specified by the // bit pattern in bits out of reset. func unresetBlock(bits uint32) { resets.RESET.ClearBits(bits) } // unresetBlockWait brings specified hardware blocks // specified by the bit pattern in bits // out of reset and wait for completion. func unresetBlockWait(bits uint32) { unresetBlock(bits) for !resets.RESET_DONE.HasBits(bits) { } } ================================================ FILE: src/machine/machine_rp2_rng.go ================================================ //go:build rp2040 || rp2350 // Implementation based on code located here: // https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/pico_lwip/random.c package machine import ( "device/rp" ) const numberOfCycles = 32 // GetRNG returns 32 bits of semi-random data based on ring oscillator. // // Unlike some other implementations of GetRNG, these random numbers are not // cryptographically secure and must not be used for cryptographic operations // (nonces, etc). func GetRNG() (uint32, error) { var val uint32 for i := 0; i < 4; i++ { val = (val << 8) | uint32(roscRandByte()) } return val, nil } var randomByte uint8 func roscRandByte() uint8 { var poly uint8 for i := 0; i < numberOfCycles; i++ { if randomByte&0x80 != 0 { poly = 0x35 } else { poly = 0 } randomByte = ((randomByte << 1) | uint8(rp.ROSC.GetRANDOMBIT()) ^ poly) // TODO: delay a little because the random bit is a little slow } return randomByte } ================================================ FILE: src/machine/machine_rp2_spi.go ================================================ //go:build rp2040 || rp2350 package machine import ( "device/rp" "errors" "unsafe" ) // SPI on the RP2040 var ( SPI0 = &SPI{ Bus: rp.SPI0, } SPI1 = &SPI{ Bus: rp.SPI1, } ) // SPIConfig is used to store config info for SPI. type SPIConfig struct { Frequency uint32 // LSB not supported on rp2040. LSBFirst bool // Mode's two most LSB are CPOL and CPHA. i.e. Mode==2 (0b10) is CPOL=1, CPHA=0 Mode uint8 // Serial clock pin SCK Pin // TX or Serial Data Out (MOSI if rp2040 is master) SDO Pin // RX or Serial Data In (MISO if rp2040 is master) SDI Pin } var ( ErrLSBNotSupported = errors.New("SPI LSB unsupported on PL022") ErrSPITimeout = errors.New("SPI timeout") ErrSPIBaud = errors.New("SPI baud too low or above 66.5Mhz") errSPIInvalidSDI = errors.New("invalid SPI SDI pin") errSPIInvalidSDO = errors.New("invalid SPI SDO pin") errSPIInvalidSCK = errors.New("invalid SPI SCK pin") ) type SPI struct { Bus *rp.SPI0_Type } // Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read // interface, there must always be the same number of bytes written as bytes read. // The Tx method knows about this, and offers a few different ways of calling it. // // This form sends the bytes in tx buffer, putting the resulting bytes read into the rx buffer. // Note that the tx and rx buffers must be the same size: // // spi.Tx(tx, rx) // // This form sends the tx buffer, ignoring the result. Useful for sending "commands" that return zeros // until all the bytes in the command packet have been received: // // spi.Tx(tx, nil) // // This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": // // spi.Tx(nil, rx) // // Remark: This implementation (RP2040) allows reading into buffer with a custom repeated // value on tx. // // spi.Tx([]byte{0xff}, rx) // may cause unwanted heap allocations. // // This form sends 0xff and puts the result into rx buffer. Useful for reading from SD cards // which require 0xff input on SI. func (spi *SPI) Tx(w, r []byte) (err error) { switch { case w == nil: // read only, so write zero and read a result. err = spi.rx(r, 0) case r == nil: // write only err = spi.tx(w) case len(w) == 1 && len(r) > 1: // Read with custom repeated value. err = spi.rx(r, w[0]) default: // write/read err = spi.txrx(w, r) } return err } // Write a single byte and read a single byte from TX/RX FIFO. func (spi *SPI) Transfer(w byte) (byte, error) { for !spi.isWritable() { } spi.Bus.SSPDR.Set(uint32(w)) for !spi.isReadable() { } return uint8(spi.Bus.SSPDR.Get()), nil } func (spi *SPI) SetBaudRate(br uint32) error { const maxBaud uint32 = 66.5 * MHz // max output frequency is 66.5MHz on rp2040. see Note page 527. // Find smallest prescale value which puts output frequency in range of // post-divide. Prescale is an even number from 2 to 254 inclusive. var prescale, postdiv uint32 freq := CPUFrequency() for prescale = 2; prescale < 255; prescale += 2 { if uint64(freq) < uint64((prescale+2)*256)*uint64(br) { break } } if prescale > 254 || br > maxBaud { return ErrSPIBaud } // Find largest post-divide which makes output <= baudrate. Post-divide is // an integer in the range 1 to 256 inclusive. for postdiv = 256; postdiv > 1; postdiv-- { if freq/(prescale*(postdiv-1)) > br { break } } spi.Bus.SSPCPSR.Set(prescale) spi.Bus.SSPCR0.ReplaceBits((postdiv-1)<> rp.SPI0_SSPCR0_SCR_Pos) + 1 return freqin / (prescale * postdiv) } // Configure is intended to setup/initialize the SPI interface. // Default baudrate of 4MHz is used if Frequency == 0. Default // word length (data bits) is 8. // Below is a list of GPIO pins corresponding to SPI0 bus on the rp2040: // // SI : 0, 4, 17 a.k.a RX and MISO (if rp2040 is master) // SO : 3, 7, 19 a.k.a TX and MOSI (if rp2040 is master) // SCK: 2, 6, 18 // // SPI1 bus GPIO pins: // // SI : 8, 12 // SO : 11, 15 // SCK: 10, 14 // // No pin configuration is needed of SCK, SDO and SDI needed after calling Configure. func (spi *SPI) Configure(config SPIConfig) error { const defaultBaud uint32 = 4 * MHz if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { // set default pins if config zero valued or invalid clock pin supplied. switch spi.Bus { case rp.SPI0: config.SCK = SPI0_SCK_PIN config.SDO = SPI0_SDO_PIN config.SDI = SPI0_SDI_PIN case rp.SPI1: config.SCK = SPI1_SCK_PIN config.SDO = SPI1_SDO_PIN config.SDI = SPI1_SDI_PIN } } if err := spi.validPins(config); err != nil { return err } if config.Frequency == 0 { config.Frequency = defaultBaud } // SPI pin configuration config.SCK.setFunc(fnSPI) config.SDO.setFunc(fnSPI) config.SDI.setFunc(fnSPI) return spi.initSPI(config) } func (spi *SPI) initSPI(config SPIConfig) (err error) { spi.reset() // LSB-first not supported on PL022: if config.LSBFirst { return ErrLSBNotSupported } err = spi.SetBaudRate(config.Frequency) // Set SPI Format (CPHA and CPOL) and frame format (default is Motorola) spi.setFormat(config.Mode) // Always enable DREQ signals -- harmless if DMA is not listening spi.Bus.SSPDMACR.SetBits(rp.SPI0_SSPDMACR_TXDMAE | rp.SPI0_SSPDMACR_RXDMAE) // Finally enable the SPI spi.Bus.SSPCR1.SetBits(rp.SPI0_SSPCR1_SSE) return err } //go:inline func (spi *SPI) setFormat(mode uint8) { cpha := uint32(mode) & 1 cpol := uint32(mode>>1) & 1 spi.Bus.SSPCR0.ReplaceBits( (cpha<>3].Set(p.ioIntBit(change)) } // Basic interrupt setting via ioBANK0 for GPIO interrupts. func (p Pin) setInterrupt(change PinChange, enabled bool) { // Separate mask/force/status per-core, so check which core called, and // set the relevant IRQ controls. switch CurrentCore() { case 0: p.ctrlSetInterrupt(change, enabled, &ioBank0.proc0IRQctrl) case 1: p.ctrlSetInterrupt(change, enabled, &ioBank0.proc1IRQctrl) } } // ctrlSetInterrupt acknowledges any pending interrupt and enables or disables // the interrupt for a given IRQ control bank (IOBANK, DormantIRQ, QSPI). // // pico-sdk calls this the _gpio_set_irq_enabled, not to be confused with // gpio_set_irq_enabled (no leading underscore). func (p Pin) ctrlSetInterrupt(change PinChange, enabled bool, base *irqCtrl) { p.acknowledgeInterrupt(change) enReg := &base.intE[p>>3] if enabled { enReg.SetBits(p.ioIntBit(change)) } else { enReg.ClearBits(p.ioIntBit(change)) } } ================================================ FILE: src/machine/machine_rp2_timer.go ================================================ //go:build rp2040 || rp2350 package machine import ( "device/arm" "runtime/interrupt" "runtime/volatile" ) const numTimers = 4 // Alarm0 is reserved for sleeping by tinygo runtime code for RP2040. // Alarm0 is also IRQ0 const sleepAlarm = 0 const sleepAlarmIRQ = 0 // The minimum sleep duration in μs (ticks) const minSleep = 10 type timerType struct { timeHW volatile.Register32 timeLW volatile.Register32 timeHR volatile.Register32 timeLR volatile.Register32 alarm [numTimers]volatile.Register32 armed volatile.Register32 timeRawH volatile.Register32 timeRawL volatile.Register32 dbgPause volatile.Register32 pause volatile.Register32 locked [rp2350ExtraReg]volatile.Register32 source [rp2350ExtraReg]volatile.Register32 intR volatile.Register32 intE volatile.Register32 intF volatile.Register32 intS volatile.Register32 } // TimeElapsed returns time elapsed since power up, in microseconds. func (tmr *timerType) timeElapsed() (us uint64) { // Need to make sure that the upper 32 bits of the timer // don't change, so read that first hi := tmr.timeRawH.Get() var lo, nextHi uint32 for { // Read the lower 32 bits lo = tmr.timeRawL.Get() // Now read the upper 32 bits again and // check that it hasn't incremented. If it has, loop around // and read the lower 32 bits again to get an accurate value nextHi = tmr.timeRawH.Get() if hi == nextHi { break } hi = nextHi } return uint64(hi)<<32 | uint64(lo) } // lightSleep will put the processor into a sleep state a short period // (up to approx 72mins per RP2040 datasheet, 4.6.3. Alarms). // // This function is a 'light' sleep and will return early if another // interrupt or event triggers. This is intentional since the // primary use-case is for use by the TinyGo scheduler which will // re-sleep if needed. func (tmr *timerType) lightSleep(us uint64) { // minSleep is a way to avoid race conditions for short // sleeps by ensuring there is enough time to setup the // alarm before sleeping. For very short sleeps, this // effectively becomes a 'busy loop'. if us < minSleep { return } // Interrupt handler is essentially a no-op, we're just relying // on the side-effect of waking the CPU from "wfe" intr := interrupt.New(sleepAlarmIRQ, func(interrupt.Interrupt) { // Clear the IRQ timer.intR.Set(1 << sleepAlarm) }) // Reset interrupt flag tmr.intR.Set(1 << sleepAlarm) // Enable interrupt tmr.intE.SetBits(1 << sleepAlarm) intr.Enable() // Only the low 32 bits of time can be used for alarms target := uint64(tmr.timeRawL.Get()) + us tmr.alarm[sleepAlarm].Set(uint32(target)) // Wait for sleep (or any other) interrupt arm.Asm("wfe") // Disarm timer tmr.armed.Set(1 << sleepAlarm) // Disable interrupt intr.Disable() } // setDbgPause sets whether this timer is paused when a debugger is connected. func (tmr *timerType) setDbgPause(enable bool) { const bitPos = 1 const bitMask = 0b11 val := uint32(0b00) if enable { // Disable timer when debugger is connected to either core. val = 0b11 } tmr.dbgPause.ReplaceBits(val, bitMask, bitPos) } ================================================ FILE: src/machine/machine_rp2_uart.go ================================================ //go:build rp2040 || rp2350 package machine import ( "device/rp" "runtime/interrupt" ) // UART on the RP2040. type UART struct { Buffer *RingBuffer Bus *rp.UART0_Type Interrupt interrupt.Interrupt } // Configure the UART. func (uart *UART) Configure(config UARTConfig) error { initUART(uart) // Default baud rate to 115200. if config.BaudRate == 0 { config.BaudRate = 115200 } // Use default pins if pins are not set. if config.TX == 0 && config.RX == 0 { // use default pins config.TX = UART_TX_PIN config.RX = UART_RX_PIN } uart.SetBaudRate(config.BaudRate) // default to 8-1-N uart.SetFormat(8, 1, ParityNone) // Enable the UART, both TX and RX settings := uint32(rp.UART0_UARTCR_UARTEN | rp.UART0_UARTCR_RXE | rp.UART0_UARTCR_TXE) const bits = rp.UART0_UARTCR_UARTEN | rp.UART0_UARTCR_TXE if config.RTS != 0 { settings |= rp.UART0_UARTCR_RTSEN } if config.CTS != 0 { settings |= rp.UART0_UARTCR_CTSEN } uart.Bus.UARTCR.SetBits(settings) // set GPIO mux to UART for the pins if config.TX != NoPin { config.TX.Configure(PinConfig{Mode: PinUART}) } if config.RX != NoPin { config.RX.Configure(PinConfig{Mode: PinUART}) } if config.RTS != 0 { config.RTS.Configure(PinConfig{Mode: PinOutput}) } if config.CTS != 0 { config.CTS.Configure(PinConfig{Mode: PinInput}) } // Enable RX IRQ. uart.Interrupt.SetPriority(0x80) uart.Interrupt.Enable() // Setup interrupt on receive. uart.Bus.UARTIMSC.Set(rp.UART0_UARTIMSC_RXIM) return nil } // SetBaudRate sets the baudrate to be used for the UART. func (uart *UART) SetBaudRate(br uint32) { div := 8 * CPUFrequency() / br ibrd := div >> 7 var fbrd uint32 switch { case ibrd == 0: ibrd = 1 fbrd = 0 case ibrd >= 65535: ibrd = 65535 fbrd = 0 default: fbrd = ((div & 0x7f) + 1) / 2 } // set PL011 baud divisor registers uart.Bus.UARTIBRD.Set(ibrd) uart.Bus.UARTFBRD.Set(fbrd) // PL011 needs a (dummy) line control register write. // See https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_uart/uart.c#L93-L95 uart.Bus.UARTLCR_H.SetBits(0) } // WriteByte writes a byte of data to the UART. func (uart *UART) writeByte(c byte) error { // wait until buffer is not full for uart.Bus.UARTFR.HasBits(rp.UART0_UARTFR_TXFF) { if !interrupt.In() { gosched() } } // write data uart.Bus.UARTDR.Set(uint32(c)) return nil } func (uart *UART) flush() { for uart.Bus.UARTFR.HasBits(rp.UART0_UARTFR_BUSY) { if !interrupt.In() { gosched() } } } // SetFormat for number of data bits, stop bits, and parity for the UART. func (uart *UART) SetFormat(databits, stopbits uint8, parity UARTParity) error { var pen, pev uint8 if parity != ParityNone { pen = rp.UART0_UARTLCR_H_PEN } if parity == ParityEven { pev = rp.UART0_UARTLCR_H_EPS } uart.Bus.UARTLCR_H.SetBits(uint32((databits-5)< usb.EndpointPacketSize { count = usb.EndpointPacketSize sendOnEP0DATADONE.offset = count sendOnEP0DATADONE.data = data } else { sendOnEP0DATADONE.offset = 0 } epXdata0[ep] = true } sendViaEPIn(ep, data, count) } func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) { var b [cdcLineInfoSize]byte ep := 0 for !_usbDPSRAM.EPxBufferControl[ep].Out.HasBits(usbBuf0CtrlFull) { // TODO: timeout } ctrl := _usbDPSRAM.EPxBufferControl[ep].Out.Get() _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask) sz := ctrl & usbBuf0CtrlLenMask copy(b[:], _usbDPSRAM.EPxBuffer[ep].Buffer0[:sz]) _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid) _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) return b, nil } func handleEndpointRx(ep uint32) []byte { ctrl := _usbDPSRAM.EPxBufferControl[ep].Out.Get() _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask) sz := ctrl & usbBuf0CtrlLenMask return _usbDPSRAM.EPxBuffer[ep].Buffer0[:sz] } // AckUsbOutTransfer is called to acknowledge the completion of a USB OUT transfer. func AckUsbOutTransfer(ep uint32) { ep = ep & 0x7F setEPDataPID(ep, !epXdata0[ep]) } // Set the USB endpoint Packet ID to DATA0 or DATA1. func setEPDataPID(ep uint32, dataOne bool) { epXdata0[ep] = dataOne if epXdata0[ep] || ep == 0 { _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid) } _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) } func SendZlp() { sendUSBPacket(0, []byte{}, 0) } func sendViaEPIn(ep uint32, data []byte, count int) { // Prepare buffer control register value val := uint32(count) | usbBuf0CtrlAvail // DATA0 or DATA1 epXdata0[ep&0x7F] = !epXdata0[ep&0x7F] if !epXdata0[ep&0x7F] { val |= usbBuf0CtrlData1Pid } // Mark as full val |= usbBuf0CtrlFull copy(_usbDPSRAM.EPxBuffer[ep&0x7F].Buffer0[:], data[:count]) _usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) } // Set ENDPOINT_HALT/stall status on a USB IN endpoint. func (dev *USBDevice) SetStallEPIn(ep uint32) { ep = ep & 0x7F // Prepare buffer control register value if ep == 0 { armEPZeroStall() } val := uint32(usbBuf0CtrlFull) _usbDPSRAM.EPxBufferControl[ep].In.Set(val) val |= uint32(usbBuf0CtrlStall) _usbDPSRAM.EPxBufferControl[ep].In.Set(val) } // Set ENDPOINT_HALT/stall status on a USB OUT endpoint. func (dev *USBDevice) SetStallEPOut(ep uint32) { ep = ep & 0x7F if ep == 0 { panic("SetStallEPOut: EP0 OUT not valid") } val := uint32(usbBuf0CtrlStall) _usbDPSRAM.EPxBufferControl[ep].Out.Set(val) } // Clear the ENDPOINT_HALT/stall on a USB IN endpoint. func (dev *USBDevice) ClearStallEPIn(ep uint32) { ep = ep & 0x7F val := uint32(usbBuf0CtrlStall) _usbDPSRAM.EPxBufferControl[ep].In.ClearBits(val) if epXPIDReset[ep] { // Reset the PID to DATA0 setEPDataPID(ep, false) } } // Clear the ENDPOINT_HALT/stall on a USB OUT endpoint. func (dev *USBDevice) ClearStallEPOut(ep uint32) { ep = ep & 0x7F val := uint32(usbBuf0CtrlStall) _usbDPSRAM.EPxBufferControl[ep].Out.ClearBits(val) if epXPIDReset[ep] { // Reset the PID to DATA0 setEPDataPID(ep, false) } } type usbDPSRAM struct { // Note that EPxControl[0] is not EP0Control but 8-byte setup data. EPxControl [16]usbEndpointControlRegister EPxBufferControl [16]usbBufferControlRegister EPxBuffer [16]usbBuffer } type usbEndpointControlRegister struct { In volatile.Register32 Out volatile.Register32 } type usbBufferControlRegister struct { In volatile.Register32 Out volatile.Register32 } type usbBuffer struct { Buffer0 [usbBufferLen]byte Buffer1 [usbBufferLen]byte } var ( _usbDPSRAM = (*usbDPSRAM)(unsafe.Pointer(uintptr(0x50100000))) epXdata0 [16]bool epXPIDReset [16]bool setupBytes [8]byte ) func (d *usbDPSRAM) setupBytes() []byte { data := d.EPxControl[usb.CONTROL_ENDPOINT].In.Get() setupBytes[0] = byte(data) setupBytes[1] = byte(data >> 8) setupBytes[2] = byte(data >> 16) setupBytes[3] = byte(data >> 24) data = d.EPxControl[usb.CONTROL_ENDPOINT].Out.Get() setupBytes[4] = byte(data) setupBytes[5] = byte(data >> 8) setupBytes[6] = byte(data >> 16) setupBytes[7] = byte(data >> 24) return setupBytes[:] } func (d *usbDPSRAM) clear() { for i := 0; i < len(d.EPxControl); i++ { d.EPxControl[i].In.Set(0) d.EPxControl[i].Out.Set(0) d.EPxBufferControl[i].In.Set(0) d.EPxBufferControl[i].Out.Set(0) } } const ( // DPRAM : Endpoint control register usbEpControlEnable = 0x80000000 usbEpControlDoubleBuffered = 0x40000000 usbEpControlInterruptPerBuff = 0x20000000 usbEpControlInterruptPerDoubleBuff = 0x10000000 usbEpControlEndpointType = 0x0c000000 usbEpControlInterruptOnStall = 0x00020000 usbEpControlInterruptOnNak = 0x00010000 usbEpControlBufferAddress = 0x0000ffff usbEpControlEndpointTypeControl = 0x00000000 usbEpControlEndpointTypeISO = 0x04000000 usbEpControlEndpointTypeBulk = 0x08000000 usbEpControlEndpointTypeInterrupt = 0x0c000000 // Endpoint buffer control bits usbBuf1CtrlFull = 0x80000000 usbBuf1CtrlLast = 0x40000000 usbBuf1CtrlData0Pid = 0x20000000 usbBuf1CtrlData1Pid = 0x00000000 usbBuf1CtrlSel = 0x10000000 usbBuf1CtrlStall = 0x08000000 usbBuf1CtrlAvail = 0x04000000 usbBuf1CtrlLenMask = 0x03FF0000 usbBuf0CtrlFull = 0x00008000 usbBuf0CtrlLast = 0x00004000 usbBuf0CtrlData0Pid = 0x00000000 usbBuf0CtrlData1Pid = 0x00002000 usbBuf0CtrlSel = 0x00001000 usbBuf0CtrlStall = 0x00000800 usbBuf0CtrlAvail = 0x00000400 usbBuf0CtrlLenMask = 0x000003FF usbBufferLen = 64 ) ================================================ FILE: src/machine/machine_rp2_watchdog.go ================================================ //go:build rp2040 || rp2350 package machine import ( "device/rp" ) // Watchdog provides access to the hardware watchdog available // in the RP2040. var Watchdog = &watchdogImpl{} const ( // WatchdogMaxTimeout in milliseconds (approx 8.3s). // // Nominal 1us per watchdog tick, 24-bit counter, // but due to errata two ticks consumed per 1us. // See: Errata RP2040-E1 WatchdogMaxTimeout = (rp.WATCHDOG_LOAD_LOAD_Msk / 1000) / 2 ) type watchdogImpl struct { // The value to reset the counter to on each Update loadValue uint32 } // Configure the watchdog. // // This method should not be called after the watchdog is started and on // some platforms attempting to reconfigure after starting the watchdog // is explicitly forbidden / will not work. func (wd *watchdogImpl) Configure(config WatchdogConfig) error { // x2 due to errata RP2040-E1 wd.loadValue = config.TimeoutMillis * 1000 * 2 if wd.loadValue > rp.WATCHDOG_LOAD_LOAD_Msk { wd.loadValue = rp.WATCHDOG_LOAD_LOAD_Msk } rp.WATCHDOG.CTRL.ClearBits(rp.WATCHDOG_CTRL_ENABLE) // Reset everything apart from ROSC and XOSC rp.PSM.WDSEL.Set(0x0001ffff &^ (rp.PSM_WDSEL_ROSC | rp.PSM_WDSEL_XOSC)) // Pause watchdog during debug rp.WATCHDOG.CTRL.SetBits(rp.WATCHDOG_CTRL_PAUSE_DBG0 | rp.WATCHDOG_CTRL_PAUSE_DBG1 | rp.WATCHDOG_CTRL_PAUSE_JTAG) // Load initial counter rp.WATCHDOG.LOAD.Set(wd.loadValue) return nil } // Starts the watchdog. func (wd *watchdogImpl) Start() error { rp.WATCHDOG.CTRL.SetBits(rp.WATCHDOG_CTRL_ENABLE) return nil } // Update the watchdog, indicating that the app is healthy. func (wd *watchdogImpl) Update() { rp.WATCHDOG.LOAD.Set(wd.loadValue) } ================================================ FILE: src/machine/machine_rp2_xosc.go ================================================ //go:build rp2040 || rp2350 package machine import ( "device/rp" "runtime/volatile" "unsafe" ) // On some boards, the XOSC can take longer than usual to stabilize. On such // boards, this is needed to avoid a hard fault on boot/reset. Refer to // PICO_XOSC_STARTUP_DELAY_MULTIPLIER in the Pico SDK for additional details. const XOSC_STARTUP_DELAY_MULTIPLIER = 64 type xoscType struct { ctrl volatile.Register32 status volatile.Register32 dormant volatile.Register32 startup volatile.Register32 reserved [3 - 3*rp2350ExtraReg]volatile.Register32 count volatile.Register32 } var xosc = (*xoscType)(unsafe.Pointer(rp.XOSC)) // init initializes the crystal oscillator system. // // This function will block until the crystal oscillator has stabilised. func (osc *xoscType) init() { // Assumes 1-15 MHz input if xoscFreq > 15 { panic("xosc frequency cannot be greater than 15MHz") } osc.ctrl.Set(rp.XOSC_CTRL_FREQ_RANGE_1_15MHZ) // Set xosc startup delay delay := (((xoscFreq * MHz) / 1000) + 128) / 256 * XOSC_STARTUP_DELAY_MULTIPLIER osc.startup.Set(uint32(delay)) // Set the enable bit now that we have set freq range and startup delay osc.ctrl.SetBits(rp.XOSC_CTRL_ENABLE_ENABLE << rp.XOSC_CTRL_ENABLE_Pos) // Wait for xosc to be stable for !osc.status.HasBits(rp.XOSC_STATUS_STABLE) { } } ================================================ FILE: src/machine/machine_stm32.go ================================================ //go:build stm32 package machine import ( "device/stm32" "runtime/volatile" "unsafe" ) const deviceName = stm32.Device // Peripheral abstraction layer for the stm32. const ( portA Pin = iota * 16 portB portC portD portE portF portG portH portI portJ portK ) // Peripheral operations sequence: // 1. Enable the clock to the alternate function. // 2. Enable clock to corresponding GPIO // 3. Attach the alternate function. // 4. Configure the input-output port and pins (of the corresponding GPIOx) to match the AF . // 5. If desired enable the nested vector interrupt control to generate interrupts. // 6. Program the AF/peripheral for the required configuration (eg baud rate for a USART) . // Given that the stm32 family has the AF and GPIO on different registers based on the chip, // use the main function here for configuring, and use hooks in the more specific chip // definition files // Also, the stm32f1xx series handles things differently from the stm32f0/2/3/4 // ---------- General pin operations ---------- type PinChange uint8 const ( PinRising PinChange = 1 << iota PinFalling PinToggle = PinRising | PinFalling ) // Set the pin to high or low. // Warning: only use this on an output pin! func (p Pin) Set(high bool) { port := p.getPort() pin := uint8(p) % 16 if high { port.BSRR.Set(1 << pin) } else { port.BSRR.Set(1 << (pin + 16)) } } // Get returns the current value of a GPIO pin when the pin is configured as an // input or as an output. func (p Pin) Get() bool { port := p.getPort() pin := uint8(p) % 16 val := port.IDR.Get() & (1 << pin) return (val > 0) } // PortMaskSet returns the register and mask to enable a given GPIO pin. This // can be used to implement bit-banged drivers. func (p Pin) PortMaskSet() (*uint32, uint32) { port := p.getPort() pin := uint8(p) % 16 return &port.BSRR.Reg, 1 << pin } // PortMaskClear returns the register and mask to disable a given port. This can // be used to implement bit-banged drivers. func (p Pin) PortMaskClear() (*uint32, uint32) { port := p.getPort() pin := uint8(p) % 16 return &port.BSRR.Reg, 1 << (pin + 16) } var deviceID [12]byte // DeviceID returns an identifier that is unique within // a particular chipset. // // The identity is one burnt into the MCU itself. // // The length of the device ID for STM32 is 12 bytes (96 bits). func DeviceID() []byte { for i := 0; i < len(deviceID); i++ { word := (*volatile.Register32)(unsafe.Pointer(deviceIDAddr[i/4])).Get() deviceID[i] = byte(word >> ((i % 4) * 8)) } return deviceID[:] } ================================================ FILE: src/machine/machine_stm32_adc_f1.go ================================================ //go:build stm32f103 package machine import ( "device/stm32" "unsafe" ) const ( Cycles_1_5 = 0x0 Cycles_7_5 = 0x1 Cycles_13_5 = 0x2 Cycles_28_5 = 0x3 Cycles_41_5 = 0x4 Cycles_55_5 = 0x5 Cycles_71_5 = 0x6 Cycles_239_5 = 0x7 ) // InitADC initializes the registers needed for ADC1. func InitADC() { // Enable ADC clock enableAltFuncClock(unsafe.Pointer(stm32.ADC1)) // enable stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_ADON | stm32.ADC_CR2_ALIGN) return } // Configure configures an ADC pin to be able to read analog data. func (a ADC) Configure(ADCConfig) { a.Pin.Configure(PinConfig{Mode: PinInputModeAnalog}) // set sample time ch := a.getChannel() if ch > 9 { stm32.ADC1.SMPR1.SetBits(Cycles_28_5 << (ch - 10) * stm32.ADC_SMPR1_SMP11_Pos) } else { stm32.ADC1.SMPR2.SetBits(Cycles_28_5 << (ch * stm32.ADC_SMPR2_SMP1_Pos)) } return } // Get returns the current value of a ADC pin in the range 0..0xffff. // TODO: DMA based implementation. func (a ADC) Get() uint16 { // set rank ch := uint32(a.getChannel()) stm32.ADC1.SetSQR3_SQ1(ch) // start conversion stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_ADON) // wait for conversion to complete for !stm32.ADC1.SR.HasBits(stm32.ADC_SR_EOC) { } // read result as 16 bit value return uint16(stm32.ADC1.DR.Get()) } func (a ADC) getChannel() uint8 { switch a.Pin { case PA0: return 0 case PA1: return 1 case PA2: return 2 case PA3: return 3 case PA4: return 4 case PA5: return 5 case PA6: return 6 case PA7: return 7 case PB0: return 8 case PB1: return 9 } return 0 } ================================================ FILE: src/machine/machine_stm32_adc_f4.go ================================================ //go:build stm32f4 package machine import ( "device/stm32" "unsafe" ) // InitADC initializes the registers needed for ADC1. func InitADC() { // Enable ADC clock enableAltFuncClock(unsafe.Pointer(stm32.ADC1)) // stop scan, and clear scan resolution stm32.ADC1.CR1.ClearBits(stm32.ADC_CR1_SCAN | stm32.ADC_CR1_RES_Msk) // set conversion mode and resolution stm32.ADC1.CR1.SetBits(stm32.ADC_CR1_RES_TwelveBit) // clear CONT, ALIGN, EXTEN and EXTSEL bits from CR2 stm32.ADC1.CR2.ClearBits(stm32.ADC_CR2_CONT | stm32.ADC_CR2_ALIGN | stm32.ADC_CR2_EXTEN_Msk | stm32.ADC_CR2_EXTSEL_Msk) // set CONT, ALIGN, EXTEN and EXTSEL bits from CR2 stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_CONT_Single | stm32.ADC_CR2_ALIGN_Right) stm32.ADC1.SQR1.ClearBits(stm32.ADC_SQR1_L_Msk) stm32.ADC1.SQR1.SetBits(2 << stm32.ADC_SQR1_L_Pos) // 2 means 3 conversions // enable stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_ADON) return } // Configure configures an ADC pin to be able to read analog data. func (a ADC) Configure(ADCConfig) { a.Pin.ConfigureAltFunc(PinConfig{Mode: PinInputAnalog}, 0) // set sample time ch := a.getChannel() if ch > 9 { stm32.ADC1.SMPR1.SetBits(stm32.ADC_SMPR1_SMP11_Cycles84 << (ch - 10) * stm32.ADC_SMPR1_SMP11_Pos) } else { stm32.ADC1.SMPR2.SetBits(stm32.ADC_SMPR2_SMP1_Cycles84 << (ch * stm32.ADC_SMPR2_SMP1_Pos)) } return } // Get returns the current value of a ADC pin in the range 0..0xffff. // TODO: DMA based implementation. func (a ADC) Get() uint16 { // set rank ch := uint32(a.getChannel()) stm32.ADC1.SQR3.SetBits(ch) // start conversion stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_SWSTART) // wait for conversion to complete for !stm32.ADC1.SR.HasBits(stm32.ADC_SR_EOC) { } // read 12-bit result as 16 bit value result := uint16(stm32.ADC1.DR.Get()) << 4 // clear flag stm32.ADC1.SR.ClearBits(stm32.ADC_SR_EOC) // clear rank stm32.ADC1.SQR3.ClearBits(ch) return result } func (a ADC) getChannel() uint8 { switch a.Pin { case PA0: return 0 case PA1: return 1 case PA2: return 2 case PA3: return 3 case PA4: return 4 case PA5: return 5 case PA6: return 6 case PA7: return 7 case PB0: return 8 case PB1: return 9 case PC0: return 10 case PC1: return 11 case PC2: return 12 case PC3: return 13 case PC4: return 14 case PC5: return 15 } return 0 } ================================================ FILE: src/machine/machine_stm32_exti_afio.go ================================================ //go:build stm32f1 package machine import ( "device/stm32" "runtime/volatile" ) func getEXTIConfigRegister(pin uint8) *volatile.Register32 { switch (pin & 0xf) / 4 { case 0: return &stm32.AFIO.EXTICR1 case 1: return &stm32.AFIO.EXTICR2 case 2: return &stm32.AFIO.EXTICR3 case 3: return &stm32.AFIO.EXTICR4 } return nil } func enableEXTIConfigRegisters() { // Enable AFIO stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_AFIOEN) } ================================================ FILE: src/machine/machine_stm32_exti_exti.go ================================================ //go:build stm32l5 package machine import ( "device/stm32" "runtime/volatile" ) func getEXTIConfigRegister(pin uint8) *volatile.Register32 { switch (pin & 0xf) / 4 { case 0: return &stm32.EXTI.EXTICR1 case 1: return &stm32.EXTI.EXTICR2 case 2: return &stm32.EXTI.EXTICR3 case 3: return &stm32.EXTI.EXTICR4 } return nil } func enableEXTIConfigRegisters() { // No-op } ================================================ FILE: src/machine/machine_stm32_exti_syscfg.go ================================================ //go:build stm32 && !stm32f1 && !stm32l5 && !stm32wlx package machine import ( "device/stm32" "runtime/volatile" ) func getEXTIConfigRegister(pin uint8) *volatile.Register32 { switch (pin & 0xf) / 4 { case 0: return &stm32.SYSCFG.EXTICR1 case 1: return &stm32.SYSCFG.EXTICR2 case 2: return &stm32.SYSCFG.EXTICR3 case 3: return &stm32.SYSCFG.EXTICR4 } return nil } func enableEXTIConfigRegisters() { // Enable SYSCFG stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SYSCFGEN) } ================================================ FILE: src/machine/machine_stm32_exti_syscfg_noenable.go ================================================ //go:build stm32wlx package machine import ( "device/stm32" "runtime/volatile" ) func getEXTIConfigRegister(pin uint8) *volatile.Register32 { switch (pin & 0xf) / 4 { case 0: return &stm32.SYSCFG.EXTICR1 case 1: return &stm32.SYSCFG.EXTICR2 case 2: return &stm32.SYSCFG.EXTICR3 case 3: return &stm32.SYSCFG.EXTICR4 } return nil } func enableEXTIConfigRegisters() { // No registers to enable } ================================================ FILE: src/machine/machine_stm32_flash.go ================================================ //go:build stm32f4 || stm32l4 || stm32wlx package machine import ( "device/stm32" "unsafe" ) // compile-time check for ensuring we fulfill BlockDevice interface var _ BlockDevice = flashBlockDevice{} var Flash flashBlockDevice type flashBlockDevice struct { } // ReadAt reads the given number of bytes from the block device. func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) { if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() { return 0, errFlashCannotReadPastEOF } data := unsafe.Slice((*byte)(unsafe.Pointer(FlashDataStart()+uintptr(off))), len(p)) copy(p, data) return len(p), nil } // WriteAt writes the given number of bytes to the block device. // Only double-word (64 bits) length data can be programmed. See rm0461 page 78. // If the length of p is not long enough it will be padded with 0xFF bytes. // This method assumes that the destination is already erased. func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() { return 0, errFlashCannotWritePastEOF } unlockFlash() defer lockFlash() p = flashPad(p, int(f.WriteBlockSize())) return writeFlashData(FlashDataStart()+uintptr(off), p) } // Size returns the number of bytes in this block device. func (f flashBlockDevice) Size() int64 { return int64(FlashDataEnd() - FlashDataStart()) } // WriteBlockSize returns the block size in which data can be written to // memory. It can be used by a client to optimize writes, non-aligned writes // should always work correctly. func (f flashBlockDevice) WriteBlockSize() int64 { return writeBlockSize } func eraseBlockSize() int64 { return eraseBlockSizeValue } // EraseBlockSize returns the smallest erasable area on this particular chip // in bytes. This is used for the block size in EraseBlocks. // It must be a power of two, and may be as small as 1. A typical size is 4096. // TODO: correctly handle processors that have differently sized blocks // in different areas of memory like the STM32F40x and STM32F1x. func (f flashBlockDevice) EraseBlockSize() int64 { return eraseBlockSize() } // EraseBlocks erases the given number of blocks. An implementation may // transparently coalesce ranges of blocks into larger bundles if the chip // supports this. The start and len parameters are in block numbers, use // EraseBlockSize to map addresses to blocks. // Note that block 0 should map to the address of FlashDataStart(). func (f flashBlockDevice) EraseBlocks(start, len int64) error { var address uintptr = uintptr(start*f.EraseBlockSize()) + FlashDataStart() blk := int64(address-uintptr(memoryStart)) / f.EraseBlockSize() unlockFlash() defer lockFlash() for i := blk; i < blk+len; i++ { if err := eraseBlock(uint32(i)); err != nil { return err } } return nil } const memoryStart = 0x08000000 func unlockFlash() { // keys as described rm0461 page 76 var fkey1 uint32 = 0x45670123 var fkey2 uint32 = 0xCDEF89AB // Wait for the flash memory not to be busy for stm32.FLASH.GetSR_BSY() != 0 { } // Check if the controller is unlocked already if stm32.FLASH.GetCR_LOCK() != 0 { // Write the first key stm32.FLASH.SetKEYR(fkey1) // Write the second key stm32.FLASH.SetKEYR(fkey2) } } func lockFlash() { stm32.FLASH.SetCR_LOCK(1) } ================================================ FILE: src/machine/machine_stm32_gpio_reva.go ================================================ //go:build stm32 && !stm32l4 && !stm32l5 && !stm32wlx package machine import ( "device/stm32" ) // This variant of the GPIO input interrupt logic is for // chips with a smaller number of interrupt channels // (that fits in a single register). // // STM32 allows one interrupt source per pin number, with // the same pin number in different ports sharing a single // interrupt source (so PA0, PB0, PC0 all share). Only a // single physical pin can be connected to each interrupt // line. // // To call interrupt callbacks, we record here for each // pin number the callback and the actual associated pin. // // Callbacks for pin interrupt events var pinCallbacks [16]func(Pin) // The pin currently associated with interrupt callback // for a given slot. var interruptPins [16]Pin // SetInterrupt sets an interrupt to be executed when a particular pin changes // state. The pin should already be configured as an input, including a pull up // or down if no external pull is provided. // // This call will replace a previously set callback on this pin. You can pass a // nil func to unset the pin change interrupt. If you do so, the change // parameter is ignored and can be set to any value (such as 0). func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { port := uint32(uint8(p) / 16) pin := uint8(p) % 16 enableEXTIConfigRegisters() if callback == nil { stm32.EXTI.IMR.ClearBits(1 << pin) pinCallbacks[pin] = nil return nil } if pinCallbacks[pin] != nil { // The pin was already configured. // To properly re-configure a pin, unset it first and set a new // configuration. return ErrNoPinChangeChannel } // Set the callback now (before the interrupt is enabled) to avoid // possible race condition pinCallbacks[pin] = callback interruptPins[pin] = p crReg := getEXTIConfigRegister(pin) shift := (pin & 0x3) * 4 crReg.ReplaceBits(port, 0xf, shift) if (change & PinRising) != 0 { stm32.EXTI.RTSR.SetBits(1 << pin) } if (change & PinFalling) != 0 { stm32.EXTI.FTSR.SetBits(1 << pin) } stm32.EXTI.IMR.SetBits(1 << pin) intr := p.registerInterrupt() intr.SetPriority(0) intr.Enable() return nil } func handlePinInterrupt(pin uint8) { if stm32.EXTI.PR.HasBits(1 << pin) { // Writing 1 to the pending register clears the // pending flag for that bit stm32.EXTI.PR.Set(1 << pin) callback := pinCallbacks[pin] if callback != nil { callback(interruptPins[pin]) } } } ================================================ FILE: src/machine/machine_stm32_gpio_revb.go ================================================ //go:build stm32l4 || stm32l5 package machine import ( "device/stm32" ) // This variant of the GPIO input interrupt logic is for // chips with a larger number of interrupt channels (more // than fits in a single register). // // STM32 allows one interrupt source per pin number, with // the same pin number in different ports sharing a single // interrupt source (so PA0, PB0, PC0 all share). Only a // single physical pin can be connected to each interrupt // line. // // To call interrupt callbacks, we record here for each // pin number the callback and the actual associated pin. // // Callbacks for pin interrupt events var pinCallbacks [16]func(Pin) // The pin currently associated with interrupt callback // for a given slot. var interruptPins [16]Pin // SetInterrupt sets an interrupt to be executed when a particular pin changes // state. The pin should already be configured as an input, including a pull up // or down if no external pull is provided. // // This call will replace a previously set callback on this pin. You can pass a // nil func to unset the pin change interrupt. If you do so, the change // parameter is ignored and can be set to any value (such as 0). func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { port := uint32(uint8(p) / 16) pin := uint8(p) % 16 enableEXTIConfigRegisters() if callback == nil { stm32.EXTI.IMR1.ClearBits(1 << pin) pinCallbacks[pin] = nil return nil } if pinCallbacks[pin] != nil { // The pin was already configured. // To properly re-configure a pin, unset it first and set a new // configuration. return ErrNoPinChangeChannel } // Set the callback now (before the interrupt is enabled) to avoid // possible race condition pinCallbacks[pin] = callback interruptPins[pin] = p crReg := getEXTIConfigRegister(pin) shift := (pin & 0x3) * 4 crReg.ReplaceBits(port, 0xf, shift) if (change & PinRising) != 0 { stm32.EXTI.RTSR1.SetBits(1 << pin) } if (change & PinFalling) != 0 { stm32.EXTI.FTSR1.SetBits(1 << pin) } stm32.EXTI.IMR1.SetBits(1 << pin) intr := p.registerInterrupt() intr.SetPriority(0) intr.Enable() return nil } ================================================ FILE: src/machine/machine_stm32_gpio_revb_mp.go ================================================ //go:build stm32wlx package machine import ( "device/stm32" ) // // This variant of the GPIO input interrupt logic is for // multi-core chips with a larger number of interrupt // channels (more than fits in a single register). // // This logic is currently used by the single-core stm32wle5 // due to a patch in stm32-rs project that has renamed the // registers to match the dual-core names. This renaming is // being discussed and may change in future. // // // STM32 allows one interrupt source per pin number, with // the same pin number in different ports sharing a single // interrupt source (so PA0, PB0, PC0 all share). Only a // single physical pin can be connected to each interrupt // line. // // To call interrupt callbacks, we record here for each // pin number the callback and the actual associated pin. // // Callbacks for pin interrupt events var pinCallbacks [16]func(Pin) // The pin currently associated with interrupt callback // for a given slot. var interruptPins [16]Pin // SetInterrupt sets an interrupt to be executed when a particular pin changes // state. The pin should already be configured as an input, including a pull up // or down if no external pull is provided. // // This call will replace a previously set callback on this pin. You can pass a // nil func to unset the pin change interrupt. If you do so, the change // parameter is ignored and can be set to any value (such as 0). func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { port := uint32(uint8(p) / 16) pin := uint8(p) % 16 enableEXTIConfigRegisters() if callback == nil { stm32.EXTI.C1IMR1.ClearBits(1 << pin) pinCallbacks[pin] = nil return nil } if pinCallbacks[pin] != nil { // The pin was already configured. // To properly re-configure a pin, unset it first and set a new // configuration. return ErrNoPinChangeChannel } // Set the callback now (before the interrupt is enabled) to avoid // possible race condition pinCallbacks[pin] = callback interruptPins[pin] = p crReg := getEXTIConfigRegister(pin) shift := (pin & 0x3) * 4 crReg.ReplaceBits(port, 0xf, shift) if (change & PinRising) != 0 { stm32.EXTI.RTSR1.SetBits(1 << pin) } if (change & PinFalling) != 0 { stm32.EXTI.FTSR1.SetBits(1 << pin) } stm32.EXTI.C1IMR1.SetBits(1 << pin) intr := p.registerInterrupt() intr.SetPriority(0) intr.Enable() return nil } ================================================ FILE: src/machine/machine_stm32_i2c_reva.go ================================================ //go:build stm32f4 || stm32f1 package machine // I2C implementation for 'older' STM32 MCUs, including the F1 and F4 series // of MCUs. import ( "device/stm32" "unsafe" ) const ( flagOVR = 0x00010800 flagAF = 0x00010400 flagARLO = 0x00010200 flagBERR = 0x00010100 flagTXE = 0x00010080 flagRXNE = 0x00010040 flagSTOPF = 0x00010010 flagADD10 = 0x00010008 flagBTF = 0x00010004 flagADDR = 0x00010002 flagSB = 0x00010001 flagDUALF = 0x00100080 flagGENCALL = 0x00100010 flagTRA = 0x00100004 flagBUSY = 0x00100002 flagMSL = 0x00100001 ) func (i2c *I2C) hasFlag(flag uint32) bool { const mask = 0x0000FFFF if uint8(flag>>16) == 1 { return i2c.Bus.SR1.HasBits(flag & mask) } else { return i2c.Bus.SR2.HasBits(flag & mask) } } func (i2c *I2C) clearFlag(flag uint32) { const mask = 0x0000FFFF i2c.Bus.SR1.Set(^(flag & mask)) } // clearFlagADDR reads both status registers to clear any pending ADDR flags. func (i2c *I2C) clearFlagADDR() { i2c.Bus.SR1.Get() i2c.Bus.SR2.Get() } func (i2c *I2C) waitForFlag(flag uint32, set bool) bool { const tryMax = 10000 hasFlag := false for i := 0; !hasFlag && i < tryMax; i++ { hasFlag = i2c.hasFlag(flag) == set } return hasFlag } func (i2c *I2C) waitForFlagOrError(flag uint32, set bool) bool { const tryMax = 10000 hasFlag := false for i := 0; !hasFlag && i < tryMax; i++ { if hasFlag = i2c.hasFlag(flag) == set; !hasFlag { // check for ACK failure if i2c.hasFlag(flagAF) { // generate stop condition i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP) // clear pending flags i2c.clearFlag(flagAF) return false } else if i2c.hasFlag(flagSTOPF) { // clear stop flag i2c.clearFlag(flagSTOPF) return false } } } return hasFlag } type transferOption uint32 const ( frameFirst = 0x00000001 frameFirstAndNext = 0x00000002 frameNext = 0x00000004 frameFirstAndLast = 0x00000008 frameLastNoStop = 0x00000010 frameLast = 0x00000020 frameNoOption = 0xFFFF0000 ) // I2C fast mode (Fm) duty cycle const ( DutyCycle2 = 0 DutyCycle16x9 = 1 ) // I2CConfig is used to store config info for I2C. type I2CConfig struct { Frequency uint32 SCL Pin SDA Pin DutyCycle uint8 } // Configure is intended to setup the STM32 I2C interface. func (i2c *I2C) Configure(config I2CConfig) error { // The following is the required sequence in controller mode. // 1. Program the peripheral input clock in I2C_CR2 Register in order to // generate correct timings // 2. Configure the clock control registers // 3. Configure the rise time register // 4. Program the I2C_CR1 register to enable the peripheral // 5. Set the START bit in the I2C_CR1 register to generate a Start condition // disable I2C interface before any configuration changes i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_PE) // reset I2C bus i2c.Bus.CR1.SetBits(stm32.I2C_CR1_SWRST) i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_SWRST) // enable clock for I2C enableAltFuncClock(unsafe.Pointer(i2c.Bus)) // init pins if config.SCL == 0 && config.SDA == 0 { config.SCL = I2C0_SCL_PIN config.SDA = I2C0_SDA_PIN } i2c.configurePins(config) // default to 100 kHz (Sm, standard mode) if no frequency is set if config.Frequency == 0 { config.Frequency = 100 * KHz } // configure I2C input clock i2c.Bus.CR2.SetBits(i2c.getFreqRange(config)) // configure rise time i2c.Bus.TRISE.Set(i2c.getRiseTime(config)) // configure clock control i2c.Bus.CCR.Set(i2c.getSpeed(config)) // disable GeneralCall and NoStretch modes i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_ENGC | stm32.I2C_CR1_NOSTRETCH) // enable I2C interface i2c.Bus.CR1.SetBits(stm32.I2C_CR1_PE) return nil } // SetBaudRate sets the communication speed for I2C. func (i2c *I2C) SetBaudRate(br uint32) error { // TODO: implement return errI2CNotImplemented } func (i2c *I2C) Tx(addr uint16, w, r []byte) error { if err := i2c.controllerTransmit(addr, w); nil != err { return err } if len(r) > 0 { if err := i2c.controllerReceive(addr, r); nil != err { return err } } return nil } func (i2c *I2C) controllerTransmit(addr uint16, w []byte) error { if !i2c.waitForFlag(flagBUSY, false) { return errI2CBusReadyTimeout } // disable POS i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_POS) pos := 0 rem := len(w) // send peripheral address if err := i2c.controllerRequestWrite(addr, frameNoOption); nil != err { return err } // clear ADDR flag i2c.clearFlagADDR() for rem > 0 { // wait for TXE flag set if !i2c.waitForFlagOrError(flagTXE, true) { return errI2CAckExpected } // write data to DR i2c.Bus.DR.Set(uint32(w[pos])) // update counters pos++ rem-- if i2c.hasFlag(flagBTF) && rem != 0 { // write data to DR i2c.Bus.DR.Set(uint32(w[pos])) // update counters pos++ rem-- } // wait for transfer finished flag BTF set if !i2c.waitForFlagOrError(flagBTF, true) { return errI2CWriteTimeout } } // generate stop condition i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP) return nil } func (i2c *I2C) controllerRequestWrite(addr uint16, option transferOption) error { if frameFirstAndLast == option || frameFirst == option || frameNoOption == option { // generate start condition i2c.Bus.CR1.SetBits(stm32.I2C_CR1_START) } else if false /* (hi2c->PreviousState == I2C_STATE_MASTER_BUSY_RX) */ { // generate restart condition i2c.Bus.CR1.SetBits(stm32.I2C_CR1_START) } // ensure start bit is set if !i2c.waitForFlag(flagSB, true) { return errI2CSignalStartTimeout } // send peripheral address i2c.Bus.DR.Set(uint32(addr) << 1) // wait for address ACK from peripheral if !i2c.waitForFlagOrError(flagADDR, true) { return errI2CSignalStartTimeout } return nil } func (i2c *I2C) controllerReceive(addr uint16, r []byte) error { if !i2c.waitForFlag(flagBUSY, false) { return errI2CBusReadyTimeout } // disable POS i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_POS) pos := 0 rem := len(r) // send peripheral address if err := i2c.controllerRequestRead(addr, frameNoOption); nil != err { return err } switch rem { case 0: // clear ADDR flag i2c.clearFlagADDR() // generate stop condition i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP) case 1: // disable ACK i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_ACK) // clear ADDR flag i2c.clearFlagADDR() // generate stop condition i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP) case 2: // disable ACK i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_ACK) // enable POS i2c.Bus.CR1.SetBits(stm32.I2C_CR1_POS) // clear ADDR flag i2c.clearFlagADDR() default: // enable ACK i2c.Bus.CR1.SetBits(stm32.I2C_CR1_ACK) // clear ADDR flag i2c.clearFlagADDR() } for rem > 0 { switch rem { case 1: // wait until RXNE flag is set if !i2c.waitForFlagOrError(flagRXNE, true) { return errI2CReadTimeout } // read data from DR r[pos] = byte(i2c.Bus.DR.Get()) // update counters pos++ rem-- case 2: // wait until transfer finished flag BTF is set if !i2c.waitForFlag(flagBTF, true) { return errI2CReadTimeout } // generate stop condition i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP) // read data from DR r[pos] = byte(i2c.Bus.DR.Get()) // update counters pos++ rem-- // read data from DR r[pos] = byte(i2c.Bus.DR.Get()) // update counters pos++ rem-- case 3: // wait until transfer finished flag BTF is set if !i2c.waitForFlag(flagBTF, true) { return errI2CReadTimeout } // disable ACK i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_ACK) // read data from DR r[pos] = byte(i2c.Bus.DR.Get()) // update counters pos++ rem-- // wait until transfer finished flag BTF is set if !i2c.waitForFlag(flagBTF, true) { return errI2CReadTimeout } // generate stop condition i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP) // read data from DR r[pos] = byte(i2c.Bus.DR.Get()) // update counters pos++ rem-- // read data from DR r[pos] = byte(i2c.Bus.DR.Get()) // update counters pos++ rem-- default: // wait until RXNE flag is set if !i2c.waitForFlagOrError(flagRXNE, true) { return errI2CReadTimeout } // read data from DR r[pos] = byte(i2c.Bus.DR.Get()) // update counters pos++ rem-- if i2c.hasFlag(flagBTF) { // read data from DR r[pos] = byte(i2c.Bus.DR.Get()) // update counters pos++ rem-- } } } return nil } func (i2c *I2C) controllerRequestRead(addr uint16, option transferOption) error { // enable ACK i2c.Bus.CR1.SetBits(stm32.I2C_CR1_ACK) if frameFirstAndLast == option || frameFirst == option || frameNoOption == option { // generate start condition i2c.Bus.CR1.SetBits(stm32.I2C_CR1_START) } else if false /* (hi2c->PreviousState == I2C_STATE_MASTER_BUSY_TX) */ { // generate restart condition i2c.Bus.CR1.SetBits(stm32.I2C_CR1_START) } // ensure start bit is set if !i2c.waitForFlag(flagSB, true) { return errI2CSignalStartTimeout } // send peripheral address i2c.Bus.DR.Set(uint32(addr)<<1 | 1) // wait for address ACK from peripheral if !i2c.waitForFlagOrError(flagADDR, true) { return errI2CSignalStartTimeout } return nil } ================================================ FILE: src/machine/machine_stm32_i2c_revb.go ================================================ //go:build stm32l5 || stm32f7 || stm32l4 || stm32l0 || stm32wlx package machine import ( "device/stm32" "unsafe" ) //go:linkname ticks runtime.ticks func ticks() int64 // I2C implementation for 'newer' STM32 MCUs, including the F7, L5 and L4 // series of MCUs. // // Currently, only 100KHz mode is supported const ( flagBUSY = stm32.I2C_ISR_BUSY flagTCR = stm32.I2C_ISR_TCR flagRXNE = stm32.I2C_ISR_RXNE flagSTOPF = stm32.I2C_ISR_STOPF flagAF = stm32.I2C_ISR_NACKF flagTXIS = stm32.I2C_ISR_TXIS flagTXE = stm32.I2C_ISR_TXE ) const ( MAX_NBYTE_SIZE = 255 // 100ms delay = 100e6ns / 16ns // In runtime_stm32_timers.go, tick is fixed at 16ns per tick TIMEOUT_TICKS = 100e6 / 16 I2C_NO_STARTSTOP = 0x0 I2C_GENERATE_START_WRITE = 0x80000000 | stm32.I2C_CR2_START I2C_GENERATE_START_READ = 0x80000000 | stm32.I2C_CR2_START | stm32.I2C_CR2_RD_WRN I2C_GENERATE_STOP = 0x80000000 | stm32.I2C_CR2_STOP ) type I2C struct { Bus *stm32.I2C_Type AltFuncSelector uint8 } // I2CConfig is used to store config info for I2C. type I2CConfig struct { Frequency uint32 SCL Pin SDA Pin } func (i2c *I2C) Configure(config I2CConfig) error { // Frequency range switch config.Frequency { case 0: config.Frequency = 100 * KHz case 10 * KHz, 100 * KHz, 400 * KHz, 500 * KHz: default: return errI2CNotImplemented } // disable I2C interface before any configuration changes i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_PE) // enable clock for I2C enableAltFuncClock(unsafe.Pointer(i2c.Bus)) // init pins if config.SCL == 0 && config.SDA == 0 { config.SCL = I2C0_SCL_PIN config.SDA = I2C0_SDA_PIN } i2c.configurePins(config) i2c.Bus.TIMINGR.Set(i2c.getFreqRange(config.Frequency)) // Disable Own Address1 before set the Own Address1 configuration i2c.Bus.OAR1.ClearBits(stm32.I2C_OAR1_OA1EN) // 7 bit addressing, no self address i2c.Bus.OAR1.Set(stm32.I2C_OAR1_OA1EN) // Enable the AUTOEND by default, and enable NACK (should be disable only during Slave process i2c.Bus.CR2.Set(stm32.I2C_CR2_AUTOEND | stm32.I2C_CR2_NACK) // Disable Own Address2 / Dual Addressing i2c.Bus.OAR2.Set(0) // Disable Generalcall and NoStretch, Enable peripheral i2c.Bus.CR1.Set(stm32.I2C_CR1_PE) return nil } // SetBaudRate sets the communication speed for I2C. func (i2c *I2C) SetBaudRate(br uint32) error { switch br { case 10 * KHz, 100 * KHz, 400 * KHz, 500 * KHz: default: return errI2CNotImplemented } // disable I2C interface before any configuration changes i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_PE) i2c.Bus.TIMINGR.Set(i2c.getFreqRange(br)) // Disable Generalcall and NoStretch, Enable peripheral i2c.Bus.CR1.Set(stm32.I2C_CR1_PE) return nil } func (i2c *I2C) Tx(addr uint16, w, r []byte) error { if len(w) > 0 { if err := i2c.controllerTransmit(addr, w); nil != err { return err } } if len(r) > 0 { if err := i2c.controllerReceive(addr, r); nil != err { return err } } return nil } func (i2c *I2C) configurePins(config I2CConfig) { config.SCL.ConfigureAltFunc(PinConfig{Mode: PinModeI2CSCL}, i2c.AltFuncSelector) config.SDA.ConfigureAltFunc(PinConfig{Mode: PinModeI2CSDA}, i2c.AltFuncSelector) } func (i2c *I2C) controllerTransmit(addr uint16, w []byte) error { start := ticks() if !i2c.waitOnFlagUntilTimeout(flagBUSY, false, start) { return errI2CBusReadyTimeout } pos := 0 xferCount := len(w) xferSize := uint8(xferCount) if xferCount > MAX_NBYTE_SIZE { // Large write, indicate reload xferSize = MAX_NBYTE_SIZE i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_RELOAD, I2C_GENERATE_START_WRITE) } else { // Small write, auto-end i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_AUTOEND, I2C_GENERATE_START_WRITE) } for xferCount > 0 { if !i2c.waitOnTXISFlagUntilTimeout(start) { return errI2CWriteTimeout } i2c.Bus.TXDR.Set(uint32(w[pos])) pos++ xferCount-- xferSize-- // If we've written the last byte of this chunk if xferCount != 0 && xferSize == 0 { // Wait for Transfer Complete Reload to be flagged if !i2c.waitOnFlagUntilTimeout(flagTCR, true, start) { return errI2CWriteTimeout } if xferCount > MAX_NBYTE_SIZE { // Large write remaining, indicate reload xferSize = MAX_NBYTE_SIZE i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_RELOAD, I2C_NO_STARTSTOP) } else { // Small write, auto-end xferSize = uint8(xferCount) i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_AUTOEND, I2C_NO_STARTSTOP) } } } if !i2c.waitOnStopFlagUntilTimeout(start) { return errI2CWriteTimeout } i2c.clearFlag(stm32.I2C_ISR_STOPF) i2c.resetCR2() return nil } func (i2c *I2C) controllerReceive(addr uint16, r []byte) error { start := ticks() if !i2c.waitOnFlagUntilTimeout(flagBUSY, false, start) { return errI2CBusReadyTimeout } pos := 0 xferCount := len(r) xferSize := uint8(xferCount) if xferCount > MAX_NBYTE_SIZE { // Large read, indicate reload xferSize = MAX_NBYTE_SIZE i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_RELOAD, I2C_GENERATE_START_READ) } else { // Small read, auto-end i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_AUTOEND, I2C_GENERATE_START_READ) } for xferCount > 0 { if !i2c.waitOnRXNEFlagUntilTimeout(start) { return errI2CWriteTimeout } r[pos] = uint8(i2c.Bus.RXDR.Get()) pos++ xferCount-- xferSize-- // If we've read the last byte of this chunk if xferCount != 0 && xferSize == 0 { // Wait for Transfer Complete Reload to be flagged if !i2c.waitOnFlagUntilTimeout(flagTCR, true, start) { return errI2CWriteTimeout } if xferCount > MAX_NBYTE_SIZE { // Large read remaining, indicate reload xferSize = MAX_NBYTE_SIZE i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_RELOAD, I2C_NO_STARTSTOP) } else { // Small read, auto-end xferSize = uint8(xferCount) i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_AUTOEND, I2C_NO_STARTSTOP) } } } if !i2c.waitOnStopFlagUntilTimeout(start) { return errI2CWriteTimeout } i2c.clearFlag(stm32.I2C_ISR_STOPF) i2c.resetCR2() return nil } func (i2c *I2C) waitOnFlagUntilTimeout(flag uint32, set bool, startTicks int64) bool { for i2c.hasFlag(flag) != set { if (ticks() - startTicks) > TIMEOUT_TICKS { return false } } return true } func (i2c *I2C) waitOnRXNEFlagUntilTimeout(startTicks int64) bool { for !i2c.hasFlag(flagRXNE) { if i2c.isAcknowledgeFailed(startTicks) { return false } if i2c.hasFlag(flagSTOPF) { i2c.clearFlag(flagSTOPF) i2c.resetCR2() return false } if (ticks() - startTicks) > TIMEOUT_TICKS { return false } } return true } func (i2c *I2C) waitOnTXISFlagUntilTimeout(startTicks int64) bool { for !i2c.hasFlag(flagTXIS) { if i2c.isAcknowledgeFailed(startTicks) { return false } if (ticks() - startTicks) > TIMEOUT_TICKS { return false } } return true } func (i2c *I2C) waitOnStopFlagUntilTimeout(startTicks int64) bool { for !i2c.hasFlag(flagSTOPF) { if i2c.isAcknowledgeFailed(startTicks) { return false } if (ticks() - startTicks) > TIMEOUT_TICKS { return false } } return true } func (i2c *I2C) isAcknowledgeFailed(startTicks int64) bool { if i2c.hasFlag(flagAF) { // Wait until STOP Flag is reset // AutoEnd should be initiate after AF for !i2c.hasFlag(flagSTOPF) { if (ticks() - startTicks) > TIMEOUT_TICKS { return true } } i2c.clearFlag(flagAF) i2c.clearFlag(flagSTOPF) i2c.flushTXDR() i2c.resetCR2() return true } return false } func (i2c *I2C) flushTXDR() { // If a pending TXIS flag is set, write a dummy data in TXDR to clear it if i2c.hasFlag(flagTXIS) { i2c.Bus.TXDR.Set(0) } // Flush TX register if not empty if !i2c.hasFlag(flagTXE) { i2c.clearFlag(flagTXE) } } func (i2c *I2C) resetCR2() { i2c.Bus.CR2.ClearBits(stm32.I2C_CR2_SADD_Msk | stm32.I2C_CR2_HEAD10R_Msk | stm32.I2C_CR2_NBYTES_Msk | stm32.I2C_CR2_RELOAD_Msk | stm32.I2C_CR2_RD_WRN_Msk) } func (i2c *I2C) transferConfig(addr uint16, size uint8, mode uint32, request uint32) { mask := uint32(stm32.I2C_CR2_SADD_Msk | stm32.I2C_CR2_NBYTES_Msk | stm32.I2C_CR2_RELOAD_Msk | stm32.I2C_CR2_AUTOEND_Msk | (stm32.I2C_CR2_RD_WRN & uint32(request>>(31-stm32.I2C_CR2_RD_WRN_Pos))) | stm32.I2C_CR2_START_Msk | stm32.I2C_CR2_STOP_Msk) value := (uint32(addr<<1) & stm32.I2C_CR2_SADD_Msk) | ((uint32(size) << stm32.I2C_CR2_NBYTES_Pos) & stm32.I2C_CR2_NBYTES_Msk) | mode | request i2c.Bus.CR2.ReplaceBits(value, mask, 0) } func (i2c *I2C) hasFlag(flag uint32) bool { return i2c.Bus.ISR.HasBits(flag) } func (i2c *I2C) clearFlag(flag uint32) { if flag == stm32.I2C_ISR_TXE { i2c.Bus.ISR.SetBits(flag) } else { i2c.Bus.ICR.SetBits(flag) } } ================================================ FILE: src/machine/machine_stm32_iwdg.go ================================================ //go:build stm32 package machine import "device/stm32" var ( Watchdog = &watchdogImpl{} ) const ( // WatchdogMaxTimeout in milliseconds (32.768s) // // Timeout is based on 12-bit counter with /256 divider on // 32.768kHz clock. See 21.3.3 of RM0090 for table. WatchdogMaxTimeout = ((0xfff + 1) * 256 * 1024) / 32768 ) const ( // Enable access to PR, RLR and WINR registers (0x5555) iwdgKeyEnable = 0x5555 // Reset the watchdog value (0xAAAA) iwdgKeyReset = 0xaaaa // Start the watchdog (0xCCCC) iwdgKeyStart = 0xcccc // Divide by 256 iwdgDiv256 = 6 ) type watchdogImpl struct { } // Configure the watchdog. // // This method should not be called after the watchdog is started and on // some platforms attempting to reconfigure after starting the watchdog // is explicitly forbidden / will not work. func (wd *watchdogImpl) Configure(config WatchdogConfig) error { // Enable configuration of IWDG stm32.IWDG.KR.Set(iwdgKeyEnable) // Unconditionally divide by /256 since we don't really need accuracy // less than 8ms stm32.IWDG.PR.Set(iwdgDiv256) timeout := config.TimeoutMillis if timeout > WatchdogMaxTimeout { timeout = WatchdogMaxTimeout } // Set reload value based on /256 divider stm32.IWDG.RLR.Set(((config.TimeoutMillis*32768 + (256 * 1024) - 1) / (256 * 1024)) - 1) return nil } // Starts the watchdog. func (wd *watchdogImpl) Start() error { stm32.IWDG.KR.Set(iwdgKeyStart) return nil } // Update the watchdog, indicating that `source` is healthy. func (wd *watchdogImpl) Update() { stm32.IWDG.KR.Set(iwdgKeyReset) } ================================================ FILE: src/machine/machine_stm32_moder_gpio.go ================================================ //go:build stm32 && !stm32f103 package machine import ( "device/stm32" ) // GPIO for the stm32 families except the stm32f1xx which uses a simpler but // less flexible mechanism. Extend the go:build directive above to exclude other // models in the stm32f1xx series as necessary const ( // Mode Flag PinOutput PinMode = 0 PinInput PinMode = PinInputFloating PinInputFloating PinMode = 1 PinInputPulldown PinMode = 2 PinInputPullup PinMode = 3 // for UART PinModeUARTTX PinMode = 4 PinModeUARTRX PinMode = 5 // for I2C PinModeI2CSCL PinMode = 6 PinModeI2CSDA PinMode = 7 // for SPI PinModeSPICLK PinMode = 8 PinModeSPISDO PinMode = 9 PinModeSPISDI PinMode = 10 // for analog/ADC PinInputAnalog PinMode = 11 // for PWM PinModePWMOutput PinMode = 12 ) // Define several bitfields that have different names across chip families but // essentially have the same meaning. const ( // MODER bitfields. gpioModeInput = 0 gpioModeOutput = 1 gpioModeAlternate = 2 gpioModeAnalog = 3 gpioModeMask = 0x3 // PUPDR bitfields. gpioPullFloating = 0 gpioPullUp = 1 gpioPullDown = 2 gpioPullMask = 0x3 // OSPEED bitfields. gpioOutputSpeedVeryHigh = 3 gpioOutputSpeedHigh = 2 gpioOutputSpeedMedium = 1 gpioOutputSpeedLow = 0 gpioOutputSpeedMask = 0x3 ) // Configure this pin with the given configuration func (p Pin) Configure(config PinConfig) { // Use the default system alternate function; this // will only be used if you try to call this with // one of the peripheral modes instead of vanilla GPIO. p.ConfigureAltFunc(config, 0) } // Configure this pin with the given configuration including alternate // // function mapping if necessary. func (p Pin) ConfigureAltFunc(config PinConfig, altFunc uint8) { // Configure the GPIO pin. p.enableClock() port := p.getPort() pos := (uint8(p) % 16) * 2 // assume each field is two bits in size (with mask 0x3) switch config.Mode { // GPIO case PinInputFloating: port.MODER.ReplaceBits(gpioModeInput, gpioModeMask, pos) port.PUPDR.ReplaceBits(gpioPullFloating, gpioPullMask, pos) case PinInputPulldown: port.MODER.ReplaceBits(gpioModeInput, gpioModeMask, pos) port.PUPDR.ReplaceBits(gpioPullDown, gpioPullMask, pos) case PinInputPullup: port.MODER.ReplaceBits(gpioModeInput, gpioModeMask, pos) port.PUPDR.ReplaceBits(gpioPullUp, gpioPullMask, pos) case PinOutput: port.MODER.ReplaceBits(gpioModeOutput, gpioModeMask, pos) port.OSPEEDR.ReplaceBits(gpioOutputSpeedHigh, gpioOutputSpeedMask, pos) // UART case PinModeUARTTX: port.MODER.ReplaceBits(gpioModeAlternate, gpioModeMask, pos) port.OSPEEDR.ReplaceBits(gpioOutputSpeedHigh, gpioOutputSpeedMask, pos) port.PUPDR.ReplaceBits(gpioPullUp, gpioPullMask, pos) p.SetAltFunc(altFunc) case PinModeUARTRX: port.MODER.ReplaceBits(gpioModeAlternate, gpioModeMask, pos) port.PUPDR.ReplaceBits(gpioPullFloating, gpioPullMask, pos) p.SetAltFunc(altFunc) // I2C case PinModeI2CSCL: port.MODER.ReplaceBits(gpioModeAlternate, gpioModeMask, pos) port.OTYPER.ReplaceBits(stm32.GPIO_OTYPER_OT0_OpenDrain, stm32.GPIO_OTYPER_OT0_Msk, pos/2) port.OSPEEDR.ReplaceBits(gpioOutputSpeedLow, gpioOutputSpeedMask, pos) port.PUPDR.ReplaceBits(gpioPullUp, gpioPullMask, pos) p.SetAltFunc(altFunc) case PinModeI2CSDA: port.MODER.ReplaceBits(gpioModeAlternate, gpioModeMask, pos) port.OTYPER.ReplaceBits(stm32.GPIO_OTYPER_OT0_OpenDrain, stm32.GPIO_OTYPER_OT0_Msk, pos/2) port.OSPEEDR.ReplaceBits(gpioOutputSpeedLow, gpioOutputSpeedMask, pos) port.PUPDR.ReplaceBits(gpioPullUp, gpioPullMask, pos) p.SetAltFunc(altFunc) // SPI case PinModeSPICLK: port.MODER.ReplaceBits(gpioModeAlternate, gpioModeMask, pos) port.OSPEEDR.ReplaceBits(gpioOutputSpeedHigh, gpioOutputSpeedMask, pos) port.PUPDR.ReplaceBits(gpioPullFloating, gpioPullMask, pos) p.SetAltFunc(altFunc) case PinModeSPISDO: port.MODER.ReplaceBits(gpioModeAlternate, gpioModeMask, pos) port.OSPEEDR.ReplaceBits(gpioOutputSpeedLow, gpioOutputSpeedMask, pos) port.PUPDR.ReplaceBits(gpioPullFloating, gpioPullMask, pos) p.SetAltFunc(altFunc) case PinModeSPISDI: port.MODER.ReplaceBits(gpioModeAlternate, gpioModeMask, pos) port.OSPEEDR.ReplaceBits(gpioOutputSpeedLow, gpioOutputSpeedMask, pos) port.PUPDR.ReplaceBits(gpioPullFloating, gpioPullMask, pos) p.SetAltFunc(altFunc) // PWM case PinModePWMOutput: port.MODER.ReplaceBits(gpioModeAlternate, gpioModeMask, pos) port.OSPEEDR.ReplaceBits(gpioOutputSpeedHigh, gpioOutputSpeedMask, pos) port.PUPDR.ReplaceBits(gpioPullFloating, gpioPullMask, pos) p.SetAltFunc(altFunc) // ADC case PinInputAnalog: port.MODER.ReplaceBits(gpioModeAnalog, gpioModeMask, pos) port.PUPDR.ReplaceBits(gpioPullFloating, gpioPullMask, pos) } } // SetAltFunc maps the given alternative function to the I/O pin func (p Pin) SetAltFunc(af uint8) { port := p.getPort() pin := uint8(p) % 16 pos := (pin % 8) * 4 if pin < 8 { port.AFRL.ReplaceBits(uint32(af), 0xf, pos) } else { port.AFRH.ReplaceBits(uint32(af), 0xf, pos) } } ================================================ FILE: src/machine/machine_stm32_rng.go ================================================ //go:build stm32 && !(stm32f103 || stm32l0x1) package machine import "device/stm32" var rngInitDone = false const RNG_MAX_READ_RETRIES = 1000 // GetRNG returns 32 bits of cryptographically secure random data func GetRNG() (uint32, error) { if !rngInitDone { initRNG() rngInitDone = true } if stm32.RNG.SR.HasBits(stm32.RNG_SR_CECS) { return 0, ErrClockRNG } if stm32.RNG.SR.HasBits(stm32.RNG_SR_SECS) { return 0, ErrSeedRNG } cnt := RNG_MAX_READ_RETRIES for !stm32.RNG.SR.HasBits(stm32.RNG_SR_DRDY) { cnt-- if cnt == 0 { return 0, ErrTimeoutRNG } } ret := stm32.RNG.DR.Get() return ret, nil } ================================================ FILE: src/machine/machine_stm32_spi.go ================================================ //go:build stm32 && !stm32f7x2 && !stm32l5x2 package machine // Peripheral abstraction layer for SPI on the stm32 family import ( "device/stm32" "runtime/volatile" "unsafe" ) // SPIConfig is used to store config info for SPI. type SPIConfig struct { Frequency uint32 SCK Pin SDO Pin SDI Pin LSBFirst bool Mode uint8 } // Configure is intended to setup the STM32 SPI1 interface. func (spi *SPI) Configure(config SPIConfig) error { // -- CONFIGURING THE SPI IN MASTER MODE -- // // 1. Select the BR[2:0] bits to define the serial clock baud rate (see // SPI_CR1 register). // 2. Select the CPOL and CPHA bits to define one of the four relationships // between the data transfer and the serial clock (see Figure 248). This // step is not required when the TI mode is selected. // 3. Set the DFF bit to define 8- or 16-bit data frame format // 4. Configure the LSBFIRST bit in the SPI_CR1 register to define the frame // format. This step is not required when the TI mode is selected. // 5. If the NSS pin is required in input mode, in hardware mode, connect the // NSS pin to a high-level signal during the complete byte transmit // sequence. In NSS software mode, set the SSM and SSI bits in the SPI_CR1 // register. If the NSS pin is required in output mode, the SSOE bit only // should be set. This step is not required when the TI mode is selected. // 6. Set the FRF bit in SPI_CR2 to select the TI protocol for serial // communications. // 7. The MSTR and SPE bits must be set (they remain set only if the NSS pin // is connected to a high-level signal). // disable SPI interface before any configuration changes spi.Bus.CR1.ClearBits(stm32.SPI_CR1_SPE) // enable clock for SPI enableAltFuncClock(unsafe.Pointer(spi.Bus)) // init pins if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { config.SCK = SPI0_SCK_PIN config.SDO = SPI0_SDO_PIN config.SDI = SPI0_SDI_PIN } spi.configurePins(config) // Get SPI baud rate based on the bus speed it's attached to var conf uint32 = spi.getBaudRate(config) // set bit transfer order if config.LSBFirst { conf |= stm32.SPI_CR1_LSBFIRST } // set polarity and phase on the SPI interface switch config.Mode { case Mode1: conf |= stm32.SPI_CR1_CPHA case Mode2: conf |= stm32.SPI_CR1_CPOL case Mode3: conf |= stm32.SPI_CR1_CPOL conf |= stm32.SPI_CR1_CPHA } // configure as SPI master conf |= stm32.SPI_CR1_MSTR | stm32.SPI_CR1_SSI // enable the SPI interface conf |= stm32.SPI_CR1_SPE // use software CS (GPIO) by default conf |= stm32.SPI_CR1_SSM // now set the configuration spi.Bus.CR1.Set(conf) // Series-specific configuration to set 8-bit transfer mode spi.config8Bits() // enable SPI spi.Bus.CR1.SetBits(stm32.SPI_CR1_SPE) return nil } // Transfer writes/reads a single byte using the SPI interface. func (spi *SPI) Transfer(w byte) (byte, error) { // 1. Enable the SPI by setting the SPE bit to 1. // 2. Write the first data item to be transmitted into the SPI_DR register // (this clears the TXE flag). // 3. Wait until TXE=1 and write the second data item to be transmitted. Then // wait until RXNE=1 and read the SPI_DR to get the first received data // item (this clears the RXNE bit). Repeat this operation for each data // item to be transmitted/received until the n–1 received data. // 4. Wait until RXNE=1 and read the last received data. // 5. Wait until TXE=1 and then wait until BSY=0 before disabling the SPI. // put output word (8-bit) in data register (DR), which is parallel-loaded // into shift register, and shifted out on MOSI. Some series have 16-bit // register but writes must be strictly 8-bit to output a byte. Writing // 16-bits indicates a packed transfer (2 bytes). (*volatile.Register8)(unsafe.Pointer(&spi.Bus.DR.Reg)).Set(w) // wait for SPI bus receive buffer not empty bit (RXNE) to be set. // warning: blocks forever until this condition is met. for !spi.Bus.SR.HasBits(stm32.SPI_SR_RXNE) { } // copy input word (8-bit) in data register (DR), which was shifted in on MISO // and parallel-loaded into register. data := byte(spi.Bus.DR.Get()) // wait for SPI bus transmit buffer empty bit (TXE) to be set. // warning: blocks forever until this condition is met. for !spi.Bus.SR.HasBits(stm32.SPI_SR_TXE) { } // wait for SPI bus busy bit (BSY) to be clear to indicate synchronous // transfer complete. this will effectively prevent this Transfer() function // from being capable of maintaining high-bandwidth communication throughput, // but it will help guarantee stability on the bus. for spi.Bus.SR.HasBits(stm32.SPI_SR_BSY) { } // clear the overrun flag (only in full-duplex mode) if !spi.Bus.CR1.HasBits(stm32.SPI_CR1_RXONLY | stm32.SPI_CR1_BIDIMODE | stm32.SPI_CR1_BIDIOE) { spi.Bus.SR.Get() } // Return received data from SPI data register return data, nil } ================================================ FILE: src/machine/machine_stm32_tim.go ================================================ //go:build stm32 package machine // The type alias `arrtype` should be defined to either uint32 or uint16 // depending on the size of that register in the MCU's TIM_Type structure. import ( "device/stm32" "runtime/interrupt" "runtime/volatile" ) const PWM_MODE1 = 0x6 type TimerCallback func() type ChannelCallback func(channel uint8) type PinFunction struct { Pin Pin AltFunc uint8 } type TimerChannel struct { Pins []PinFunction } type TIM struct { EnableRegister *volatile.Register32 EnableFlag uint32 Device *stm32.TIM_Type Channels [4]TimerChannel UpInterrupt interrupt.Interrupt OCInterrupt interrupt.Interrupt wraparoundCallback TimerCallback channelCallbacks [4]ChannelCallback busFreq uint64 } // Configure enables and configures this PWM. func (t *TIM) Configure(config PWMConfig) error { // Enable device t.EnableRegister.SetBits(t.EnableFlag) err := t.setPeriod(config.Period, true) if err != nil { return err } // Auto-repeat t.Device.EGR.SetBits(stm32.TIM_EGR_UG) // Enable the timer t.Device.CR1.SetBits(stm32.TIM_CR1_CEN | stm32.TIM_CR1_ARPE) return nil } func (t *TIM) Count() uint32 { return uint32(t.Device.CNT.Get()) } // SetWraparoundInterrupt configures a callback to be called each // time the timer 'wraps-around'. // // For example, if `Configure(PWMConfig{Period:1000000})` is used, // to set the timer period to 1ms, this callback will be called every // 1ms. func (t *TIM) SetWraparoundInterrupt(callback TimerCallback) error { // Disable this interrupt to prevent race conditions //t.UpInterrupt.Disable() // Ensure the interrupt handler for Update events is registered t.UpInterrupt = t.registerUPInterrupt() // Clear update flag t.Device.SR.ClearBits(stm32.TIM_SR_UIF) t.wraparoundCallback = callback t.UpInterrupt.SetPriority(0xc1) t.UpInterrupt.Enable() // Enable the hardware interrupt t.Device.DIER.SetBits(stm32.TIM_DIER_UIE) return nil } // Sets a callback to be called when a channel reaches it's set-point. // // For example, if `t.Set(ch, t.Top() / 4)` is used then the callback will // be called every quarter-period of the timer's base Period. func (t *TIM) SetMatchInterrupt(channel uint8, callback ChannelCallback) error { t.channelCallbacks[channel] = callback // Ensure the interrupt handler for Output Compare events is registered t.OCInterrupt = t.registerOCInterrupt() // Clear the interrupt flag t.Device.SR.ClearBits(stm32.TIM_SR_CC1IF << channel) // Enable the interrupt t.OCInterrupt.SetPriority(0xc1) t.OCInterrupt.Enable() // Enable the hardware interrupt t.Device.DIER.SetBits(stm32.TIM_DIER_CC1IE << channel) return nil } // SetPeriod updates the period of this PWM peripheral. // To set a particular frequency, use the following formula: // // period = 1e9 / frequency // // If you use a period of 0, a period that works well for LEDs will be picked. // // SetPeriod will not change the prescaler, but also won't change the current // value in any of the channels. This means that you may need to update the // value for the particular channel. // // Note that you cannot pick any arbitrary period after the PWM peripheral has // been configured. If you want to switch between frequencies, pick the lowest // frequency (longest period) once when calling Configure and adjust the // frequency here as needed. func (t *TIM) SetPeriod(period uint64) error { return t.setPeriod(period, false) } func (t *TIM) setPeriod(period uint64, updatePrescaler bool) error { var top uint64 if period == 0 { top = ARR_MAX } else { top = (period / 1000) * (t.busFreq / 1000) / 1000 } var psc uint64 if updatePrescaler { if top > ARR_MAX*PSC_MAX { return ErrPWMPeriodTooLong } // Select the minimum PSC that scales the ARR value into // range to maintain precision in ARR for changing frequencies // later psc = ceil(top, ARR_MAX) top = top / psc t.Device.PSC.Set(uint32(psc - 1)) } else { psc = uint64(t.Device.PSC.Get()) + 1 top = top / psc if top > ARR_MAX { return ErrPWMPeriodTooLong } } t.Device.ARR.Set(arrtype(top - 1)) return nil } // Top returns the current counter top, for use in duty cycle calculation. It // will only change with a call to Configure or SetPeriod, otherwise it is // constant. // // The value returned here is hardware dependent. In general, it's best to treat // it as an opaque value that can be divided by some number and passed to // pwm.Set (see pwm.Set for more information). func (t *TIM) Top() uint32 { return uint32(t.Device.ARR.Get()) + 1 } // Channel returns a PWM channel for the given pin. func (t *TIM) Channel(pin Pin) (uint8, error) { for chi, ch := range t.Channels { for _, p := range ch.Pins { if p.Pin == pin { t.configurePin(uint8(chi), p) //p.Pin.ConfigureAltFunc(PinConfig{Mode: PinModePWMOutput}, p.AltFunc) return uint8(chi), nil } } } return 0, ErrInvalidOutputPin } // Set updates the channel value. This is used to control the channel duty // cycle. For example, to set it to a 25% duty cycle, use: // // t.Set(ch, t.Top() / 4) // // ch.Set(0) will set the output to low and ch.Set(ch.Top()) will set the output // to high, assuming the output isn't inverted. func (t *TIM) Set(channel uint8, value uint32) { t.enableMainOutput() ccr := t.channelCCR(channel) ccmr, offset := t.channelCCMR(channel) // Disable interrupts whilst programming to prevent spurious OC interrupts mask := interrupt.Disable() // Set the PWM to Mode 1 (active below set value, inactive above) // Preload is disabled so we can change OC value within one update period. var ccmrVal uint32 ccmrVal |= PWM_MODE1 << stm32.TIM_CCMR1_Output_OC1M_Pos ccmr.ReplaceBits(ccmrVal, 0xFF, offset) // Set the compare value ccr.Set(arrtype(value)) // Enable the channel (if not already) t.Device.CCER.ReplaceBits(stm32.TIM_CCER_CC1E, 0xD, channel*4) // Force update t.Device.EGR.SetBits(stm32.TIM_EGR_CC1G << channel) // Reset Interrupt Flag t.Device.SR.ClearBits(stm32.TIM_SR_CC1IF << channel) // Restore interrupts interrupt.Restore(mask) } // Unset disables a channel, including any configured interrupts. func (t *TIM) Unset(channel uint8) { // Disable interrupts whilst programming to prevent spurious OC interrupts mask := interrupt.Disable() // Disable the channel t.Device.CCER.ReplaceBits(0, 0xD, channel*4) // Reset to zero value ccr := t.channelCCR(channel) ccr.Set(0) // Disable the hardware interrupt t.Device.DIER.ClearBits(stm32.TIM_DIER_CC1IE << channel) // Clear the interrupt flag t.Device.SR.ClearBits(stm32.TIM_SR_CC1IF << channel) // Restore interrupts interrupt.Restore(mask) } // SetInverting sets whether to invert the output of this channel. // Without inverting, a 25% duty cycle would mean the output is high for 25% of // the time and low for the rest. Inverting flips the output as if a NOT gate // was placed at the output, meaning that the output would be 25% low and 75% // high with a duty cycle of 25%. func (t *TIM) SetInverting(channel uint8, inverting bool) { // Enable the channel (if not already) var val = uint32(0) if inverting { val |= stm32.TIM_CCER_CC1P } t.Device.CCER.ReplaceBits(val, stm32.TIM_CCER_CC1P_Msk, channel*4) } func (t *TIM) handleUPInterrupt(interrupt.Interrupt) { if t.Device.SR.HasBits(stm32.TIM_SR_UIF) { // clear the update flag t.Device.SR.ClearBits(stm32.TIM_SR_UIF) if t.wraparoundCallback != nil { t.wraparoundCallback() } } } func (t *TIM) handleOCInterrupt(interrupt.Interrupt) { if t.Device.SR.HasBits(stm32.TIM_SR_CC1IF) { if t.channelCallbacks[0] != nil { t.channelCallbacks[0](0) } } if t.Device.SR.HasBits(stm32.TIM_SR_CC2IF) { if t.channelCallbacks[1] != nil { t.channelCallbacks[1](1) } } if t.Device.SR.HasBits(stm32.TIM_SR_CC3IF) { if t.channelCallbacks[2] != nil { t.channelCallbacks[2](2) } } if t.Device.SR.HasBits(stm32.TIM_SR_CC4IF) { if t.channelCallbacks[3] != nil { t.channelCallbacks[3](3) } } // Reset interrupt flags t.Device.SR.ClearBits(stm32.TIM_SR_CC1IF | stm32.TIM_SR_CC2IF | stm32.TIM_SR_CC3IF | stm32.TIM_SR_CC4IF) } func (t *TIM) channelCCR(channel uint8) *arrRegType { switch channel { case 0: return &t.Device.CCR1 case 1: return &t.Device.CCR2 case 2: return &t.Device.CCR3 case 3: return &t.Device.CCR4 } return nil } func (t *TIM) channelCCMR(channel uint8) (reg *volatile.Register32, offset uint8) { switch channel { case 0: return &t.Device.CCMR1_Output, 0 case 1: return &t.Device.CCMR1_Output, 8 case 2: return &t.Device.CCMR2_Output, 0 case 3: return &t.Device.CCMR2_Output, 8 } return nil, 0 } //go:inline func ceil(num uint64, denom uint64) uint64 { return (num + denom - 1) / denom } ================================================ FILE: src/machine/machine_stm32_tim_moder.go ================================================ //go:build stm32 && !stm32f1 package machine // Configuration of a GPIO pin for PWM output for STM32 MCUs with MODER // register (most MCUs except STM32F1 series). func (t *TIM) configurePin(channel uint8, pf PinFunction) { pf.Pin.ConfigureAltFunc(PinConfig{Mode: PinModePWMOutput}, pf.AltFunc) } ================================================ FILE: src/machine/machine_stm32_uart.go ================================================ //go:build stm32 package machine // Peripheral abstraction layer for UARTs on the stm32 family. import ( "device/stm32" "runtime/interrupt" "runtime/volatile" "unsafe" ) // UART representation type UART struct { Buffer *RingBuffer Bus *stm32.USART_Type Interrupt interrupt.Interrupt TxAltFuncSelector uint8 RxAltFuncSelector uint8 // Registers specific to the chip rxReg *volatile.Register32 txReg *volatile.Register32 statusReg *volatile.Register32 txEmptyFlag uint32 } // Configure the UART. func (uart *UART) Configure(config UARTConfig) { // Default baud rate to 115200. if config.BaudRate == 0 { config.BaudRate = 115200 } // Set the GPIO pins to defaults if they're not set if config.TX == 0 && config.RX == 0 { config.TX = UART_TX_PIN config.RX = UART_RX_PIN } // STM32 families have different, but compatible, registers for // basic UART functions. For each family populate the registers // into `uart`. uart.setRegisters() // Enable USART clock enableAltFuncClock(unsafe.Pointer(uart.Bus)) uart.configurePins(config) // Set baud rate uart.SetBaudRate(config.BaudRate) // Enable USART port, tx, rx and rx interrupts uart.Bus.CR1.Set(stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE) // Enable RX IRQ uart.Interrupt.SetPriority(0xc0) uart.Interrupt.Enable() } // handleInterrupt should be called from the appropriate interrupt handler for // this UART instance. func (uart *UART) handleInterrupt(interrupt.Interrupt) { uart.Receive(byte((uart.rxReg.Get() & 0xFF))) } // SetBaudRate sets the communication speed for the UART. Defer to chip-specific // routines for calculation func (uart *UART) SetBaudRate(br uint32) { divider := uart.getBaudRateDivisor(br) uart.Bus.BRR.Set(divider) } // WriteByte writes a byte of data to the UART. func (uart *UART) writeByte(c byte) error { uart.txReg.Set(uint32(c)) for !uart.statusReg.HasBits(uart.txEmptyFlag) { } return nil } func (uart *UART) flush() {} ================================================ FILE: src/machine/machine_stm32f103.go ================================================ //go:build stm32 && stm32f103 package machine // Peripheral abstraction layer for the stm32. import ( "device/stm32" "runtime/interrupt" "runtime/volatile" "unsafe" ) func CPUFrequency() uint32 { return 72000000 } var deviceIDAddr = []uintptr{0x1FFFF7E8, 0x1FFFF7EC, 0x1FFFF7F0} // Internal use: configured speed of the APB1 and APB2 timers, this should be kept // in sync with any changes to runtime package which configures the oscillators // and clock frequencies const APB1_TIM_FREQ = 72e6 // 72MHz const APB2_TIM_FREQ = 72e6 // 72MHz const ( PinInput PinMode = 0 // Input mode PinOutput10MHz PinMode = 1 // Output mode, max speed 10MHz PinOutput2MHz PinMode = 2 // Output mode, max speed 2MHz PinOutput50MHz PinMode = 3 // Output mode, max speed 50MHz PinOutput PinMode = PinOutput2MHz PinInputModeAnalog PinMode = 0 // Input analog mode PinInputModeFloating PinMode = 4 // Input floating mode PinInputModePullUpDown PinMode = 8 // Input pull up/down mode PinInputModeReserved PinMode = 12 // Input mode (reserved) PinOutputModeGPPushPull PinMode = 0 // Output mode general purpose push/pull PinOutputModeGPOpenDrain PinMode = 4 // Output mode general purpose open drain PinOutputModeAltPushPull PinMode = 8 // Output mode alt. purpose push/pull PinOutputModeAltOpenDrain PinMode = 12 // Output mode alt. purpose open drain // Pull-up vs Pull down is not part of the CNF0 / CNF1 bits, but is // controlled by PxODR. Encoded using the 'spare' bit 5. PinInputPulldown PinMode = PinInputModePullUpDown PinInputPullup PinMode = PinInputModePullUpDown | 0x10 ) // Pin constants for all stm32f103 package sizes const ( PA0 = portA + 0 PA1 = portA + 1 PA2 = portA + 2 PA3 = portA + 3 PA4 = portA + 4 PA5 = portA + 5 PA6 = portA + 6 PA7 = portA + 7 PA8 = portA + 8 PA9 = portA + 9 PA10 = portA + 10 PA11 = portA + 11 PA12 = portA + 12 PA13 = portA + 13 PA14 = portA + 14 PA15 = portA + 15 PB0 = portB + 0 PB1 = portB + 1 PB2 = portB + 2 PB3 = portB + 3 PB4 = portB + 4 PB5 = portB + 5 PB6 = portB + 6 PB7 = portB + 7 PB8 = portB + 8 PB9 = portB + 9 PB10 = portB + 10 PB11 = portB + 11 PB12 = portB + 12 PB13 = portB + 13 PB14 = portB + 14 PB15 = portB + 15 PC0 = portC + 0 PC1 = portC + 1 PC2 = portC + 2 PC3 = portC + 3 PC4 = portC + 4 PC5 = portC + 5 PC6 = portC + 6 PC7 = portC + 7 PC8 = portC + 8 PC9 = portC + 9 PC10 = portC + 10 PC11 = portC + 11 PC12 = portC + 12 PC13 = portC + 13 PC14 = portC + 14 PC15 = portC + 15 PD0 = portD + 0 PD1 = portD + 1 PD2 = portD + 2 PD3 = portD + 3 PD4 = portD + 4 PD5 = portD + 5 PD6 = portD + 6 PD7 = portD + 7 PD8 = portD + 8 PD9 = portD + 9 PD10 = portD + 10 PD11 = portD + 11 PD12 = portD + 12 PD13 = portD + 13 PD14 = portD + 14 PD15 = portD + 15 PE0 = portE + 0 PE1 = portE + 1 PE2 = portE + 2 PE3 = portE + 3 PE4 = portE + 4 PE5 = portE + 5 PE6 = portE + 6 PE7 = portE + 7 PE8 = portE + 8 PE9 = portE + 9 PE10 = portE + 10 PE11 = portE + 11 PE12 = portE + 12 PE13 = portE + 13 PE14 = portE + 14 PE15 = portE + 15 PF0 = portF + 0 PF1 = portF + 1 PF2 = portF + 2 PF3 = portF + 3 PF4 = portF + 4 PF5 = portF + 5 PF6 = portF + 6 PF7 = portF + 7 PF8 = portF + 8 PF9 = portF + 9 PF10 = portF + 10 PF11 = portF + 11 PF12 = portF + 12 PF13 = portF + 13 PF14 = portF + 14 PF15 = portF + 15 ) // Configure this pin with the given I/O settings. // stm32f1xx uses different technique for setting the GPIO pins than the stm32f407 func (p Pin) Configure(config PinConfig) { // Configure the GPIO pin. p.enableClock() port := p.getPort() pin := uint8(p) % 16 pos := (pin % 8) * 4 if pin < 8 { port.CRL.ReplaceBits(uint32(config.Mode), 0xf, pos) } else { port.CRH.ReplaceBits(uint32(config.Mode), 0xf, pos) } // If configured for input pull-up or pull-down, set ODR // for desired pull-up or pull-down. if (config.Mode & 0xf) == PinInputModePullUpDown { var pullup uint32 if config.Mode == PinInputPullup { pullup = 1 } port.ODR.ReplaceBits(pullup, 0x1, pin) } } func (p Pin) getPort() *stm32.GPIO_Type { switch p / 16 { case 0: return stm32.GPIOA case 1: return stm32.GPIOB case 2: return stm32.GPIOC case 3: return stm32.GPIOD case 4: return stm32.GPIOE case 5: return stm32.GPIOF case 6: return stm32.GPIOG default: panic("machine: unknown port") } } // enableClock enables the clock for this desired GPIO port. func (p Pin) enableClock() { switch p / 16 { case 0: stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_IOPAEN) case 1: stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_IOPBEN) case 2: stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_IOPCEN) case 3: stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_IOPDEN) case 4: stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_IOPEEN) case 5: stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_IOPFEN) case 6: stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_IOPGEN) default: panic("machine: unknown port") } } // Enable peripheral clock. Expand to include all the desired peripherals func enableAltFuncClock(bus unsafe.Pointer) { switch bus { case unsafe.Pointer(stm32.USART1): stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN) case unsafe.Pointer(stm32.USART2): stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART2EN) case unsafe.Pointer(stm32.I2C1): stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C1EN) case unsafe.Pointer(stm32.SPI1): stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SPI1EN) case unsafe.Pointer(stm32.ADC1): stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_ADC1EN) default: panic("machine: unknown peripheral") } } func (p Pin) registerInterrupt() interrupt.Interrupt { pin := uint8(p) % 16 switch pin { case 0: return interrupt.New(stm32.IRQ_EXTI0, func(interrupt.Interrupt) { handlePinInterrupt(0) }) case 1: return interrupt.New(stm32.IRQ_EXTI1, func(interrupt.Interrupt) { handlePinInterrupt(1) }) case 2: return interrupt.New(stm32.IRQ_EXTI2, func(interrupt.Interrupt) { handlePinInterrupt(2) }) case 3: return interrupt.New(stm32.IRQ_EXTI3, func(interrupt.Interrupt) { handlePinInterrupt(3) }) case 4: return interrupt.New(stm32.IRQ_EXTI4, func(interrupt.Interrupt) { handlePinInterrupt(4) }) case 5: return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(5) }) case 6: return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(6) }) case 7: return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(7) }) case 8: return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(8) }) case 9: return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(9) }) case 10: return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(10) }) case 11: return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(11) }) case 12: return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(12) }) case 13: return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(13) }) case 14: return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(14) }) case 15: return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(15) }) } return interrupt.Interrupt{} } //---------- UART related code // Configure the TX and RX pins func (uart *UART) configurePins(config UARTConfig) { // pins switch config.TX { case UART_ALT_TX_PIN: // use alternate TX/RX pins via AFIO mapping stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_AFIOEN) if uart.Bus == stm32.USART1 { stm32.AFIO.MAPR.SetBits(stm32.AFIO_MAPR_USART1_REMAP) } else if uart.Bus == stm32.USART2 { stm32.AFIO.MAPR.SetBits(stm32.AFIO_MAPR_USART2_REMAP) } default: // use standard TX/RX pins PA9 and PA10 } config.TX.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltPushPull}) config.RX.Configure(PinConfig{Mode: PinInputModeFloating}) } // Determine the divisor for USARTs to get the given baudrate func (uart *UART) getBaudRateDivisor(br uint32) uint32 { // Note: PCLK2 (from APB2) used for USART1 and PCLK1 for USART2, 3, 4, 5 var divider uint32 if uart.Bus == stm32.USART1 { // first divide by PCLK2 prescaler (div 1) and then desired baudrate divider = CPUFrequency() / br } else { // first divide by PCLK1 prescaler (div 2) and then desired baudrate divider = CPUFrequency() / 2 / br } return divider } // Register names vary by ST processor, these are for STM F103xx func (uart *UART) setRegisters() { uart.rxReg = &uart.Bus.DR uart.txReg = &uart.Bus.DR uart.statusReg = &uart.Bus.SR uart.txEmptyFlag = stm32.USART_SR_TXE } //---------- SPI related types and code type SPI struct { Bus *stm32.SPI_Type } // There are 3 SPI interfaces on the STM32F103xx. // Since the first interface is named SPI1, both SPI0 and SPI1 refer to SPI1. // TODO: implement SPI2 and SPI3. var ( SPI1 = &SPI{Bus: stm32.SPI1} SPI0 = SPI1 ) func (spi *SPI) config8Bits() { // no-op on this series } // Set baud rate for SPI func (spi *SPI) getBaudRate(config SPIConfig) uint32 { var conf uint32 // set frequency dependent on PCLK2 prescaler (div 1) switch { case config.Frequency < 125000: // Note: impossible to achieve lower frequency with current PCLK2! conf |= stm32.SPI_CR1_BR_Div256 case config.Frequency < 250000: conf |= stm32.SPI_CR1_BR_Div256 case config.Frequency < 500000: conf |= stm32.SPI_CR1_BR_Div128 case config.Frequency < 1000000: conf |= stm32.SPI_CR1_BR_Div64 case config.Frequency < 2000000: conf |= stm32.SPI_CR1_BR_Div32 case config.Frequency < 4000000: conf |= stm32.SPI_CR1_BR_Div16 default: // When its bigger than Div16, just round to the maximum frequency. conf |= stm32.SPI_CR1_BR_Div8 } return conf << stm32.SPI_CR1_BR_Pos } // Configure SPI pins for input output and clock func (spi *SPI) configurePins(config SPIConfig) { config.SCK.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltPushPull}) config.SDO.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltPushPull}) config.SDI.Configure(PinConfig{Mode: PinInputModeFloating}) } //---------- I2C related types and code // There are 2 I2C interfaces on the STM32F103xx. // Since the first interface is named I2C1, both I2C0 and I2C1 refer to I2C1. // TODO: implement I2C2. type I2C struct { Bus *stm32.I2C_Type } var ( I2C1 = &I2C{Bus: stm32.I2C1} I2C0 = I2C1 ) func (i2c *I2C) configurePins(config I2CConfig) { if config.SDA == PB9 { // use alternate I2C1 pins PB8/PB9 via AFIO mapping stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_AFIOEN) stm32.AFIO.MAPR.SetBits(stm32.AFIO_MAPR_I2C1_REMAP) } config.SDA.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltOpenDrain}) config.SCL.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltOpenDrain}) } func (i2c *I2C) getFreqRange(config I2CConfig) uint32 { // pclk1 clock speed is main frequency divided by PCLK1 prescaler (div 2) pclk1 := CPUFrequency() / 2 // set frequency range to PCLK1 clock speed in MHz // aka setting the value 36 means to use 36 MHz clock return pclk1 / 1000000 } func (i2c *I2C) getRiseTime(config I2CConfig) uint32 { // These bits must be programmed with the maximum SCL rise time given in the // I2C bus specification, incremented by 1. // For instance: in Sm mode, the maximum allowed SCL rise time is 1000 ns. // If, in the I2C_CR2 register, the value of FREQ[5:0] bits is equal to 0x08 // and PCLK1 = 125 ns, therefore the TRISE[5:0] bits must be programmed with // 09h (1000 ns / 125 ns = 8 + 1) freqRange := i2c.getFreqRange(config) if config.Frequency > 100000 { // fast mode (Fm) adjustment freqRange *= 300 freqRange /= 1000 } return (freqRange + 1) << stm32.I2C_TRISE_TRISE_Pos } func (i2c *I2C) getSpeed(config I2CConfig) uint32 { ccr := func(pclk uint32, freq uint32, coeff uint32) uint32 { return (((pclk - 1) / (freq * coeff)) + 1) & stm32.I2C_CCR_CCR_Msk } sm := func(pclk uint32, freq uint32) uint32 { // standard mode (Sm) if s := ccr(pclk, freq, 2); s < 4 { return 4 } else { return s } } fm := func(pclk uint32, freq uint32, duty uint8) uint32 { // fast mode (Fm) if duty == DutyCycle2 { return ccr(pclk, freq, 3) } else { return ccr(pclk, freq, 25) | stm32.I2C_CCR_DUTY } } clock := CPUFrequency() / 2 if config.Frequency <= 100000 { return sm(clock, config.Frequency) } else { s := fm(clock, config.Frequency, config.DutyCycle) if (s & stm32.I2C_CCR_CCR_Msk) == 0 { return 1 } else { return s | stm32.I2C_CCR_F_S } } } //---------- Timer related code // For Pin Mappings see RM0008, pg 179 // https://www.st.com/resource/en/reference_manual/cd00171190-stm32f101xx-stm32f102xx-stm32f103xx-stm32f105xx-and-stm32f107xx-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf // // Note: for STM32F1 series the pin mapping is done 'per timer' not per channel, // not all channels on a timer have the same degrees of flexibility, and some // combinations are only available on some packages - so care is needed at app // level to ensure valid combinations of pins are used. // var ( TIM1 = TIM{ EnableRegister: &stm32.RCC.APB2ENR, EnableFlag: stm32.RCC_APB2ENR_TIM1EN, Device: stm32.TIM1, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{{PE9, 0b11}, {PA8, 0b00}}}, TimerChannel{Pins: []PinFunction{{PE11, 0b11}, {PA9, 0b00}}}, TimerChannel{Pins: []PinFunction{{PE13, 0b11}, {PA10, 0b00}}}, TimerChannel{Pins: []PinFunction{{PE14, 0b11}, {PA11, 0b00}}}, }, busFreq: APB2_TIM_FREQ, } TIM2 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM2EN, Device: stm32.TIM2, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{{PA0, 0b00}, {PA15, 0b01}}}, TimerChannel{Pins: []PinFunction{{PA1, 0b00}, {PB3, 0b01}}}, TimerChannel{Pins: []PinFunction{{PA2, 0b00}, {PB10, 0b10}}}, TimerChannel{Pins: []PinFunction{{PA3, 0b00}, {PB11, 0b10}}}, }, busFreq: APB1_TIM_FREQ, } TIM3 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM3EN, Device: stm32.TIM3, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{{PA6, 0b00}, {PC6, 0b11}, {PB4, 0b10}}}, TimerChannel{Pins: []PinFunction{{PA7, 0b00}, {PC7, 0b11}, {PB5, 0b10}}}, TimerChannel{Pins: []PinFunction{{PB0, 0b00}, {PC8, 0b11}}}, TimerChannel{Pins: []PinFunction{{PB1, 0b00}, {PC9, 0b11}}}, }, busFreq: APB1_TIM_FREQ, } TIM4 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM4EN, Device: stm32.TIM4, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{{PD12, 0b1}, {PB6, 0}}}, TimerChannel{Pins: []PinFunction{{PD13, 0b1}, {PB7, 0}}}, TimerChannel{Pins: []PinFunction{{PD14, 0b1}, {PB8, 0}}}, TimerChannel{Pins: []PinFunction{{PD15, 0b1}, {PB9, 0}}}, }, busFreq: APB1_TIM_FREQ, } TIM5 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM5EN, Device: stm32.TIM5, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{{PA3, 0b0}}}, }, busFreq: APB1_TIM_FREQ, } TIM6 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM6EN, Device: stm32.TIM6, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB1_TIM_FREQ, } TIM7 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM7EN, Device: stm32.TIM7, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB1_TIM_FREQ, } TIM8 = TIM{ EnableRegister: &stm32.RCC.APB2ENR, EnableFlag: stm32.RCC_APB2ENR_TIM8EN, Device: stm32.TIM8, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB2_TIM_FREQ, } TIM9 = TIM{ EnableRegister: &stm32.RCC.APB2ENR, EnableFlag: stm32.RCC_APB2ENR_TIM9EN, Device: stm32.TIM9, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{{PA2, 0b0}, {PE5, 0b1}}}, TimerChannel{Pins: []PinFunction{{PA3, 0b0}, {PE6, 0b1}}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB2_TIM_FREQ, } TIM10 = TIM{ EnableRegister: &stm32.RCC.APB2ENR, EnableFlag: stm32.RCC_APB2ENR_TIM10EN, Device: stm32.TIM10, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{{PB8, 0b0}, {PF6, 0b1}}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB2_TIM_FREQ, } TIM11 = TIM{ EnableRegister: &stm32.RCC.APB2ENR, EnableFlag: stm32.RCC_APB2ENR_TIM11EN, Device: stm32.TIM11, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{{PB9, 0b0}, {PF7, 0b1}}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB2_TIM_FREQ, } TIM12 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM12EN, Device: stm32.TIM12, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{{}}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB1_TIM_FREQ, } TIM13 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM13EN, Device: stm32.TIM13, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{{PA6, 0b0}, {PF8, 0b1}}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB1_TIM_FREQ, } TIM14 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM14EN, Device: stm32.TIM14, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{{PA7, 0b0}, {PF9, 0b1}}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB1_TIM_FREQ, } ) func (t *TIM) registerUPInterrupt() interrupt.Interrupt { switch t { case &TIM1: return interrupt.New(stm32.IRQ_TIM1_UP, TIM1.handleUPInterrupt) case &TIM2: return interrupt.New(stm32.IRQ_TIM2, TIM2.handleUPInterrupt) case &TIM3: return interrupt.New(stm32.IRQ_TIM3, TIM3.handleUPInterrupt) case &TIM4: return interrupt.New(stm32.IRQ_TIM4, TIM4.handleUPInterrupt) case &TIM5: return interrupt.New(stm32.IRQ_TIM5, TIM5.handleUPInterrupt) case &TIM6: return interrupt.New(stm32.IRQ_TIM6, TIM6.handleUPInterrupt) case &TIM7: return interrupt.New(stm32.IRQ_TIM7, TIM7.handleUPInterrupt) case &TIM8: return interrupt.New(stm32.IRQ_TIM8_UP, TIM8.handleUPInterrupt) } return interrupt.Interrupt{} } func (t *TIM) registerOCInterrupt() interrupt.Interrupt { switch t { case &TIM1: return interrupt.New(stm32.IRQ_TIM1_CC, TIM1.handleOCInterrupt) case &TIM2: return interrupt.New(stm32.IRQ_TIM2, TIM2.handleOCInterrupt) case &TIM3: return interrupt.New(stm32.IRQ_TIM3, TIM3.handleOCInterrupt) case &TIM4: return interrupt.New(stm32.IRQ_TIM4, TIM4.handleOCInterrupt) case &TIM5: return interrupt.New(stm32.IRQ_TIM5, TIM5.handleOCInterrupt) case &TIM6: return interrupt.New(stm32.IRQ_TIM6, TIM6.handleOCInterrupt) case &TIM7: return interrupt.New(stm32.IRQ_TIM7, TIM7.handleOCInterrupt) case &TIM8: return interrupt.New(stm32.IRQ_TIM8_CC, TIM8.handleOCInterrupt) } return interrupt.Interrupt{} } func (t *TIM) configurePin(channel uint8, pf PinFunction) { remap := uint32(pf.AltFunc) switch t { case &TIM1: stm32.AFIO.MAPR.ReplaceBits(remap< clock { freq = clock } // calculate the exact clock divisor (freq=clock/div -> div=clock/freq). // truncation is fine, since it produces a less-than-or-equal divisor, and // thus a greater-than-or-equal frequency. // divisors only come in consecutive powers of 2, so we can use log2 (or, // equivalently, bits.Len - 1) to convert to respective enum value. div := bits.Len32(clock/freq) - 1 // but DIV1 (2^0) is not permitted, as the least divisor is DIV2 (2^1), so // subtract 1 from the log2 value, keeping a lower bound of 0 if div < 0 { div = 0 } else if div > 0 { div-- } // finally, shift the enumerated value into position for SPI CR1 return uint32(div) << stm32.SPI_CR1_BR_Pos } // -- I2C ---------------------------------------------------------------------- type I2C struct { Bus *stm32.I2C_Type AltFuncSelector uint8 } func (i2c *I2C) configurePins(config I2CConfig) { config.SCL.ConfigureAltFunc(PinConfig{Mode: PinModeI2CSCL}, i2c.AltFuncSelector) config.SDA.ConfigureAltFunc(PinConfig{Mode: PinModeI2CSDA}, i2c.AltFuncSelector) } func (i2c *I2C) getFreqRange(config I2CConfig) uint32 { // all I2C interfaces are on APB1 clock := CPUFrequency() / 4 // convert to MHz clock /= 1000000 // must be between 2 MHz (or 4 MHz for fast mode (Fm)) and 50 MHz, inclusive var min, max uint32 = 2, 50 if config.Frequency > 100000 { min = 4 // fast mode (Fm) } if clock < min { clock = min } else if clock > max { clock = max } return clock << stm32.I2C_CR2_FREQ_Pos } func (i2c *I2C) getRiseTime(config I2CConfig) uint32 { // These bits must be programmed with the maximum SCL rise time given in the // I2C bus specification, incremented by 1. // For instance: in Sm mode, the maximum allowed SCL rise time is 1000 ns. // If, in the I2C_CR2 register, the value of FREQ[5:0] bits is equal to 0x08 // and PCLK1 = 125 ns, therefore the TRISE[5:0] bits must be programmed with // 09h (1000 ns / 125 ns = 8 + 1) freqRange := i2c.getFreqRange(config) if config.Frequency > 100000 { // fast mode (Fm) adjustment freqRange *= 300 freqRange /= 1000 } return (freqRange + 1) << stm32.I2C_TRISE_TRISE_Pos } func (i2c *I2C) getSpeed(config I2CConfig) uint32 { ccr := func(pclk uint32, freq uint32, coeff uint32) uint32 { return (((pclk - 1) / (freq * coeff)) + 1) & stm32.I2C_CCR_CCR_Msk } sm := func(pclk uint32, freq uint32) uint32 { // standard mode (Sm) if s := ccr(pclk, freq, 2); s < 4 { return 4 } else { return s } } fm := func(pclk uint32, freq uint32, duty uint8) uint32 { // fast mode (Fm) if duty == DutyCycle2 { return ccr(pclk, freq, 3) } else { return ccr(pclk, freq, 25) | stm32.I2C_CCR_DUTY } } // all I2C interfaces are on APB1 clock := CPUFrequency() / 4 if config.Frequency <= 100000 { return sm(clock, config.Frequency) } else { s := fm(clock, config.Frequency, config.DutyCycle) if (s & stm32.I2C_CCR_CCR_Msk) == 0 { return 1 } else { return s | stm32.I2C_CCR_F_S } } } //---------- Flash related code // the block size actually depends on the sector. // TODO: handle this correctly for sectors > 3 const eraseBlockSizeValue = 16384 // see RM0090 page 75 func sectorNumber(address uintptr) uint32 { switch { // 0x0800 0000 - 0x0800 3FFF case address >= 0x08000000 && address <= 0x08003FFF: return 0 // 0x0800 4000 - 0x0800 7FFF case address >= 0x08004000 && address <= 0x08007FFF: return 1 // 0x0800 8000 - 0x0800 BFFF case address >= 0x08008000 && address <= 0x0800BFFF: return 2 // 0x0800 C000 - 0x0800 FFFF case address >= 0x0800C000 && address <= 0x0800FFFF: return 3 // 0x0801 0000 - 0x0801 FFFF case address >= 0x08010000 && address <= 0x0801FFFF: return 4 // 0x0802 0000 - 0x0803 FFFF case address >= 0x08020000 && address <= 0x0803FFFF: return 5 // 0x0804 0000 - 0x0805 FFFF case address >= 0x08040000 && address <= 0x0805FFFF: return 6 case address >= 0x08060000 && address <= 0x0807FFFF: return 7 case address >= 0x08080000 && address <= 0x0809FFFF: return 8 case address >= 0x080A0000 && address <= 0x080BFFFF: return 9 case address >= 0x080C0000 && address <= 0x080DFFFF: return 10 case address >= 0x080E0000 && address <= 0x080FFFFF: return 11 default: return 0 } } // calculate sector number from address // var sector uint32 = sectorNumber(address) // see RM0090 page 85 // eraseBlock at the passed in block number func eraseBlock(block uint32) error { waitUntilFlashDone() // clear any previous errors stm32.FLASH.SR.SetBits(0xF0) // set SER bit stm32.FLASH.SetCR_SER(1) defer stm32.FLASH.SetCR_SER(0) // set the block (aka sector) to be erased stm32.FLASH.SetCR_SNB(block) defer stm32.FLASH.SetCR_SNB(0) // start the page erase stm32.FLASH.SetCR_STRT(1) waitUntilFlashDone() if err := checkError(); err != nil { return err } return nil } const writeBlockSize = 2 // see RM0090 page 86 // must write data in word-length func writeFlashData(address uintptr, data []byte) (int, error) { if len(data)%writeBlockSize != 0 { return 0, errFlashInvalidWriteLength } waitUntilFlashDone() // clear any previous errors stm32.FLASH.SR.SetBits(0xF0) // set parallelism to x32 stm32.FLASH.SetCR_PSIZE(2) for i := 0; i < len(data); i += writeBlockSize { // start write operation stm32.FLASH.SetCR_PG(1) *(*uint16)(unsafe.Pointer(address)) = binary.LittleEndian.Uint16(data[i : i+writeBlockSize]) waitUntilFlashDone() if err := checkError(); err != nil { return i, err } // end write operation stm32.FLASH.SetCR_PG(0) } return len(data), nil } func waitUntilFlashDone() { for stm32.FLASH.GetSR_BSY() != 0 { } } var ( errFlashPGS = errors.New("errFlashPGS") errFlashPGP = errors.New("errFlashPGP") errFlashPGA = errors.New("errFlashPGA") errFlashWRP = errors.New("errFlashWRP") ) func checkError() error { switch { case stm32.FLASH.GetSR_PGSERR() != 0: return errFlashPGS case stm32.FLASH.GetSR_PGPERR() != 0: return errFlashPGP case stm32.FLASH.GetSR_PGAERR() != 0: return errFlashPGA case stm32.FLASH.GetSR_WRPERR() != 0: return errFlashWRP } return nil } ================================================ FILE: src/machine/machine_stm32f40x.go ================================================ //go:build stm32f4 && (stm32f405 || stm32f407) package machine func CPUFrequency() uint32 { return 168000000 } // Internal use: configured speed of the APB1 and APB2 timers, this should be kept // in sync with any changes to runtime package which configures the oscillators // and clock frequencies const APB1_TIM_FREQ = 42000000 * 2 const APB2_TIM_FREQ = 84000000 * 2 ================================================ FILE: src/machine/machine_stm32f469.go ================================================ //go:build stm32f4 && stm32f469 package machine func CPUFrequency() uint32 { return 180000000 } // Internal use: configured speed of the APB1 and APB2 timers, this should be kept // in sync with any changes to runtime package which configures the oscillators // and clock frequencies const APB1_TIM_FREQ = 45000000 * 2 const APB2_TIM_FREQ = 90000000 * 2 ================================================ FILE: src/machine/machine_stm32f7.go ================================================ //go:build stm32f7 package machine // Peripheral abstraction layer for the stm32f4 import ( "device/stm32" "runtime/interrupt" "runtime/volatile" "unsafe" ) var deviceIDAddr = []uintptr{0x1FF0F420, 0x1FF0F424, 0x1FF0F428} // Alternative peripheral pin functions const ( AF0_SYSTEM = 0 AF1_TIM1_2 = 1 AF2_TIM3_4_5 = 2 AF3_TIM8_9_10_11_LPTIM1 = 3 AF4_I2C1_2_3_USART1 = 4 AF5_SPI1_2_3_4_5_I2S1_2_3 = 5 AF6_SPI2_3_I2S2_3_SAI1_UART4 = 6 AF7_SPI2_3_I2S2_3_USART1_2_3_UART5 = 7 AF8_SAI2_USART6_UART4_5_7_8_OTG1_FS = 8 AF9_CAN1_TIM12_13_14_QUADSPI_FMC_OTG2_HS = 9 AF10_SAI2_QUADSPI_SDMMC2_OTG2_HS_OTG1_FS = 10 AF11_SDMMC2 = 11 AF12_UART7_FMC_SDMMC1_OTG2_FS = 12 AF15_EVENTOUT = 15 ) const ( PA0 = portA + 0 PA1 = portA + 1 PA2 = portA + 2 PA3 = portA + 3 PA4 = portA + 4 PA5 = portA + 5 PA6 = portA + 6 PA7 = portA + 7 PA8 = portA + 8 PA9 = portA + 9 PA10 = portA + 10 PA11 = portA + 11 PA12 = portA + 12 PA13 = portA + 13 PA14 = portA + 14 PA15 = portA + 15 PB0 = portB + 0 PB1 = portB + 1 PB2 = portB + 2 PB3 = portB + 3 PB4 = portB + 4 PB5 = portB + 5 PB6 = portB + 6 PB7 = portB + 7 PB8 = portB + 8 PB9 = portB + 9 PB10 = portB + 10 PB11 = portB + 11 PB12 = portB + 12 PB13 = portB + 13 PB14 = portB + 14 PB15 = portB + 15 PC0 = portC + 0 PC1 = portC + 1 PC2 = portC + 2 PC3 = portC + 3 PC4 = portC + 4 PC5 = portC + 5 PC6 = portC + 6 PC7 = portC + 7 PC8 = portC + 8 PC9 = portC + 9 PC10 = portC + 10 PC11 = portC + 11 PC12 = portC + 12 PC13 = portC + 13 PC14 = portC + 14 PC15 = portC + 15 PD0 = portD + 0 PD1 = portD + 1 PD2 = portD + 2 PD3 = portD + 3 PD4 = portD + 4 PD5 = portD + 5 PD6 = portD + 6 PD7 = portD + 7 PD8 = portD + 8 PD9 = portD + 9 PD10 = portD + 10 PD11 = portD + 11 PD12 = portD + 12 PD13 = portD + 13 PD14 = portD + 14 PD15 = portD + 15 PE0 = portE + 0 PE1 = portE + 1 PE2 = portE + 2 PE3 = portE + 3 PE4 = portE + 4 PE5 = portE + 5 PE6 = portE + 6 PE7 = portE + 7 PE8 = portE + 8 PE9 = portE + 9 PE10 = portE + 10 PE11 = portE + 11 PE12 = portE + 12 PE13 = portE + 13 PE14 = portE + 14 PE15 = portE + 15 PF0 = portF + 0 PF1 = portF + 1 PF2 = portF + 2 PF3 = portF + 3 PF4 = portF + 4 PF5 = portF + 5 PF6 = portF + 6 PF7 = portF + 7 PF8 = portF + 8 PF9 = portF + 9 PF10 = portF + 10 PF11 = portF + 11 PF12 = portF + 12 PF13 = portF + 13 PF14 = portF + 14 PF15 = portF + 15 PG0 = portG + 0 PG1 = portG + 1 PG2 = portG + 2 PG3 = portG + 3 PG4 = portG + 4 PG5 = portG + 5 PG6 = portG + 6 PG7 = portG + 7 PG8 = portG + 8 PG9 = portG + 9 PG10 = portG + 10 PG11 = portG + 11 PG12 = portG + 12 PG13 = portG + 13 PG14 = portG + 14 PG15 = portG + 15 PH0 = portH + 0 PH1 = portH + 1 PH2 = portH + 2 PH3 = portH + 3 PH4 = portH + 4 PH5 = portH + 5 PH6 = portH + 6 PH7 = portH + 7 PH8 = portH + 8 PH9 = portH + 9 PH10 = portH + 10 PH11 = portH + 11 PH12 = portH + 12 PH13 = portH + 13 PH14 = portH + 14 PH15 = portH + 15 PI0 = portI + 0 PI1 = portI + 1 PI2 = portI + 2 PI3 = portI + 3 PI4 = portI + 4 PI5 = portI + 5 PI6 = portI + 6 PI7 = portI + 7 PI8 = portI + 8 PI9 = portI + 9 PI10 = portI + 10 PI11 = portI + 11 PI12 = portI + 12 PI13 = portI + 13 PI14 = portI + 14 PI15 = portI + 15 ) func (p Pin) getPort() *stm32.GPIO_Type { switch p / 16 { case 0: return stm32.GPIOA case 1: return stm32.GPIOB case 2: return stm32.GPIOC case 3: return stm32.GPIOD case 4: return stm32.GPIOE case 5: return stm32.GPIOF case 6: return stm32.GPIOG case 7: return stm32.GPIOH case 8: return stm32.GPIOI default: panic("machine: unknown port") } } // enableClock enables the clock for this desired GPIO port. func (p Pin) enableClock() { switch p / 16 { case 0: stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_GPIOAEN) case 1: stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_GPIOBEN) case 2: stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_GPIOCEN) case 3: stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_GPIODEN) case 4: stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_GPIOEEN) case 5: stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_GPIOFEN) case 6: stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_GPIOGEN) case 7: stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_GPIOHEN) case 8: stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_GPIOIEN) default: panic("machine: unknown port") } } // Enable peripheral clock func enableAltFuncClock(bus unsafe.Pointer) { switch bus { case unsafe.Pointer(stm32.DAC): // DAC interface clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_DACEN) case unsafe.Pointer(stm32.PWR): // Power interface clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_PWREN) case unsafe.Pointer(stm32.CAN1): // CAN 1 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_CAN1EN) case unsafe.Pointer(stm32.I2C3): // I2C3 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C3EN) case unsafe.Pointer(stm32.I2C2): // I2C2 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C2EN) case unsafe.Pointer(stm32.I2C1): // I2C1 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C1EN) case unsafe.Pointer(stm32.UART5): // UART5 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_UART5EN) case unsafe.Pointer(stm32.UART4): // UART4 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_UART4EN) case unsafe.Pointer(stm32.USART3): // USART3 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART3EN) case unsafe.Pointer(stm32.USART2): // USART2 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART2EN) case unsafe.Pointer(stm32.SPI3): // SPI3 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_SPI3EN) case unsafe.Pointer(stm32.SPI2): // SPI2 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_SPI2EN) case unsafe.Pointer(stm32.WWDG): // Window watchdog clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_WWDGEN) case unsafe.Pointer(stm32.TIM14): // TIM14 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM14EN) case unsafe.Pointer(stm32.TIM13): // TIM13 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM13EN) case unsafe.Pointer(stm32.TIM12): // TIM12 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM12EN) case unsafe.Pointer(stm32.TIM7): // TIM7 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM7EN) case unsafe.Pointer(stm32.TIM6): // TIM6 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM6EN) case unsafe.Pointer(stm32.TIM5): // TIM5 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM5EN) case unsafe.Pointer(stm32.TIM4): // TIM4 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM4EN) case unsafe.Pointer(stm32.TIM3): // TIM3 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN) case unsafe.Pointer(stm32.TIM2): // TIM2 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM2EN) case unsafe.Pointer(stm32.TIM11): // TIM11 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM11EN) case unsafe.Pointer(stm32.TIM10): // TIM10 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM10EN) case unsafe.Pointer(stm32.TIM9): // TIM9 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM9EN) case unsafe.Pointer(stm32.SYSCFG): // System configuration controller clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SYSCFGEN) case unsafe.Pointer(stm32.SPI1): // SPI1 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SPI1EN) case unsafe.Pointer(stm32.ADC3): // ADC3 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_ADC3EN) case unsafe.Pointer(stm32.ADC2): // ADC2 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_ADC2EN) case unsafe.Pointer(stm32.ADC1): // ADC1 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_ADC1EN) case unsafe.Pointer(stm32.USART6): // USART6 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART6EN) case unsafe.Pointer(stm32.USART1): // USART1 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN) case unsafe.Pointer(stm32.TIM8): // TIM8 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM8EN) case unsafe.Pointer(stm32.TIM1): // TIM1 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM1EN) } } func (p Pin) registerInterrupt() interrupt.Interrupt { pin := uint8(p) % 16 switch pin { case 0: return interrupt.New(stm32.IRQ_EXTI0, func(interrupt.Interrupt) { handlePinInterrupt(0) }) case 1: return interrupt.New(stm32.IRQ_EXTI1, func(interrupt.Interrupt) { handlePinInterrupt(1) }) case 2: return interrupt.New(stm32.IRQ_EXTI2, func(interrupt.Interrupt) { handlePinInterrupt(2) }) case 3: return interrupt.New(stm32.IRQ_EXTI3, func(interrupt.Interrupt) { handlePinInterrupt(3) }) case 4: return interrupt.New(stm32.IRQ_EXTI4, func(interrupt.Interrupt) { handlePinInterrupt(4) }) case 5: return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(5) }) case 6: return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(6) }) case 7: return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(7) }) case 8: return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(8) }) case 9: return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(9) }) case 10: return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(10) }) case 11: return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(11) }) case 12: return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(12) }) case 13: return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(13) }) case 14: return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(14) }) case 15: return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(15) }) } return interrupt.Interrupt{} } //---------- Timer related code var ( TIM1 = TIM{ EnableRegister: &stm32.RCC.APB2ENR, EnableFlag: stm32.RCC_APB2ENR_TIM1EN, Device: stm32.TIM1, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{ {PA8, AF1_TIM1_2}, {PE9, AF1_TIM1_2}, }}, TimerChannel{Pins: []PinFunction{ {PA9, AF1_TIM1_2}, {PE11, AF1_TIM1_2}, }}, TimerChannel{Pins: []PinFunction{ {PA10, AF1_TIM1_2}, {PE13, AF1_TIM1_2}, }}, TimerChannel{Pins: []PinFunction{ {PA11, AF1_TIM1_2}, {PE14, AF1_TIM1_2}, }}, }, busFreq: APB2_TIM_FREQ, } TIM2 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM2EN, Device: stm32.TIM2, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{ {PA0, AF1_TIM1_2}, {PA5, AF1_TIM1_2}, {PA15, AF1_TIM1_2}, }}, TimerChannel{Pins: []PinFunction{ {PA1, AF1_TIM1_2}, {PB3, AF1_TIM1_2}, }}, TimerChannel{Pins: []PinFunction{ {PA2, AF1_TIM1_2}, {PB10, AF1_TIM1_2}, }}, TimerChannel{Pins: []PinFunction{ {PA3, AF1_TIM1_2}, {PB11, AF1_TIM1_2}, }}, }, busFreq: APB1_TIM_FREQ, } TIM3 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM3EN, Device: stm32.TIM3, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{ {PA6, AF2_TIM3_4_5}, {PB4, AF2_TIM3_4_5}, {PC6, AF2_TIM3_4_5}, }}, TimerChannel{Pins: []PinFunction{ {PA7, AF2_TIM3_4_5}, {PB5, AF2_TIM3_4_5}, {PC7, AF2_TIM3_4_5}, }}, TimerChannel{Pins: []PinFunction{ {PB0, AF2_TIM3_4_5}, {PC8, AF2_TIM3_4_5}, }}, TimerChannel{Pins: []PinFunction{ {PB1, AF2_TIM3_4_5}, {PC9, AF2_TIM3_4_5}, }}, }, busFreq: APB1_TIM_FREQ, } TIM4 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM4EN, Device: stm32.TIM4, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{ {PB6, AF2_TIM3_4_5}, {PD12, AF2_TIM3_4_5}, }}, TimerChannel{Pins: []PinFunction{ {PB7, AF2_TIM3_4_5}, {PD13, AF2_TIM3_4_5}, }}, TimerChannel{Pins: []PinFunction{ {PB8, AF2_TIM3_4_5}, {PD14, AF2_TIM3_4_5}, }}, TimerChannel{Pins: []PinFunction{ {PB9, AF2_TIM3_4_5}, {PD15, AF2_TIM3_4_5}, }}, }, busFreq: APB1_TIM_FREQ, } TIM5 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM5EN, Device: stm32.TIM5, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{ {PA0, AF2_TIM3_4_5}, {PH10, AF2_TIM3_4_5}, }}, TimerChannel{Pins: []PinFunction{ {PA1, AF2_TIM3_4_5}, {PH11, AF2_TIM3_4_5}, }}, TimerChannel{Pins: []PinFunction{ {PA2, AF2_TIM3_4_5}, {PH12, AF2_TIM3_4_5}, }}, TimerChannel{Pins: []PinFunction{ {PA3, AF2_TIM3_4_5}, {PI0, AF2_TIM3_4_5}, }}, }, busFreq: APB1_TIM_FREQ, } TIM6 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM6EN, Device: stm32.TIM6, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB1_TIM_FREQ, } TIM7 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM7EN, Device: stm32.TIM7, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB1_TIM_FREQ, } TIM8 = TIM{ EnableRegister: &stm32.RCC.APB2ENR, EnableFlag: stm32.RCC_APB2ENR_TIM8EN, Device: stm32.TIM8, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{ {PC6, AF3_TIM8_9_10_11_LPTIM1}, {PI5, AF3_TIM8_9_10_11_LPTIM1}, }}, TimerChannel{Pins: []PinFunction{ {PC7, AF3_TIM8_9_10_11_LPTIM1}, {PI6, AF3_TIM8_9_10_11_LPTIM1}, }}, TimerChannel{Pins: []PinFunction{ {PC8, AF3_TIM8_9_10_11_LPTIM1}, {PI7, AF3_TIM8_9_10_11_LPTIM1}, }}, TimerChannel{Pins: []PinFunction{ {PC9, AF3_TIM8_9_10_11_LPTIM1}, {PI2, AF3_TIM8_9_10_11_LPTIM1}, }}, }, busFreq: APB2_TIM_FREQ, } TIM9 = TIM{ EnableRegister: &stm32.RCC.APB2ENR, EnableFlag: stm32.RCC_APB2ENR_TIM9EN, Device: stm32.TIM9, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{ {PA2, AF3_TIM8_9_10_11_LPTIM1}, {PE5, AF3_TIM8_9_10_11_LPTIM1}, }}, TimerChannel{Pins: []PinFunction{ {PA3, AF3_TIM8_9_10_11_LPTIM1}, {PE6, AF3_TIM8_9_10_11_LPTIM1}, }}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB2_TIM_FREQ, } TIM10 = TIM{ EnableRegister: &stm32.RCC.APB2ENR, EnableFlag: stm32.RCC_APB2ENR_TIM10EN, Device: stm32.TIM10, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{ {PB8, AF3_TIM8_9_10_11_LPTIM1}, {PF6, AF3_TIM8_9_10_11_LPTIM1}, }}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB2_TIM_FREQ, } TIM11 = TIM{ EnableRegister: &stm32.RCC.APB2ENR, EnableFlag: stm32.RCC_APB2ENR_TIM11EN, Device: stm32.TIM11, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{ {PB9, AF3_TIM8_9_10_11_LPTIM1}, {PF7, AF3_TIM8_9_10_11_LPTIM1}, }}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB2_TIM_FREQ, } TIM12 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM12EN, Device: stm32.TIM12, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{ {PB14, AF9_CAN1_TIM12_13_14_QUADSPI_FMC_OTG2_HS}, {PH6, AF9_CAN1_TIM12_13_14_QUADSPI_FMC_OTG2_HS}, }}, TimerChannel{Pins: []PinFunction{ {PB15, AF9_CAN1_TIM12_13_14_QUADSPI_FMC_OTG2_HS}, {PH9, AF9_CAN1_TIM12_13_14_QUADSPI_FMC_OTG2_HS}, }}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB1_TIM_FREQ, } TIM13 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM13EN, Device: stm32.TIM13, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{ {PA6, AF9_CAN1_TIM12_13_14_QUADSPI_FMC_OTG2_HS}, {PF8, AF9_CAN1_TIM12_13_14_QUADSPI_FMC_OTG2_HS}, }}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB1_TIM_FREQ, } TIM14 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM14EN, Device: stm32.TIM14, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{ {PA7, AF9_CAN1_TIM12_13_14_QUADSPI_FMC_OTG2_HS}, {PF9, AF9_CAN1_TIM12_13_14_QUADSPI_FMC_OTG2_HS}, }}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB1_TIM_FREQ, } ) func (t *TIM) registerUPInterrupt() interrupt.Interrupt { switch t { case &TIM1: return interrupt.New(stm32.IRQ_TIM1_UP_TIM10, TIM1.handleUPInterrupt) case &TIM2: return interrupt.New(stm32.IRQ_TIM2, TIM2.handleUPInterrupt) case &TIM3: return interrupt.New(stm32.IRQ_TIM3, TIM3.handleUPInterrupt) case &TIM4: return interrupt.New(stm32.IRQ_TIM4, TIM4.handleUPInterrupt) case &TIM5: return interrupt.New(stm32.IRQ_TIM5, TIM5.handleUPInterrupt) case &TIM6: return interrupt.New(stm32.IRQ_TIM6_DAC, TIM6.handleUPInterrupt) case &TIM7: return interrupt.New(stm32.IRQ_TIM7, TIM7.handleUPInterrupt) case &TIM8: return interrupt.New(stm32.IRQ_TIM8_UP_TIM13, TIM8.handleUPInterrupt) case &TIM9: return interrupt.New(stm32.IRQ_TIM1_BRK_TIM9, TIM9.handleUPInterrupt) case &TIM10: return interrupt.New(stm32.IRQ_TIM1_UP_TIM10, TIM10.handleUPInterrupt) case &TIM11: return interrupt.New(stm32.IRQ_TIM1_TRG_COM_TIM11, TIM11.handleUPInterrupt) case &TIM12: return interrupt.New(stm32.IRQ_TIM8_BRK_TIM12, TIM12.handleUPInterrupt) case &TIM13: return interrupt.New(stm32.IRQ_TIM8_UP_TIM13, TIM13.handleUPInterrupt) case &TIM14: return interrupt.New(stm32.IRQ_TIM8_TRG_COM_TIM14, TIM14.handleUPInterrupt) } return interrupt.Interrupt{} } func (t *TIM) registerOCInterrupt() interrupt.Interrupt { switch t { case &TIM1: return interrupt.New(stm32.IRQ_TIM1_CC, TIM1.handleUPInterrupt) case &TIM2: return interrupt.New(stm32.IRQ_TIM2, TIM2.handleOCInterrupt) case &TIM3: return interrupt.New(stm32.IRQ_TIM3, TIM3.handleOCInterrupt) case &TIM4: return interrupt.New(stm32.IRQ_TIM4, TIM4.handleOCInterrupt) case &TIM5: return interrupt.New(stm32.IRQ_TIM5, TIM5.handleOCInterrupt) case &TIM6: return interrupt.New(stm32.IRQ_TIM6_DAC, TIM6.handleOCInterrupt) case &TIM7: return interrupt.New(stm32.IRQ_TIM7, TIM7.handleOCInterrupt) case &TIM8: return interrupt.New(stm32.IRQ_TIM8_CC, TIM8.handleOCInterrupt) case &TIM9: return interrupt.New(stm32.IRQ_TIM1_BRK_TIM9, TIM9.handleOCInterrupt) case &TIM10: return interrupt.New(stm32.IRQ_TIM1_UP_TIM10, TIM10.handleOCInterrupt) case &TIM11: return interrupt.New(stm32.IRQ_TIM1_TRG_COM_TIM11, TIM11.handleOCInterrupt) case &TIM12: return interrupt.New(stm32.IRQ_TIM8_BRK_TIM12, TIM12.handleOCInterrupt) case &TIM13: return interrupt.New(stm32.IRQ_TIM8_UP_TIM13, TIM13.handleOCInterrupt) case &TIM14: return interrupt.New(stm32.IRQ_TIM8_TRG_COM_TIM14, TIM14.handleOCInterrupt) } return interrupt.Interrupt{} } func (t *TIM) enableMainOutput() { t.Device.BDTR.SetBits(stm32.TIM_BDTR_MOE) } type arrtype = uint32 type arrRegType = volatile.Register32 const ( ARR_MAX = 0x10000 PSC_MAX = 0x10000 ) func initRNG() { stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_RNGEN) stm32.RNG.CR.SetBits(stm32.RNG_CR_RNGEN) } ================================================ FILE: src/machine/machine_stm32f7x2.go ================================================ //go:build stm32f7x2 package machine // Peripheral abstraction layer for the stm32f407 import ( "device/stm32" ) func CPUFrequency() uint32 { return 216000000 } // Internal use: configured speed of the APB1 and APB2 timers, this should be kept // in sync with any changes to runtime package which configures the oscillators // and clock frequencies const APB1_TIM_FREQ = 54e6 // 54MHz const APB2_TIM_FREQ = 216e6 // 216MHz //---------- UART related code // Configure the UART. func (uart *UART) configurePins(config UARTConfig) { // enable the alternate functions on the TX and RX pins config.TX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTTX}, uart.TxAltFuncSelector) config.RX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTRX}, uart.RxAltFuncSelector) } // UART baudrate calc based on the bus and clockspeed // NOTE: keep this in sync with the runtime/runtime_stm32f7x2.go clock init code func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 { var clock uint32 switch uart.Bus { case stm32.USART1, stm32.USART6: clock = CPUFrequency() / 2 // APB2 Frequency case stm32.USART2, stm32.USART3, stm32.UART4, stm32.UART5: clock = CPUFrequency() / 8 // APB1 Frequency } return clock / baudRate } // Register names vary by ST processor, these are for STM F7x2 func (uart *UART) setRegisters() { uart.rxReg = &uart.Bus.RDR uart.txReg = &uart.Bus.TDR uart.statusReg = &uart.Bus.ISR uart.txEmptyFlag = stm32.USART_ISR_TXE } //---------- I2C related code // Gets the value for TIMINGR register func (i2c *I2C) getFreqRange(br uint32) uint32 { // This is a 'magic' value calculated by STM32CubeMX // for 27MHz PCLK1 (216MHz CPU Freq / 8). // TODO: Do calculations based on PCLK1 switch br { case 10 * KHz: return 0x5010C0FF case 100 * KHz: return 0x00606A9B case 400 * KHz: return 0x00201625 case 500 * KHz: return 0x00100429 default: return 0 } } ================================================ FILE: src/machine/machine_stm32l0.go ================================================ //go:build stm32l0 package machine // Peripheral abstraction layer for the stm32l0 import ( "device/stm32" "runtime/interrupt" ) func CPUFrequency() uint32 { return 32000000 } var deviceIDAddr = []uintptr{0x1FF80050, 0x1FF80054, 0x1FF80058} // Internal use: configured speed of the APB1 and APB2 timers, this should be kept // in sync with any changes to runtime package which configures the oscillators // and clock frequencies const APB1_TIM_FREQ = 32e6 // 32MHz const APB2_TIM_FREQ = 32e6 // 32MHz const ( PA0 = portA + 0 PA1 = portA + 1 PA2 = portA + 2 PA3 = portA + 3 PA4 = portA + 4 PA5 = portA + 5 PA6 = portA + 6 PA7 = portA + 7 PA8 = portA + 8 PA9 = portA + 9 PA10 = portA + 10 PA11 = portA + 11 PA12 = portA + 12 PA13 = portA + 13 PA14 = portA + 14 PA15 = portA + 15 PB0 = portB + 0 PB1 = portB + 1 PB2 = portB + 2 PB3 = portB + 3 PB4 = portB + 4 PB5 = portB + 5 PB6 = portB + 6 PB7 = portB + 7 PB8 = portB + 8 PB9 = portB + 9 PB10 = portB + 10 PB11 = portB + 11 PB12 = portB + 12 PB13 = portB + 13 PB14 = portB + 14 PB15 = portB + 15 PC0 = portC + 0 PC1 = portC + 1 PC2 = portC + 2 PC3 = portC + 3 PC4 = portC + 4 PC5 = portC + 5 PC6 = portC + 6 PC7 = portC + 7 PC8 = portC + 8 PC9 = portC + 9 PC10 = portC + 10 PC11 = portC + 11 PC12 = portC + 12 PC13 = portC + 13 PC14 = portC + 14 PC15 = portC + 15 PD0 = portD + 0 PD1 = portD + 1 PD2 = portD + 2 PD3 = portD + 3 PD4 = portD + 4 PD5 = portD + 5 PD6 = portD + 6 PD7 = portD + 7 PD8 = portD + 8 PD9 = portD + 9 PD10 = portD + 10 PD11 = portD + 11 PD12 = portD + 12 PD13 = portD + 13 PD14 = portD + 14 PD15 = portD + 15 PE0 = portE + 0 PE1 = portE + 1 PE2 = portE + 2 PE3 = portE + 3 PE4 = portE + 4 PE5 = portE + 5 PE6 = portE + 6 PE7 = portE + 7 PE8 = portE + 8 PE9 = portE + 9 PE10 = portE + 10 PE11 = portE + 11 PE12 = portE + 12 PE13 = portE + 13 PE14 = portE + 14 PE15 = portE + 15 PH0 = portH + 0 PH1 = portH + 1 ) func (p Pin) getPort() *stm32.GPIO_Type { switch p / 16 { case 0: return stm32.GPIOA case 1: return stm32.GPIOB case 2: return stm32.GPIOC case 3: return stm32.GPIOD case 4: return stm32.GPIOE case 7: return stm32.GPIOH default: panic("machine: unknown port") } } // enableClock enables the clock for this desired GPIO port. func (p Pin) enableClock() { switch p / 16 { case 0: stm32.RCC.IOPENR.SetBits(stm32.RCC_IOPENR_IOPAEN) case 1: stm32.RCC.IOPENR.SetBits(stm32.RCC_IOPENR_IOPBEN) case 2: stm32.RCC.IOPENR.SetBits(stm32.RCC_IOPENR_IOPCEN) case 3: stm32.RCC.IOPENR.SetBits(stm32.RCC_IOPENR_IOPDEN) case 4: stm32.RCC.IOPENR.SetBits(stm32.RCC_IOPENR_IOPEEN) case 7: stm32.RCC.IOPENR.SetBits(stm32.RCC_IOPENR_IOPHEN) default: panic("machine: unknown port") } } func (p Pin) registerInterrupt() interrupt.Interrupt { pin := uint8(p) % 16 switch pin { case 0: return interrupt.New(stm32.IRQ_EXTI0_1, func(interrupt.Interrupt) { handlePinInterrupt(0) }) case 1: return interrupt.New(stm32.IRQ_EXTI0_1, func(interrupt.Interrupt) { handlePinInterrupt(1) }) case 2: return interrupt.New(stm32.IRQ_EXTI2_3, func(interrupt.Interrupt) { handlePinInterrupt(2) }) case 3: return interrupt.New(stm32.IRQ_EXTI2_3, func(interrupt.Interrupt) { handlePinInterrupt(3) }) case 4: return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(4) }) case 5: return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(5) }) case 6: return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(6) }) case 7: return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(7) }) case 8: return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(8) }) case 9: return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(9) }) case 10: return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(10) }) case 11: return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(11) }) case 12: return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(12) }) case 13: return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(13) }) case 14: return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(14) }) case 15: return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(15) }) } return interrupt.Interrupt{} } //---------- UART related types and code // Configure the UART. func (uart *UART) configurePins(config UARTConfig) { // enable the alternate functions on the TX and RX pins config.TX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTTX}, uart.TxAltFuncSelector) config.RX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTRX}, uart.RxAltFuncSelector) } // UART baudrate calc based on the bus and clockspeed func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 { var clock, rate uint32 switch uart.Bus { case stm32.LPUART1: clock = CPUFrequency() / 2 // APB1 Frequency rate = uint32((256 * clock) / baudRate) case stm32.USART1: clock = CPUFrequency() / 2 // APB2 Frequency rate = uint32(clock / baudRate) case stm32.USART2: clock = CPUFrequency() / 2 // APB1 Frequency rate = uint32(clock / baudRate) } return rate } // Register names vary by ST processor, these are for STM L0 family func (uart *UART) setRegisters() { uart.rxReg = &uart.Bus.RDR uart.txReg = &uart.Bus.TDR uart.statusReg = &uart.Bus.ISR uart.txEmptyFlag = stm32.USART_ISR_TXE } //---------- SPI related types and code // SPI on the STM32Fxxx using MODER / alternate function pins type SPI struct { Bus *stm32.SPI_Type AltFuncSelector uint8 } func (spi *SPI) config8Bits() { // no-op on this series } // Set baud rate for SPI func (spi *SPI) getBaudRate(config SPIConfig) uint32 { var conf uint32 localFrequency := config.Frequency // Default if config.Frequency == 0 { config.Frequency = 4e6 } if spi.Bus != stm32.SPI1 { // Assume it's SPI2 or SPI3 on APB1 at 1/2 the clock frequency of APB2, so // we want to pretend to request 2x the baudrate asked for localFrequency = localFrequency * 2 } // set frequency dependent on PCLK prescaler. Since these are rather weird // speeds due to the CPU frequency, pick a range up to that frequency for // clients to use more human-understandable numbers, e.g. nearest 100KHz // These are based on APB2 clock frequency (84MHz on the discovery board) // TODO: also include the MCU/APB clock setting in the equation switch { case localFrequency < 328125: conf = stm32.SPI_CR1_BR_Div256 case localFrequency < 656250: conf = stm32.SPI_CR1_BR_Div128 case localFrequency < 1312500: conf = stm32.SPI_CR1_BR_Div64 case localFrequency < 2625000: conf = stm32.SPI_CR1_BR_Div32 case localFrequency < 5250000: conf = stm32.SPI_CR1_BR_Div16 case localFrequency < 10500000: conf = stm32.SPI_CR1_BR_Div8 // NOTE: many SPI components won't operate reliably (or at all) above 10MHz // Check the datasheet of the part case localFrequency < 21000000: conf = stm32.SPI_CR1_BR_Div4 case localFrequency < 42000000: conf = stm32.SPI_CR1_BR_Div2 default: // None of the specific baudrates were selected; choose the lowest speed conf = stm32.SPI_CR1_BR_Div256 } return conf << stm32.SPI_CR1_BR_Pos } // Configure SPI pins for input output and clock func (spi *SPI) configurePins(config SPIConfig) { config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector) config.SDO.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDO}, spi.AltFuncSelector) config.SDI.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDI}, spi.AltFuncSelector) } //---------- I2C related types and code // Gets the value for TIMINGR register func (i2c I2C) getFreqRange(br uint32) uint32 { // This is a 'magic' value calculated by STM32CubeMX // for 16MHz PCLK1. // TODO: Do calculations based on PCLK1 switch br { case 10 * KHz: return 0x40003EFF case 100 * KHz: return 0x00303D5B case 400 * KHz: return 0x0010061A case 500 * KHz: return 0x00000117 default: return 0 } } ================================================ FILE: src/machine/machine_stm32l0x1.go ================================================ //go:build stm32l0x1 package machine // Peripheral abstraction layer for the stm32l0 import ( "device/stm32" "runtime/interrupt" "runtime/volatile" "unsafe" ) const ( AF0_SYSTEM_SPI1_USART2_LPTIM_TIM21 = 0 AF1_SPI1_I2C1_LPTIM = 1 AF2_LPTIM_TIM2 = 2 AF3_I2C1 = 3 AF4_I2C1_USART2_LPUART1_TIM22 = 4 AF5_TIM2_21_22 = 5 AF6_LPUART1 = 6 AF7_COMP1_2 = 7 ) // Enable peripheral clock func enableAltFuncClock(bus unsafe.Pointer) { switch bus { case unsafe.Pointer(stm32.PWR): // Power interface clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_PWREN) case unsafe.Pointer(stm32.I2C3): // I2C3 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C3EN) case unsafe.Pointer(stm32.I2C2): // I2C2 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C2EN) case unsafe.Pointer(stm32.I2C1): // I2C1 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C1EN) case unsafe.Pointer(stm32.USART5): // UART5 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART5EN) case unsafe.Pointer(stm32.USART4): // UART4 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART4EN) case unsafe.Pointer(stm32.USART2): // USART2 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART2EN) case unsafe.Pointer(stm32.SPI2): // SPI2 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_SPI2EN) case unsafe.Pointer(stm32.LPUART1): // LPUART1 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_LPUART1EN) case unsafe.Pointer(stm32.WWDG): // Window watchdog clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_WWDGEN) case unsafe.Pointer(stm32.TIM7): // TIM7 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM7EN) case unsafe.Pointer(stm32.TIM6): // TIM6 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM6EN) case unsafe.Pointer(stm32.TIM3): // TIM3 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN) case unsafe.Pointer(stm32.TIM2): // TIM2 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM2EN) case unsafe.Pointer(stm32.SYSCFG): // System configuration controller clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SYSCFGEN) case unsafe.Pointer(stm32.SPI1): // SPI1 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SPI1EN) case unsafe.Pointer(stm32.ADC): // ADC clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_ADCEN) case unsafe.Pointer(stm32.USART1): // USART1 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN) } } //---------- Timer related code var ( TIM2 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM2EN, Device: stm32.TIM2, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{{PA0, AF2_LPTIM_TIM2}, {PA5, AF5_TIM2_21_22}, {PA8, AF5_TIM2_21_22}, {PA15, AF5_TIM2_21_22}}}, TimerChannel{Pins: []PinFunction{{PA1, AF2_LPTIM_TIM2}, {PB3, AF2_LPTIM_TIM2}}}, TimerChannel{Pins: []PinFunction{{PA2, AF2_LPTIM_TIM2}, {PB0, AF5_TIM2_21_22}, {PB10, AF2_LPTIM_TIM2}}}, TimerChannel{Pins: []PinFunction{{PA3, AF2_LPTIM_TIM2}, {PB1, AF5_TIM2_21_22}, {PB11, AF2_LPTIM_TIM2}}}, }, busFreq: APB1_TIM_FREQ, } TIM3 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM3EN, Device: stm32.TIM3, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB1_TIM_FREQ, } TIM6 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM6EN, Device: stm32.TIM6, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB1_TIM_FREQ, } TIM7 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM7EN, Device: stm32.TIM7, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB1_TIM_FREQ, } TIM21 = TIM{ EnableRegister: &stm32.RCC.APB2ENR, EnableFlag: stm32.RCC_APB2ENR_TIM21EN, Device: stm32.TIM21, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB2_TIM_FREQ, } TIM22 = TIM{ EnableRegister: &stm32.RCC.APB2ENR, EnableFlag: stm32.RCC_APB2ENR_TIM22EN, Device: stm32.TIM2, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB2_TIM_FREQ, } ) func (t *TIM) registerUPInterrupt() interrupt.Interrupt { switch t { case &TIM2: return interrupt.New(stm32.IRQ_TIM2, TIM2.handleUPInterrupt) case &TIM3: return interrupt.New(stm32.IRQ_TIM3, TIM3.handleUPInterrupt) case &TIM6: return interrupt.New(stm32.IRQ_TIM6, TIM6.handleUPInterrupt) case &TIM7: return interrupt.New(stm32.IRQ_TIM7, TIM7.handleUPInterrupt) case &TIM21: return interrupt.New(stm32.IRQ_TIM21, TIM21.handleUPInterrupt) case &TIM22: return interrupt.New(stm32.IRQ_TIM22, TIM22.handleUPInterrupt) } return interrupt.Interrupt{} } func (t *TIM) registerOCInterrupt() interrupt.Interrupt { switch t { case &TIM2: return interrupt.New(stm32.IRQ_TIM2, TIM2.handleOCInterrupt) case &TIM3: return interrupt.New(stm32.IRQ_TIM3, TIM3.handleOCInterrupt) case &TIM6: return interrupt.New(stm32.IRQ_TIM6, TIM6.handleOCInterrupt) case &TIM7: return interrupt.New(stm32.IRQ_TIM7, TIM7.handleOCInterrupt) case &TIM21: return interrupt.New(stm32.IRQ_TIM21, TIM21.handleOCInterrupt) case &TIM22: return interrupt.New(stm32.IRQ_TIM22, TIM22.handleOCInterrupt) } return interrupt.Interrupt{} } func (t *TIM) enableMainOutput() { // nothing to do - no BDTR register } type arrtype = uint16 type arrRegType = volatile.Register16 const ( ARR_MAX = 0x10000 PSC_MAX = 0x10000 ) ================================================ FILE: src/machine/machine_stm32l0x2.go ================================================ //go:build stm32l0x2 package machine // Peripheral abstraction layer for the stm32l0 import ( "device/stm32" "runtime/interrupt" "runtime/volatile" "unsafe" ) const ( AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22 = 0 AF1_SPI1_2_I2S2_I2C1_TIM2_21 = 1 AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3 = 2 AF3_I2C1_TSC = 3 AF4_I2C1_USART1_2_LPUART1_TIM3_22 = 4 AF5_SPI2_I2S2_I2C2_USART1_TIM2_21_22 = 5 AF6_I2C1_2_LPUART1_USART4_5_TIM21 = 6 AF7_I2C3_LPUART1_COMP1_2_TIM3 = 7 ) // Enable peripheral clock func enableAltFuncClock(bus unsafe.Pointer) { switch bus { case unsafe.Pointer(stm32.DAC): // DAC interface clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_DACEN) case unsafe.Pointer(stm32.PWR): // Power interface clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_PWREN) case unsafe.Pointer(stm32.I2C3): // I2C3 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C3EN) case unsafe.Pointer(stm32.I2C2): // I2C2 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C2EN) case unsafe.Pointer(stm32.I2C1): // I2C1 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C1EN) case unsafe.Pointer(stm32.USART5): // UART5 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART5EN) case unsafe.Pointer(stm32.USART4): // UART4 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART4EN) case unsafe.Pointer(stm32.USART2): // USART2 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART2EN) case unsafe.Pointer(stm32.SPI2): // SPI2 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_SPI2EN) case unsafe.Pointer(stm32.LPUART1): // LPUART1 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_LPUART1EN) case unsafe.Pointer(stm32.WWDG): // Window watchdog clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_WWDGEN) case unsafe.Pointer(stm32.TIM7): // TIM7 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM7EN) case unsafe.Pointer(stm32.TIM6): // TIM6 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM6EN) case unsafe.Pointer(stm32.TIM3): // TIM3 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN) case unsafe.Pointer(stm32.TIM2): // TIM2 clock enable stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM2EN) case unsafe.Pointer(stm32.SYSCFG): // System configuration controller clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SYSCFGEN) case unsafe.Pointer(stm32.SPI1): // SPI1 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SPI1EN) case unsafe.Pointer(stm32.ADC): // ADC clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_ADCEN) case unsafe.Pointer(stm32.USART1): // USART1 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN) } } //---------- Timer related code var ( TIM2 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM2EN, Device: stm32.TIM2, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{ {PA0, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, {PA5, AF5_SPI2_I2S2_I2C2_USART1_TIM2_21_22}, {PA15, AF5_SPI2_I2S2_I2C2_USART1_TIM2_21_22}, {PE9, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, }}, TimerChannel{Pins: []PinFunction{ {PA1, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, {PB3, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, {PE10, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, }}, TimerChannel{Pins: []PinFunction{ {PA2, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, {PB10, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, {PE11, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, }}, TimerChannel{Pins: []PinFunction{ {PA3, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, {PB11, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, {PE12, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, }}, }, busFreq: APB1_TIM_FREQ, } TIM3 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM3EN, Device: stm32.TIM3, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{ {PA6, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, {PB4, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, {PC6, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, {PE3, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, }}, TimerChannel{Pins: []PinFunction{ {PA7, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, {PB5, AF4_I2C1_USART1_2_LPUART1_TIM3_22}, {PC7, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, {PE4, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, }}, TimerChannel{Pins: []PinFunction{ {PB0, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, {PC8, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, {PE5, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, }}, TimerChannel{Pins: []PinFunction{ {PB1, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, {PC9, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, {PE6, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, }}, }, busFreq: APB1_TIM_FREQ, } TIM6 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM6EN, Device: stm32.TIM6, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB1_TIM_FREQ, } TIM7 = TIM{ EnableRegister: &stm32.RCC.APB1ENR, EnableFlag: stm32.RCC_APB1ENR_TIM7EN, Device: stm32.TIM7, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB1_TIM_FREQ, } TIM21 = TIM{ EnableRegister: &stm32.RCC.APB2ENR, EnableFlag: stm32.RCC_APB2ENR_TIM21EN, Device: stm32.TIM21, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{ {PA2, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, {PB13, AF6_I2C1_2_LPUART1_USART4_5_TIM21}, {PD0, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, {PE5, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, }}, TimerChannel{Pins: []PinFunction{ {PA3, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, {PB14, AF6_I2C1_2_LPUART1_USART4_5_TIM21}, {PD7, AF1_SPI1_2_I2S2_I2C1_TIM2_21}, {PE6, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, }}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB2_TIM_FREQ, } TIM22 = TIM{ EnableRegister: &stm32.RCC.APB2ENR, EnableFlag: stm32.RCC_APB2ENR_TIM22EN, Device: stm32.TIM2, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{ {PA6, AF5_SPI2_I2S2_I2C2_USART1_TIM2_21_22}, {PB4, AF4_I2C1_USART1_2_LPUART1_TIM3_22}, {PC6, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, {PE3, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, }}, TimerChannel{Pins: []PinFunction{ {PA7, AF5_SPI2_I2S2_I2C2_USART1_TIM2_21_22}, {PB5, AF4_I2C1_USART1_2_LPUART1_TIM3_22}, {PC7, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, {PE4, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, }}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB2_TIM_FREQ, } ) func (t *TIM) registerUPInterrupt() interrupt.Interrupt { switch t { case &TIM2: return interrupt.New(stm32.IRQ_TIM2, TIM2.handleUPInterrupt) case &TIM3: return interrupt.New(stm32.IRQ_TIM3, TIM3.handleUPInterrupt) case &TIM6: return interrupt.New(stm32.IRQ_TIM6_DAC, TIM6.handleUPInterrupt) case &TIM7: return interrupt.New(stm32.IRQ_TIM7, TIM7.handleUPInterrupt) case &TIM21: return interrupt.New(stm32.IRQ_TIM21, TIM21.handleUPInterrupt) case &TIM22: return interrupt.New(stm32.IRQ_TIM22, TIM22.handleUPInterrupt) } return interrupt.Interrupt{} } func (t *TIM) registerOCInterrupt() interrupt.Interrupt { switch t { case &TIM2: return interrupt.New(stm32.IRQ_TIM2, TIM2.handleOCInterrupt) case &TIM3: return interrupt.New(stm32.IRQ_TIM3, TIM3.handleOCInterrupt) case &TIM6: return interrupt.New(stm32.IRQ_TIM6_DAC, TIM6.handleOCInterrupt) case &TIM7: return interrupt.New(stm32.IRQ_TIM7, TIM7.handleOCInterrupt) case &TIM21: return interrupt.New(stm32.IRQ_TIM21, TIM21.handleOCInterrupt) case &TIM22: return interrupt.New(stm32.IRQ_TIM22, TIM22.handleOCInterrupt) } return interrupt.Interrupt{} } func (t *TIM) enableMainOutput() { // nothing to do - no BDTR register } type arrtype = uint16 type arrRegType = volatile.Register16 const ( ARR_MAX = 0x10000 PSC_MAX = 0x10000 ) func initRNG() { stm32.RCC.AHBENR.SetBits(stm32.RCC_AHBENR_RNGEN) stm32.RNG.CR.SetBits(stm32.RNG_CR_RNGEN) } ================================================ FILE: src/machine/machine_stm32l4.go ================================================ //go:build stm32l4 package machine import ( "device/stm32" "errors" "internal/binary" "runtime/interrupt" "runtime/volatile" "unsafe" ) // Peripheral abstraction layer for the stm32l4 var deviceIDAddr = []uintptr{0x1FFF7590, 0x1FFF7594, 0x1FFF7598} const ( AF0_SYSTEM = 0 AF1_TIM1_2_LPTIM1 = 1 AF2_TIM1_2 = 2 AF3_USART2 = 3 AF4_I2C1_2_3 = 4 AF5_SPI1_2 = 5 AF6_SPI3 = 6 AF7_USART1_2_3 = 7 AF8_LPUART1 = 8 AF9_CAN1_TSC = 9 AF10_USB_QUADSPI = 10 AF12_COMP1_2_SWPMI1 = 12 AF13_SAI1 = 13 AF14_TIM2_15_16_LPTIM2 = 14 AF15_EVENTOUT = 15 ) const ( PA0 = portA + 0 PA1 = portA + 1 PA2 = portA + 2 PA3 = portA + 3 PA4 = portA + 4 PA5 = portA + 5 PA6 = portA + 6 PA7 = portA + 7 PA8 = portA + 8 PA9 = portA + 9 PA10 = portA + 10 PA11 = portA + 11 PA12 = portA + 12 PA13 = portA + 13 PA14 = portA + 14 PA15 = portA + 15 PB0 = portB + 0 PB1 = portB + 1 PB2 = portB + 2 PB3 = portB + 3 PB4 = portB + 4 PB5 = portB + 5 PB6 = portB + 6 PB7 = portB + 7 PB8 = portB + 8 PB9 = portB + 9 PB10 = portB + 10 PB11 = portB + 11 PB12 = portB + 12 PB13 = portB + 13 PB14 = portB + 14 PB15 = portB + 15 PC0 = portC + 0 PC1 = portC + 1 PC2 = portC + 2 PC3 = portC + 3 PC4 = portC + 4 PC5 = portC + 5 PC6 = portC + 6 PC7 = portC + 7 PC8 = portC + 8 PC9 = portC + 9 PC10 = portC + 10 PC11 = portC + 11 PC12 = portC + 12 PC13 = portC + 13 PC14 = portC + 14 PC15 = portC + 15 PD0 = portD + 0 PD1 = portD + 1 PD2 = portD + 2 PD3 = portD + 3 PD4 = portD + 4 PD5 = portD + 5 PD6 = portD + 6 PD7 = portD + 7 PD8 = portD + 8 PD9 = portD + 9 PD10 = portD + 10 PD11 = portD + 11 PD12 = portD + 12 PD13 = portD + 13 PD14 = portD + 14 PD15 = portD + 15 PE0 = portE + 0 PE1 = portE + 1 PE2 = portE + 2 PE3 = portE + 3 PE4 = portE + 4 PE5 = portE + 5 PE6 = portE + 6 PE7 = portE + 7 PE8 = portE + 8 PE9 = portE + 9 PE10 = portE + 10 PE11 = portE + 11 PE12 = portE + 12 PE13 = portE + 13 PE14 = portE + 14 PE15 = portE + 15 ) // IRQs are defined here as they vary in the SVDs, but do have consistent mapping // to Timer Interrupts. const ( irq_TIM1_BRK_TIM15 = 24 irq_TIM1_UP_TIM16 = 25 irq_TIM1_TRG_COM_TIM17 = 26 irq_TIM1_CC = 27 irq_TIM2 = 28 irq_TIM3 = 29 irq_TIM4 = 30 irq_TIM5 = 50 irq_TIM6 = 54 irq_TIM7 = 55 irq_TIM8_BRK = 43 irq_TIM8_UP = 44 irq_TIM8_TRG_COM = 45 irq_TIM8_CC = 46 ) func (p Pin) getPort() *stm32.GPIO_Type { switch p / 16 { case 0: return stm32.GPIOA case 1: return stm32.GPIOB case 2: return stm32.GPIOC case 3: return stm32.GPIOD case 4: return stm32.GPIOE default: panic("machine: unknown port") } } // enableClock enables the clock for this desired GPIO port. func (p Pin) enableClock() { switch p / 16 { case 0: stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIOAEN) case 1: stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIOBEN) case 2: stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIOCEN) case 3: stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIODEN) case 4: stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIOEEN) default: panic("machine: unknown port") } } // Enable peripheral clock func enableAltFuncClock(bus unsafe.Pointer) { switch bus { case unsafe.Pointer(stm32.PWR): // Power interface clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_PWREN) case unsafe.Pointer(stm32.I2C3): // I2C3 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_I2C3EN) case unsafe.Pointer(stm32.I2C2): // I2C2 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_I2C2EN) case unsafe.Pointer(stm32.I2C1): // I2C1 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_I2C1EN) case unsafe.Pointer(stm32.UART4): // UART4 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_UART4EN) case unsafe.Pointer(stm32.USART3): // USART3 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_USART3EN) case unsafe.Pointer(stm32.USART2): // USART2 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_USART2EN) case unsafe.Pointer(stm32.SPI3): // SPI3 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_SPI3EN) case unsafe.Pointer(stm32.SPI2): // SPI2 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_SPI2EN) case unsafe.Pointer(stm32.WWDG): // Window watchdog clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_WWDGEN) case unsafe.Pointer(stm32.TIM7): // TIM7 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_TIM7EN) case unsafe.Pointer(stm32.TIM6): // TIM6 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_TIM6EN) case unsafe.Pointer(stm32.TIM3): // TIM3 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_TIM3EN) case unsafe.Pointer(stm32.TIM2): // TIM2 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_TIM2EN) case unsafe.Pointer(stm32.LPTIM2): // LPTIM2 clock enable stm32.RCC.APB1ENR2.SetBits(stm32.RCC_APB1ENR2_LPTIM2EN) case unsafe.Pointer(stm32.LPUART1): // LPUART1 clock enable stm32.RCC.APB1ENR2.SetBits(stm32.RCC_APB1ENR2_LPUART1EN) case unsafe.Pointer(stm32.TIM16): // TIM16 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM16EN) case unsafe.Pointer(stm32.TIM15): // TIM15 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM15EN) case unsafe.Pointer(stm32.SYSCFG): // System configuration controller clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SYSCFGEN) case unsafe.Pointer(stm32.SPI1): // SPI1 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SPI1EN) case unsafe.Pointer(stm32.USART1): // USART1 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN) case unsafe.Pointer(stm32.TIM1): // TIM1 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM1EN) } } func handlePinInterrupt(pin uint8) { if stm32.EXTI.PR1.HasBits(1 << pin) { // Writing 1 to the pending register clears the // pending flag for that bit stm32.EXTI.PR1.Set(1 << pin) callback := pinCallbacks[pin] if callback != nil { callback(interruptPins[pin]) } } } func (p Pin) registerInterrupt() interrupt.Interrupt { pin := uint8(p) % 16 switch pin { case 0: return interrupt.New(stm32.IRQ_EXTI0, func(interrupt.Interrupt) { handlePinInterrupt(0) }) case 1: return interrupt.New(stm32.IRQ_EXTI1, func(interrupt.Interrupt) { handlePinInterrupt(1) }) case 2: return interrupt.New(stm32.IRQ_EXTI2, func(interrupt.Interrupt) { handlePinInterrupt(2) }) case 3: return interrupt.New(stm32.IRQ_EXTI3, func(interrupt.Interrupt) { handlePinInterrupt(3) }) case 4: return interrupt.New(stm32.IRQ_EXTI4, func(interrupt.Interrupt) { handlePinInterrupt(4) }) case 5: return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(5) }) case 6: return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(6) }) case 7: return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(7) }) case 8: return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(8) }) case 9: return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(9) }) case 10: return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(10) }) case 11: return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(11) }) case 12: return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(12) }) case 13: return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(13) }) case 14: return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(14) }) case 15: return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(15) }) } return interrupt.Interrupt{} } //---------- UART related code // Configure the UART. func (uart *UART) configurePins(config UARTConfig) { // enable the alternate functions on the TX and RX pins config.TX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTTX}, uart.TxAltFuncSelector) config.RX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTRX}, uart.RxAltFuncSelector) } // UART baudrate calc based on the bus and clockspeed // NOTE: keep this in sync with the runtime/runtime_stm32l5x2.go clock init code func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 { return (CPUFrequency() / baudRate) } // Register names vary by ST processor, these are for STM L5 func (uart *UART) setRegisters() { uart.rxReg = &uart.Bus.RDR uart.txReg = &uart.Bus.TDR uart.statusReg = &uart.Bus.ISR uart.txEmptyFlag = stm32.USART_ISR_TXE } //---------- SPI related types and code // SPI on the STM32Fxxx using MODER / alternate function pins type SPI struct { Bus *stm32.SPI_Type AltFuncSelector uint8 } func (spi *SPI) config8Bits() { // Set rx threshold to 8-bits, so RXNE flag is set for 1 byte // (common STM32 SPI implementation does 8-bit transfers only) spi.Bus.CR2.SetBits(stm32.SPI_CR2_FRXTH) } // Set baud rate for SPI func (spi *SPI) getBaudRate(config SPIConfig) uint32 { var conf uint32 // Default if config.Frequency == 0 { config.Frequency = 4e6 } localFrequency := config.Frequency // set frequency dependent on PCLK prescaler. Since these are rather weird // speeds due to the CPU frequency, pick a range up to that frequency for // clients to use more human-understandable numbers, e.g. nearest 100KHz // These are based on 80MHz peripheral clock frequency switch { case localFrequency < 312500: conf = stm32.SPI_CR1_BR_Div256 case localFrequency < 625000: conf = stm32.SPI_CR1_BR_Div128 case localFrequency < 1250000: conf = stm32.SPI_CR1_BR_Div64 case localFrequency < 2500000: conf = stm32.SPI_CR1_BR_Div32 case localFrequency < 5000000: conf = stm32.SPI_CR1_BR_Div16 case localFrequency < 10000000: conf = stm32.SPI_CR1_BR_Div8 // NOTE: many SPI components won't operate reliably (or at all) above 10MHz // Check the datasheet of the part case localFrequency < 20000000: conf = stm32.SPI_CR1_BR_Div4 case localFrequency < 40000000: conf = stm32.SPI_CR1_BR_Div2 default: // None of the specific baudrates were selected; choose the lowest speed conf = stm32.SPI_CR1_BR_Div256 } return conf << stm32.SPI_CR1_BR_Pos } // Configure SPI pins for input output and clock func (spi *SPI) configurePins(config SPIConfig) { config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector) config.SDO.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDO}, spi.AltFuncSelector) config.SDI.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDI}, spi.AltFuncSelector) } //---------- Timer related code var ( TIM1 = TIM{ EnableRegister: &stm32.RCC.APB2ENR, EnableFlag: stm32.RCC_APB2ENR_TIM1EN, Device: stm32.TIM1, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{ {PA8, AF1_TIM1_2_LPTIM1}, }}, TimerChannel{Pins: []PinFunction{ {PA9, AF1_TIM1_2_LPTIM1}, }}, TimerChannel{Pins: []PinFunction{ {PA10, AF1_TIM1_2_LPTIM1}, }}, TimerChannel{Pins: []PinFunction{ {PA11, AF1_TIM1_2_LPTIM1}, }}, }, busFreq: APB2_TIM_FREQ, } TIM2 = TIM{ EnableRegister: &stm32.RCC.APB1ENR1, EnableFlag: stm32.RCC_APB1ENR1_TIM2EN, Device: stm32.TIM2, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{ {PA0, AF1_TIM1_2_LPTIM1}, {PA5, AF1_TIM1_2_LPTIM1}, {PA15, AF1_TIM1_2_LPTIM1}, }}, TimerChannel{Pins: []PinFunction{ {PA1, AF1_TIM1_2_LPTIM1}, {PB3, AF1_TIM1_2_LPTIM1}, }}, TimerChannel{Pins: []PinFunction{ {PA2, AF1_TIM1_2_LPTIM1}, }}, TimerChannel{Pins: []PinFunction{ {PA3, AF1_TIM1_2_LPTIM1}, }}, }, busFreq: APB1_TIM_FREQ, } TIM3 = TIM{ EnableRegister: &stm32.RCC.APB1ENR1, EnableFlag: stm32.RCC_APB1ENR1_TIM3EN, Device: stm32.TIM3, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB1_TIM_FREQ, } TIM6 = TIM{ EnableRegister: &stm32.RCC.APB1ENR1, EnableFlag: stm32.RCC_APB1ENR1_TIM6EN, Device: stm32.TIM6, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB1_TIM_FREQ, } TIM7 = TIM{ EnableRegister: &stm32.RCC.APB1ENR1, EnableFlag: stm32.RCC_APB1ENR1_TIM7EN, Device: stm32.TIM7, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB1_TIM_FREQ, } TIM15 = TIM{ EnableRegister: &stm32.RCC.APB2ENR, EnableFlag: stm32.RCC_APB2ENR_TIM15EN, Device: stm32.TIM15, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{ {PA2, AF14_TIM2_15_16_LPTIM2}, }}, TimerChannel{Pins: []PinFunction{ {PA3, AF14_TIM2_15_16_LPTIM2}, }}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB2_TIM_FREQ, } TIM16 = TIM{ EnableRegister: &stm32.RCC.APB2ENR, EnableFlag: stm32.RCC_APB2ENR_TIM16EN, Device: stm32.TIM16, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{ {PA6, AF14_TIM2_15_16_LPTIM2}, }}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB2_TIM_FREQ, } ) func (t *TIM) registerUPInterrupt() interrupt.Interrupt { switch t { case &TIM1: return interrupt.New(irq_TIM1_UP_TIM16, TIM1.handleUPInterrupt) case &TIM2: return interrupt.New(irq_TIM2, TIM2.handleUPInterrupt) case &TIM3: return interrupt.New(irq_TIM3, TIM3.handleUPInterrupt) case &TIM6: return interrupt.New(irq_TIM6, TIM6.handleUPInterrupt) case &TIM7: return interrupt.New(irq_TIM7, TIM7.handleUPInterrupt) case &TIM15: return interrupt.New(irq_TIM1_BRK_TIM15, TIM15.handleUPInterrupt) case &TIM16: return interrupt.New(irq_TIM1_UP_TIM16, TIM16.handleUPInterrupt) } return interrupt.Interrupt{} } func (t *TIM) registerOCInterrupt() interrupt.Interrupt { switch t { case &TIM1: return interrupt.New(irq_TIM1_CC, TIM1.handleUPInterrupt) case &TIM2: return interrupt.New(irq_TIM2, TIM2.handleOCInterrupt) case &TIM3: return interrupt.New(irq_TIM3, TIM3.handleOCInterrupt) case &TIM6: return interrupt.New(irq_TIM6, TIM6.handleOCInterrupt) case &TIM7: return interrupt.New(irq_TIM7, TIM7.handleOCInterrupt) case &TIM15: return interrupt.New(irq_TIM1_BRK_TIM15, TIM15.handleOCInterrupt) case &TIM16: return interrupt.New(irq_TIM1_UP_TIM16, TIM16.handleOCInterrupt) } return interrupt.Interrupt{} } func (t *TIM) enableMainOutput() { // nothing to do - no BDTR register } type arrtype = uint32 type arrRegType = volatile.Register32 const ( ARR_MAX = 0x10000 PSC_MAX = 0x10000 ) func initRNG() { stm32.RCC.CRRCR.SetBits(stm32.RCC_CRRCR_HSI48ON) for !stm32.RCC.CRRCR.HasBits(stm32.RCC_CRRCR_HSI48RDY) { } stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_RNGEN) stm32.RNG.CR.SetBits(stm32.RNG_CR_RNGEN) } //---------- Flash related code const eraseBlockSizeValue = 2048 // see RM0394 page 83 // eraseBlock of the passed in block number func eraseBlock(block uint32) error { waitUntilFlashDone() // clear any previous errors stm32.FLASH.SR.SetBits(0x3FA) // page erase operation stm32.FLASH.SetCR_PER(1) defer stm32.FLASH.SetCR_PER(0) // set the page to be erased stm32.FLASH.SetCR_PNB(block) // start the page erase stm32.FLASH.SetCR_START(1) waitUntilFlashDone() if err := checkError(); err != nil { return err } return nil } const writeBlockSize = 8 // see RM0394 page 84 // It is only possible to program double word (2 x 32-bit data). func writeFlashData(address uintptr, data []byte) (int, error) { if len(data)%writeBlockSize != 0 { return 0, errFlashInvalidWriteLength } waitUntilFlashDone() // clear any previous errors stm32.FLASH.SR.SetBits(0x3FA) for j := 0; j < len(data); j += writeBlockSize { // start page write operation stm32.FLASH.SetCR_PG(1) // write second word using double-word high order word *(*uint32)(unsafe.Pointer(address)) = binary.LittleEndian.Uint32(data[j : j+writeBlockSize/2]) address += writeBlockSize / 2 // write first word using double-word low order word *(*uint32)(unsafe.Pointer(address)) = binary.LittleEndian.Uint32(data[j+writeBlockSize/2 : j+writeBlockSize]) waitUntilFlashDone() if err := checkError(); err != nil { return j, err } // end flash write stm32.FLASH.SetCR_PG(0) address += writeBlockSize / 2 } return len(data), nil } func waitUntilFlashDone() { for stm32.FLASH.GetSR_BSY() != 0 { } } var ( errFlashPGS = errors.New("errFlashPGS") errFlashSIZE = errors.New("errFlashSIZE") errFlashPGA = errors.New("errFlashPGA") errFlashWRP = errors.New("errFlashWRP") errFlashPROG = errors.New("errFlashPROG") ) func checkError() error { switch { case stm32.FLASH.GetSR_PGSERR() != 0: return errFlashPGS case stm32.FLASH.GetSR_SIZERR() != 0: return errFlashSIZE case stm32.FLASH.GetSR_PGAERR() != 0: return errFlashPGA case stm32.FLASH.GetSR_WRPERR() != 0: return errFlashWRP case stm32.FLASH.GetSR_PROGERR() != 0: return errFlashPROG } return nil } ================================================ FILE: src/machine/machine_stm32l4x2.go ================================================ //go:build stm32l4x2 package machine // Peripheral abstraction layer for the stm32l4x2 func CPUFrequency() uint32 { return 80000000 } // Internal use: configured speed of the APB1 and APB2 timers, this should be kept // in sync with any changes to runtime package which configures the oscillators // and clock frequencies const APB1_TIM_FREQ = 80e6 // 80MHz const APB2_TIM_FREQ = 80e6 // 80MHz //---------- I2C related code // Gets the value for TIMINGR register func (i2c *I2C) getFreqRange(br uint32) uint32 { // These are 'magic' values calculated by STM32CubeMX // for 80MHz PCLK1. // TODO: Do calculations based on PCLK1 switch br { case 10 * KHz: return 0xF010F3FE case 100 * KHz: return 0x10909CEC case 400 * KHz: return 0x00702991 case 500 * KHz: return 0x00300E84 default: return 0 } } ================================================ FILE: src/machine/machine_stm32l4x5.go ================================================ //go:build stm32l4x5 package machine // Peripheral abstraction layer for the stm32l4x5 func CPUFrequency() uint32 { return 120e6 } // Internal use: configured speed of the APB1 and APB2 timers, this should be kept // in sync with any changes to runtime package which configures the oscillators // and clock frequencies const APB1_TIM_FREQ = 120e6 // 120MHz const APB2_TIM_FREQ = 120e6 // 120MHz //---------- I2C related code // Gets the value for TIMINGR register func (i2c *I2C) getFreqRange(br uint32) uint32 { // This is a 'magic' value calculated by STM32CubeMX // for 120MHz PCLK1. // TODO: Do calculations based on PCLK1 switch br { case 10 * KHz: return 0x0 // does this even work? zero is weird here. case 100 * KHz: return 0x307075B1 case 400 * KHz: return 0x00B03FDB case 500 * KHz: return 0x005017C7 default: return 0 } } ================================================ FILE: src/machine/machine_stm32l4x6.go ================================================ //go:build stm32l4x6 package machine // Peripheral abstraction layer for the stm32l4x6 func CPUFrequency() uint32 { return 80e6 } // Internal use: configured speed of the APB1 and APB2 timers, this should be kept // in sync with any changes to runtime package which configures the oscillators // and clock frequencies const APB1_TIM_FREQ = 80e6 // 80MHz const APB2_TIM_FREQ = 80e6 // 80MHz //---------- I2C related code // Gets the value for TIMINGR register func (i2c *I2C) getFreqRange(br uint32) uint32 { // This is a 'magic' value calculated by STM32CubeMX // for 80MHz PCLK1. // TODO: Do calculations based on PCLK1 switch br { case 10 * KHz: return 0xF010F3FE case 100 * KHz: return 0x10909CEC case 400 * KHz: return 0x00702991 case 500 * KHz: return 0x00300E84 default: return 0 } } ================================================ FILE: src/machine/machine_stm32l5.go ================================================ //go:build stm32l5 package machine // Peripheral abstraction layer for the stm32l5 import ( "device/stm32" "runtime/interrupt" "runtime/volatile" "unsafe" ) var deviceIDAddr = []uintptr{0x0BFA0590, 0x0BFA0594, 0x0BFA0598} const ( AF0_SYSTEM = 0 AF1_TIM1_2_5_8_LPTIM1 = 1 AF2_TIM1_2_3_4_5_LPTIM3 = 2 AF3_SPI2_SAI1_I2C4_USART2_TIM1_8_OCTOSPI1 = 3 AF4_I2C1_2_3_4 = 4 AF5_SPI1_2_3_I2C4_DFSDM1_OCTOSPI1 = 5 AF6_SPI3_I2C3_DFSDM1_COMP1 = 6 AF7_USART1_2_3 = 7 AF8_UART4_5_LPUART1_SDMMC1 = 8 AF9_FDCAN1_TSC = 9 AF10_USB_OCTOSPI1 = 10 AF11_UCPD1 = 11 AF12_SDMMC1_COMP1_2_TIM1_8_FMC = 12 AF13_SAI1_2_TIM8 = 13 AF14_TIM2_8_15_16_17_LPTIM2 = 14 AF15_EVENTOUT = 15 ) const ( PA0 = portA + 0 PA1 = portA + 1 PA2 = portA + 2 PA3 = portA + 3 PA4 = portA + 4 PA5 = portA + 5 PA6 = portA + 6 PA7 = portA + 7 PA8 = portA + 8 PA9 = portA + 9 PA10 = portA + 10 PA11 = portA + 11 PA12 = portA + 12 PA13 = portA + 13 PA14 = portA + 14 PA15 = portA + 15 PB0 = portB + 0 PB1 = portB + 1 PB2 = portB + 2 PB3 = portB + 3 PB4 = portB + 4 PB5 = portB + 5 PB6 = portB + 6 PB7 = portB + 7 PB8 = portB + 8 PB9 = portB + 9 PB10 = portB + 10 PB11 = portB + 11 PB12 = portB + 12 PB13 = portB + 13 PB14 = portB + 14 PB15 = portB + 15 PC0 = portC + 0 PC1 = portC + 1 PC2 = portC + 2 PC3 = portC + 3 PC4 = portC + 4 PC5 = portC + 5 PC6 = portC + 6 PC7 = portC + 7 PC8 = portC + 8 PC9 = portC + 9 PC10 = portC + 10 PC11 = portC + 11 PC12 = portC + 12 PC13 = portC + 13 PC14 = portC + 14 PC15 = portC + 15 PD0 = portD + 0 PD1 = portD + 1 PD2 = portD + 2 PD3 = portD + 3 PD4 = portD + 4 PD5 = portD + 5 PD6 = portD + 6 PD7 = portD + 7 PD8 = portD + 8 PD9 = portD + 9 PD10 = portD + 10 PD11 = portD + 11 PD12 = portD + 12 PD13 = portD + 13 PD14 = portD + 14 PD15 = portD + 15 PE0 = portE + 0 PE1 = portE + 1 PE2 = portE + 2 PE3 = portE + 3 PE4 = portE + 4 PE5 = portE + 5 PE6 = portE + 6 PE7 = portE + 7 PE8 = portE + 8 PE9 = portE + 9 PE10 = portE + 10 PE11 = portE + 11 PE12 = portE + 12 PE13 = portE + 13 PE14 = portE + 14 PE15 = portE + 15 PF0 = portF + 0 PF1 = portF + 1 PF2 = portF + 2 PF3 = portF + 3 PF4 = portF + 4 PF5 = portF + 5 PF6 = portF + 6 PF7 = portF + 7 PF8 = portF + 8 PF9 = portF + 9 PF10 = portF + 10 PF11 = portF + 11 PF12 = portF + 12 PF13 = portF + 13 PF14 = portF + 14 PF15 = portF + 15 PG0 = portG + 0 PG1 = portG + 1 PG2 = portG + 2 PG3 = portG + 3 PG4 = portG + 4 PG5 = portG + 5 PG6 = portG + 6 PG7 = portG + 7 PG8 = portG + 8 PG9 = portG + 9 PG10 = portG + 10 PG11 = portG + 11 PG12 = portG + 12 PG13 = portG + 13 PG14 = portG + 14 PG15 = portG + 15 PH0 = portH + 0 PH1 = portH + 1 ) func (p Pin) getPort() *stm32.GPIO_Type { switch p / 16 { case 0: return stm32.GPIOA case 1: return stm32.GPIOB case 2: return stm32.GPIOC case 3: return stm32.GPIOD case 4: return stm32.GPIOE case 5: return stm32.GPIOF case 6: return stm32.GPIOG case 7: return stm32.GPIOH default: panic("machine: unknown port") } } // enableClock enables the clock for this desired GPIO port. func (p Pin) enableClock() { switch p / 16 { case 0: stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIOAEN) case 1: stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIOBEN) case 2: stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIOCEN) case 3: stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIODEN) case 4: stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIOEEN) case 5: stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIOFEN) case 6: stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIOGEN) case 7: stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIOHEN) default: panic("machine: unknown port") } } // Enable peripheral clock func enableAltFuncClock(bus unsafe.Pointer) { switch bus { case unsafe.Pointer(stm32.DAC): // DAC interface clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_DAC1EN) case unsafe.Pointer(stm32.PWR): // Power interface clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_PWREN) case unsafe.Pointer(stm32.I2C3): // I2C3 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_I2C3EN) case unsafe.Pointer(stm32.I2C2): // I2C2 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_I2C2EN) case unsafe.Pointer(stm32.I2C1): // I2C1 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_I2C1EN) case unsafe.Pointer(stm32.UART5): // UART5 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_UART5EN) case unsafe.Pointer(stm32.UART4): // UART4 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_UART4EN) case unsafe.Pointer(stm32.USART3): // USART3 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_USART3EN) case unsafe.Pointer(stm32.USART2): // USART2 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_USART2EN) case unsafe.Pointer(stm32.SPI3): // SPI3 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_SP3EN) case unsafe.Pointer(stm32.SPI2): // SPI2 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_SPI2EN) case unsafe.Pointer(stm32.WWDG): // Window watchdog clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_WWDGEN) case unsafe.Pointer(stm32.TIM7): // TIM7 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_TIM7EN) case unsafe.Pointer(stm32.TIM6): // TIM6 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_TIM6EN) case unsafe.Pointer(stm32.TIM5): // TIM5 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_TIM5EN) case unsafe.Pointer(stm32.TIM4): // TIM4 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_TIM4EN) case unsafe.Pointer(stm32.TIM3): // TIM3 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_TIM3EN) case unsafe.Pointer(stm32.TIM2): // TIM2 clock enable stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_TIM2EN) case unsafe.Pointer(stm32.UCPD1): // UCPD1 clock enable stm32.RCC.APB1ENR2.SetBits(stm32.RCC_APB1ENR2_UCPD1EN) case unsafe.Pointer(stm32.FDCAN1): // FDCAN1 clock enable stm32.RCC.APB1ENR2.SetBits(stm32.RCC_APB1ENR2_FDCAN1EN) case unsafe.Pointer(stm32.LPTIM3): // LPTIM3 clock enable stm32.RCC.APB1ENR2.SetBits(stm32.RCC_APB1ENR2_LPTIM3EN) case unsafe.Pointer(stm32.LPTIM2): // LPTIM2 clock enable stm32.RCC.APB1ENR2.SetBits(stm32.RCC_APB1ENR2_LPTIM2EN) case unsafe.Pointer(stm32.I2C4): // I2C4 clock enable stm32.RCC.APB1ENR2.SetBits(stm32.RCC_APB1ENR2_I2C4EN) case unsafe.Pointer(stm32.LPUART1): // LPUART1 clock enable stm32.RCC.APB1ENR2.SetBits(stm32.RCC_APB1ENR2_LPUART1EN) case unsafe.Pointer(stm32.TIM17): // TIM17 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM17EN) case unsafe.Pointer(stm32.TIM16): // TIM16 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM16EN) case unsafe.Pointer(stm32.TIM15): // TIM15 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM15EN) case unsafe.Pointer(stm32.SYSCFG): // System configuration controller clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SYSCFGEN) case unsafe.Pointer(stm32.SPI1): // SPI1 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SPI1EN) case unsafe.Pointer(stm32.USART1): // USART1 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN) case unsafe.Pointer(stm32.TIM8): // TIM8 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM8EN) case unsafe.Pointer(stm32.TIM1): // TIM1 clock enable stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM1EN) } } func (p Pin) registerInterrupt() interrupt.Interrupt { pin := uint8(p) % 16 switch pin { case 0: return interrupt.New(stm32.IRQ_EXTI0, func(interrupt.Interrupt) { handlePinInterrupt(0) }) case 1: return interrupt.New(stm32.IRQ_EXTI1, func(interrupt.Interrupt) { handlePinInterrupt(1) }) case 2: return interrupt.New(stm32.IRQ_EXTI2, func(interrupt.Interrupt) { handlePinInterrupt(2) }) case 3: return interrupt.New(stm32.IRQ_EXTI3, func(interrupt.Interrupt) { handlePinInterrupt(3) }) case 4: return interrupt.New(stm32.IRQ_EXTI4, func(interrupt.Interrupt) { handlePinInterrupt(4) }) case 5: return interrupt.New(stm32.IRQ_EXTI5, func(interrupt.Interrupt) { handlePinInterrupt(5) }) case 6: return interrupt.New(stm32.IRQ_EXTI6, func(interrupt.Interrupt) { handlePinInterrupt(6) }) case 7: return interrupt.New(stm32.IRQ_EXTI7, func(interrupt.Interrupt) { handlePinInterrupt(7) }) case 8: return interrupt.New(stm32.IRQ_EXTI8, func(interrupt.Interrupt) { handlePinInterrupt(8) }) case 9: return interrupt.New(stm32.IRQ_EXTI9, func(interrupt.Interrupt) { handlePinInterrupt(9) }) case 10: return interrupt.New(stm32.IRQ_EXTI10, func(interrupt.Interrupt) { handlePinInterrupt(10) }) case 11: return interrupt.New(stm32.IRQ_EXTI11, func(interrupt.Interrupt) { handlePinInterrupt(11) }) case 12: return interrupt.New(stm32.IRQ_EXTI12, func(interrupt.Interrupt) { handlePinInterrupt(12) }) case 13: return interrupt.New(stm32.IRQ_EXTI13, func(interrupt.Interrupt) { handlePinInterrupt(13) }) case 14: return interrupt.New(stm32.IRQ_EXTI14, func(interrupt.Interrupt) { handlePinInterrupt(14) }) case 15: return interrupt.New(stm32.IRQ_EXTI15, func(interrupt.Interrupt) { handlePinInterrupt(15) }) } return interrupt.Interrupt{} } func handlePinInterrupt(pin uint8) { // The pin abstraction doesn't differentiate pull-up // events from pull-down events, so combine them to // a single call here. if stm32.EXTI.RPR1.HasBits(1< clock { freq = clock } // calculate the exact clock divisor (freq=clock/div -> div=clock/freq). // truncation is fine, since it produces a less-than-or-equal divisor, and // thus a greater-than-or-equal frequency. // divisors only come in consecutive powers of 2, so we can use log2 (or, // equivalently, bits.Len - 1) to convert to respective enum value. div := bits.Len32(clock/freq) - 1 // but DIV1 (2^0) is not permitted, as the least divisor is DIV2 (2^1), so // subtract 1 from the log2 value, keeping a lower bound of 0 if div < 0 { div = 0 } else if div > 0 { div-- } // finally, shift the enumerated value into position for SPI CR1 return uint32(div) << stm32.SPI_CR1_BR_Pos } //---------- I2C related code // Gets the value for TIMINGR register func (i2c *I2C) getFreqRange(br uint32) uint32 { // This is a 'magic' value calculated by STM32CubeMX // for 48Mhz PCLK1. // TODO: Do calculations based on PCLK1 switch br { case 10 * KHz: return 0x9010DEFF case 100 * KHz: return 0x20303E5D case 400 * KHz: return 0x2010091A case 500 * KHz: return 0x00201441 default: return 0 } } //---------- UART related code // Configure the UART. func (uart UART) configurePins(config UARTConfig) { // enable the alternate functions on the TX and RX pins config.TX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTTX}, uart.TxAltFuncSelector) config.RX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTRX}, uart.RxAltFuncSelector) } // UART baudrate calc based on the bus and clockspeed // NOTE: keep this in sync with the runtime/runtime_stm32wle5.go clock init code func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 { var br uint32 uartClock := CPUFrequency() // No Prescaler configuration br = uint32((uartClock + baudRate/2) / baudRate) return (br) } // Register names vary by ST processor, these are for STM L5 func (uart *UART) setRegisters() { uart.rxReg = &uart.Bus.RDR uart.txReg = &uart.Bus.TDR uart.statusReg = &uart.Bus.ISR uart.txEmptyFlag = stm32.USART_ISR_TXFNF //(TXFNF == TXE == bit 7, but depends alternate RM0461/1094) } //---------- Timer related code var ( TIM1 = TIM{ EnableRegister: &stm32.RCC.APB2ENR, EnableFlag: stm32.RCC_APB2ENR_TIM1EN, Device: stm32.TIM1, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{{PA8, AF1_TIM1_2_LPTIM1}}}, TimerChannel{Pins: []PinFunction{{PA9, AF1_TIM1_2_LPTIM1}}}, TimerChannel{Pins: []PinFunction{{PA10, AF1_TIM1_2_LPTIM1}}}, TimerChannel{Pins: []PinFunction{{PA11, AF1_TIM1_2_LPTIM1}}}, }, busFreq: APB2_TIM_FREQ, } TIM2 = TIM{ EnableRegister: &stm32.RCC.APB1ENR1, EnableFlag: stm32.RCC_APB1ENR1_TIM2EN, Device: stm32.TIM2, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{{PA0, AF1_TIM1_2_LPTIM1}, {PA5, AF1_TIM1_2_LPTIM1}, {PA15, AF1_TIM1_2_LPTIM1}}}, TimerChannel{Pins: []PinFunction{{PA1, AF1_TIM1_2_LPTIM1}, {PB3, AF1_TIM1_2_LPTIM1}}}, TimerChannel{Pins: []PinFunction{{PA2, AF1_TIM1_2_LPTIM1}, {PB10, AF1_TIM1_2_LPTIM1}}}, TimerChannel{Pins: []PinFunction{{PA3, AF1_TIM1_2_LPTIM1}, {PB11, AF1_TIM1_2_LPTIM1}}}, }, busFreq: APB1_TIM_FREQ, } TIM16 = TIM{ EnableRegister: &stm32.RCC.APB2ENR, EnableFlag: stm32.RCC_APB2ENR_TIM16EN, Device: stm32.TIM16, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{{PA6, AF14_TIM2_16_17_LPTIM2}}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB2_TIM_FREQ, } TIM17 = TIM{ EnableRegister: &stm32.RCC.APB2ENR, EnableFlag: stm32.RCC_APB2ENR_TIM17EN, Device: stm32.TIM17, Channels: [4]TimerChannel{ TimerChannel{Pins: []PinFunction{{PA7, AF1_TIM1_2_LPTIM1}, {PB9, AF1_TIM1_2_LPTIM1}}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, TimerChannel{Pins: []PinFunction{}}, }, busFreq: APB2_TIM_FREQ, } ) func (t *TIM) registerUPInterrupt() interrupt.Interrupt { switch t { case &TIM1: return interrupt.New(stm32.IRQ_TIM1_UP, TIM1.handleUPInterrupt) case &TIM2: return interrupt.New(stm32.IRQ_TIM2, TIM2.handleUPInterrupt) case &TIM16: return interrupt.New(stm32.IRQ_TIM16, TIM16.handleUPInterrupt) case &TIM17: return interrupt.New(stm32.IRQ_TIM17, TIM17.handleUPInterrupt) } return interrupt.Interrupt{} } func (t *TIM) registerOCInterrupt() interrupt.Interrupt { switch t { case &TIM1: return interrupt.New(stm32.IRQ_TIM1_CC, TIM1.handleOCInterrupt) case &TIM2: return interrupt.New(stm32.IRQ_TIM2, TIM2.handleOCInterrupt) case &TIM16: return interrupt.New(stm32.IRQ_TIM16, TIM16.handleOCInterrupt) case &TIM17: return interrupt.New(stm32.IRQ_TIM17, TIM17.handleOCInterrupt) } return interrupt.Interrupt{} } func (t *TIM) enableMainOutput() { t.Device.BDTR.SetBits(stm32.TIM_BDTR_MOE) } func initRNG() { stm32.RCC.AHB3ENR.SetBits(stm32.RCC_AHB3ENR_RNGEN) // Enable RNG with config.A (See RM0453 22.6.2) stm32.RNG.CR.Set(0x40F00D40) // RNG Config. A stm32.RNG.HTCR.Set(0x17590ABC) // MAGIC NUMBER stm32.RNG.HTCR.Set(0x0000AA74) // HTCR VALUE stm32.RNG.CR.Set(0x00F00D4C) // CONFIG A + RNG_EN=1 + IE=1 } //---------- type arrtype = uint32 type arrRegType = volatile.Register32 const ( ARR_MAX = 0x10000 PSC_MAX = 0x10000 ) //---------- Flash related code const eraseBlockSizeValue = 2048 // eraseBlock of the passed in block number func eraseBlock(block uint32) error { waitUntilFlashDone() // check if operation is allowed. if stm32.FLASH.GetSR_PESD() != 0 { return errFlashCannotErasePage } // clear any previous errors stm32.FLASH.SR.SetBits(0x3FA) // page erase operation stm32.FLASH.SetCR_PER(1) defer stm32.FLASH.SetCR_PER(0) // set the address to the page to be written stm32.FLASH.SetCR_PNB(block) defer stm32.FLASH.SetCR_PNB(0) // start the page erase stm32.FLASH.SetCR_STRT(1) waitUntilFlashDone() if err := checkError(); err != nil { return err } return nil } const writeBlockSize = 8 func writeFlashData(address uintptr, data []byte) (int, error) { if len(data)%writeBlockSize != 0 { return 0, errFlashInvalidWriteLength } waitUntilFlashDone() // check if operation is allowed if stm32.FLASH.GetSR_PESD() != 0 { return 0, errFlashNotAllowedWriteData } // clear any previous errors stm32.FLASH.SR.SetBits(0x3FA) for j := 0; j < len(data); j += writeBlockSize { // start page write operation stm32.FLASH.SetCR_PG(1) // write first word using double-word high order word *(*uint32)(unsafe.Pointer(address)) = binary.LittleEndian.Uint32(data[j : j+writeBlockSize/2]) address += writeBlockSize / 2 // write second word using double-word low order word *(*uint32)(unsafe.Pointer(address)) = binary.LittleEndian.Uint32(data[j+writeBlockSize/2 : j+writeBlockSize]) waitUntilFlashDone() if err := checkError(); err != nil { return j, err } // end flash write stm32.FLASH.SetCR_PG(0) address += writeBlockSize / 2 } return len(data), nil } func waitUntilFlashDone() { for stm32.FLASH.GetSR_BSY() != 0 { } for stm32.FLASH.GetSR_CFGBSY() != 0 { } } var ( errFlashPGS = errors.New("errFlashPGS") errFlashSIZE = errors.New("errFlashSIZE") errFlashPGA = errors.New("errFlashPGA") errFlashWRP = errors.New("errFlashWRP") errFlashPROG = errors.New("errFlashPROG") ) func checkError() error { switch { case stm32.FLASH.GetSR_PGSERR() != 0: return errFlashPGS case stm32.FLASH.GetSR_SIZERR() != 0: return errFlashSIZE case stm32.FLASH.GetSR_PGAERR() != 0: return errFlashPGA case stm32.FLASH.GetSR_WRPERR() != 0: return errFlashWRP case stm32.FLASH.GetSR_PROGERR() != 0: return errFlashPROG } return nil } ================================================ FILE: src/machine/machine_tkey.go ================================================ //go:build tkey package machine import ( "device/tkey" "errors" "strconv" ) const deviceName = "TKey" // GPIO pins modes are only here to match the Pin interface. // The actual configuration is fixed in the hardware. const ( PinOutput PinMode = iota PinInput PinInputPullup PinInputPulldown ) const ( LED_BLUE = Pin(tkey.TK1_MMIO_TK1_LED_B_BIT) LED_GREEN = Pin(tkey.TK1_MMIO_TK1_LED_G_BIT) LED_RED = Pin(tkey.TK1_MMIO_TK1_LED_R_BIT) LED = LED_GREEN TKEY_TOUCH = Pin(3) // 3 is unused, but we need a value here to match the Pin interface. BUTTON = TKEY_TOUCH GPIO1 = Pin(tkey.TK1_MMIO_TK1_GPIO1_BIT + 8) GPIO2 = Pin(tkey.TK1_MMIO_TK1_GPIO2_BIT + 8) GPIO3 = Pin(tkey.TK1_MMIO_TK1_GPIO3_BIT + 8) GPIO4 = Pin(tkey.TK1_MMIO_TK1_GPIO4_BIT + 8) ) var touchConfig, gpio1Config, gpio2Config PinConfig // No config needed for TKey, just to match the Pin interface. func (p Pin) Configure(config PinConfig) { switch p { case BUTTON: touchConfig = config // Clear any pending touch events. tkey.TOUCH.STATUS.Set(0) case GPIO1: gpio1Config = config case GPIO2: gpio2Config = config } } // Set pin to high or low. func (p Pin) Set(high bool) { switch p { case LED_BLUE, LED_GREEN, LED_RED: if high { tkey.TK1.LED.SetBits(1 << uint(p)) } else { tkey.TK1.LED.ClearBits(1 << uint(p)) } case GPIO3, GPIO4: if high { tkey.TK1.GPIO.SetBits(1 << uint(p-8)) } else { tkey.TK1.GPIO.ClearBits(1 << uint(p-8)) } } } // Get returns the current value of a pin. func (p Pin) Get() bool { pushed := false mode := PinInput switch p { case BUTTON: mode = touchConfig.Mode if tkey.TOUCH.STATUS.HasBits(1) { tkey.TOUCH.STATUS.Set(0) pushed = true } case GPIO1: mode = gpio1Config.Mode pushed = tkey.TK1.GPIO.HasBits(1 << uint(p-8)) case GPIO2: mode = gpio2Config.Mode pushed = tkey.TK1.GPIO.HasBits(1 << uint(p-8)) case GPIO3, GPIO4: mode = PinOutput pushed = tkey.TK1.GPIO.HasBits(1 << uint(p-8)) case LED_BLUE, LED_GREEN, LED_RED: mode = PinOutput pushed = tkey.TK1.LED.HasBits(1 << uint(p)) } switch mode { case PinInputPullup: return !pushed case PinInput, PinInputPulldown, PinOutput: return pushed } return false } type UART struct { Bus *tkey.UART_Type } var ( DefaultUART = UART0 UART0 = &_UART0 _UART0 = UART{Bus: tkey.UART} ) // The TKey UART is fixed at 62500 baud, 8N1. func (uart *UART) Configure(config UARTConfig) error { if !(config.BaudRate == 62500 || config.BaudRate == 0) { return errors.New("uart: only 62500 baud rate is supported") } return nil } // Write a slice of data bytes to the UART. func (uart *UART) Write(data []byte) (n int, err error) { for _, c := range data { if err := uart.WriteByte(c); err != nil { return n, err } } return len(data), nil } // WriteByte writes a byte of data to the UART. func (uart *UART) WriteByte(c byte) error { for uart.Bus.TX_STATUS.Get() == 0 { } uart.Bus.TX_DATA.Set(uint32(c)) return nil } // Buffered returns the number of bytes buffered in the UART. func (uart *UART) Buffered() int { return int(uart.Bus.RX_BYTES.Get()) } // ReadByte reads a byte of data from the UART. func (uart *UART) ReadByte() (byte, error) { for uart.Bus.RX_STATUS.Get() == 0 { } return byte(uart.Bus.RX_DATA.Get()), nil } // DTR is not available on the TKey. func (uart *UART) DTR() bool { return false } // RTS is not available on the TKey. func (uart *UART) RTS() bool { return false } // GetRNG returns 32 bits of cryptographically secure random data func GetRNG() (uint32, error) { for tkey.TRNG.STATUS.Get() == 0 { } return uint32(tkey.TRNG.ENTROPY.Get()), nil } // DesignName returns the FPGA design name. func DesignName() (string, string) { n0 := tkey.TK1.NAME0.Get() name0 := string([]byte{byte(n0 >> 24), byte(n0 >> 16), byte(n0 >> 8), byte(n0)}) n1 := tkey.TK1.NAME1.Get() name1 := string([]byte{byte(n1 >> 24), byte(n1 >> 16), byte(n1 >> 8), byte(n1)}) return name0, name1 } // DesignVersion returns the FPGA design version. func DesignVersion() string { version := tkey.TK1.VERSION.Get() return strconv.Itoa(int(version)) } // CDI returns 8 words of Compound Device Identifier (CDI) generated and written by the firmware when the application is loaded. func CDI() []byte { cdi := make([]byte, 32) for i := 0; i < 8; i++ { c := tkey.TK1.CDI_FIRST[i].Get() cdi[i*4] = byte(c >> 24) cdi[i*4+1] = byte(c >> 16) cdi[i*4+2] = byte(c >> 8) cdi[i*4+3] = byte(c) } return cdi } // UDI returns 2 words of Unique Device Identifier (UDI). Only available in firmware mode. func UDI() []byte { udi := make([]byte, 8) for i := 0; i < 2; i++ { c := tkey.TK1.UDI_FIRST[i].Get() udi[i*4] = byte(c >> 24) udi[i*4+1] = byte(c >> 16) udi[i*4+2] = byte(c >> 8) udi[i*4+3] = byte(c) } return udi } // UDS returns 8 words of Unique Device Secret. Part of the FPGA design, changed when provisioning a TKey. // Only available in firmware mode. UDS is only readable once per power cycle. func UDS() []byte { uds := make([]byte, 32) for i := 0; i < 8; i++ { c := tkey.UDS.DATA[i].Get() uds[i*4] = byte(c >> 24) uds[i*4+1] = byte(c >> 16) uds[i*4+2] = byte(c >> 8) uds[i*4+3] = byte(c) } return uds } ================================================ FILE: src/machine/machine_tkey_rom.go ================================================ //go:build tkey package machine /* #define TK1_MMIO_TK1_BLAKE2S 0xff000040 typedef unsigned char uint8_t; typedef unsigned long uint32_t; typedef unsigned long size_t; // blake2s state context typedef struct { uint8_t b[64]; // input buffer uint32_t h[8]; // chained state uint32_t t[2]; // total number of bytes size_t c; // pointer for b[] size_t outlen; // digest size } blake2s_ctx; typedef int (*fw_blake2s_p)(void *out, unsigned long outlen, const void *key, unsigned long keylen, const void *in, unsigned long inlen, blake2s_ctx *ctx); int blake2s(void *out, unsigned long outlen, const void *key, unsigned long keylen, const void *in, unsigned long inlen) { fw_blake2s_p const fw_blake2s = (fw_blake2s_p) * (volatile uint32_t *)TK1_MMIO_TK1_BLAKE2S; blake2s_ctx ctx; return fw_blake2s(out, outlen, key, keylen, in, inlen, &ctx); } */ import "C" import ( "errors" "unsafe" ) var ( ErrBLAKE2sInvalid = errors.New("invalid params for call to BLAKE2s") ErrBLAKE2sFailed = errors.New("call to BLAKE2s failed") ) func BLAKE2s(output []byte, key []byte, input []byte) error { if len(output) == 0 || len(input) == 0 { return ErrBLAKE2sInvalid } op := unsafe.Pointer(&output[0]) kp := unsafe.Pointer(&key[0]) ip := unsafe.Pointer(&input[0]) if res := C.blake2s(op, C.size_t(len(output)), kp, C.size_t(len(key)), ip, C.size_t(len(input))); res != 0 { return ErrBLAKE2sFailed } return nil } ================================================ FILE: src/machine/pdm.go ================================================ package machine type PDMConfig struct { Stereo bool DIN Pin CLK Pin } ================================================ FILE: src/machine/pwm.go ================================================ package machine import "errors" var ( ErrPWMPeriodTooLong = errors.New("pwm: period too long") ) // PWMConfig allows setting some configuration while configuring a PWM // peripheral. A zero PWMConfig is ready to use for simple applications such as // dimming LEDs. type PWMConfig struct { // PWM period in nanosecond. Leaving this zero will pick a reasonable period // value for use with LEDs. // If you want to configure a frequency instead of a period, you can use the // following formula to calculate a period from a frequency: // // period = 1e9 / frequency // Period uint64 } ================================================ FILE: src/machine/runtime.go ================================================ package machine import ( _ "unsafe" ) // // This file provides access to runtime package that would not otherwise // be permitted due to linker dependencies. // //go:linkname gosched runtime.Gosched func gosched() ================================================ FILE: src/machine/serial-none.go ================================================ //go:build baremetal && serial.none package machine // Serial is a null device: writes to it are ignored. var Serial = NullSerial{} func InitSerial() { Serial.Configure(UARTConfig{}) } ================================================ FILE: src/machine/serial-rtt.go ================================================ //go:build baremetal && serial.rtt // Implement Segger RTT support. // This is mostly useful for targets that only have a debug connection // available, and no serial output (or input). It is somewhat like semihosting, // but not unusably slow. // It was originally specified by Segger, but support is available in OpenOCD // for at least the DAPLink debuggers so I assume it works on any SWD debugger. package machine import ( "runtime/interrupt" "runtime/volatile" "unsafe" ) // This symbol name is known by the compiler, see monitor.go. // //go:linkname rttSerialInstance _SEGGER_RTT var rttSerialInstance rttSerial var Serial = &rttSerialInstance func InitSerial() { Serial.Configure(UARTConfig{}) } const ( // Some constants, see: // https://github.com/SEGGERMicro/RTT/blob/master/RTT/SEGGER_RTT.h rttMaxNumUpBuffers = 1 rttMaxNumDownBuffers = 1 rttBufferSizeUp = 1024 rttBufferSizeDown = 16 rttModeNoBlockSkip = 0 rttModeNoBlockTrim = 1 rttModeBlockIfFifoFull = 2 ) // The debugger knows about the layout of this struct, so it must not change. // This is SEGGER_RTT_CB. type rttControlBlock struct { id [16]volatile.Register8 maxNumUpBuffers int32 maxNumDownBuffers int32 buffersUp [rttMaxNumUpBuffers]rttBuffer buffersDown [rttMaxNumDownBuffers]rttBuffer } // Up or down buffer. // This is SEGGER_RTT_BUFFER_UP and SEGGER_RTT_BUFFER_DOWN. type rttBuffer struct { name *byte buffer *volatile.Register8 bufferSize uint32 writeOffset volatile.Register32 readOffset volatile.Register32 flags uint32 } // Static buffers, for the default up and down buffer. var ( rttBufferUpData [rttBufferSizeUp]volatile.Register8 rttBufferDownData [rttBufferSizeDown]volatile.Register8 ) type rttSerial struct { rttControlBlock } func (s *rttSerial) Configure(config UARTConfig) error { s.maxNumUpBuffers = rttMaxNumUpBuffers s.maxNumDownBuffers = rttMaxNumDownBuffers s.buffersUp[0].name = &[]byte("Terminal\x00")[0] s.buffersUp[0].buffer = &rttBufferUpData[0] s.buffersUp[0].bufferSize = rttBufferSizeUp s.buffersUp[0].flags = rttModeNoBlockSkip s.buffersDown[0].name = &[]byte("Terminal\x00")[0] s.buffersDown[0].buffer = &rttBufferDownData[0] s.buffersDown[0].bufferSize = rttBufferSizeDown s.buffersDown[0].flags = rttModeNoBlockSkip id := "SEGGER RTT" for i := 0; i < len(id); i++ { s.id[i].Set(id[i]) } return nil } func (b *rttBuffer) writeByte(c byte) { state := interrupt.Disable() readOffset := b.readOffset.Get() writeOffset := b.writeOffset.Get() newWriteOffset := writeOffset + 1 if newWriteOffset == b.bufferSize { newWriteOffset = 0 } if newWriteOffset != readOffset { unsafe.Slice(b.buffer, b.bufferSize)[writeOffset].Set(c) b.writeOffset.Set(newWriteOffset) } interrupt.Restore(state) } func (b *rttBuffer) readByte() byte { readOffset := b.readOffset.Get() writeOffset := b.writeOffset.Get() for readOffset == writeOffset { readOffset = b.readOffset.Get() } c := unsafe.Slice(b.buffer, b.bufferSize)[readOffset].Get() b.readOffset.Set(readOffset + 1) return c } func (b *rttBuffer) buffered() int { readOffset := b.readOffset.Get() writeOffset := b.writeOffset.Get() return int((writeOffset - readOffset) % rttBufferSizeDown) } // Write a single byte to the RTT output buffer. // // This method is set to not be inlined, to avoid blowing up binary size as a // result of inlining writeByte everywhere a println exists. // //go:noinline func (s *rttSerial) WriteByte(b byte) error { s.buffersUp[0].writeByte(b) return nil } func (s *rttSerial) ReadByte() (byte, error) { return s.buffersDown[0].readByte(), errNoByte } func (s *rttSerial) Buffered() int { return s.buffersDown[0].buffered() } func (s *rttSerial) Write(data []byte) (n int, err error) { for _, v := range data { s.WriteByte(v) } return len(data), nil } ================================================ FILE: src/machine/serial-uart.go ================================================ //go:build baremetal && serial.uart package machine // Serial is implemented via the default (usually the first) UART on the chip. var Serial = DefaultUART func InitSerial() { Serial.Configure(UARTConfig{}) } ================================================ FILE: src/machine/serial-usb.go ================================================ //go:build baremetal && serial.usb package machine // Serial is implemented via USB (USB-CDC). var Serial Serialer func InitSerial() { initUSB() Serial = USBCDC } ================================================ FILE: src/machine/serial.go ================================================ package machine import "errors" var errNoByte = errors.New("machine: no byte read") // UARTConfig is a struct with which a UART (or similar object) can be // configured. The baud rate is usually respected, but TX and RX may be ignored // depending on the chip and the type of object. type UARTConfig struct { BaudRate uint32 TX Pin RX Pin RTS Pin CTS Pin } // NullSerial is a serial version of /dev/null (or null router): it drops // everything that is written to it. type NullSerial struct { } // Configure does nothing: the null serial has no configuration. func (ns NullSerial) Configure(config UARTConfig) error { return nil } // WriteByte is a no-op: the null serial doesn't write bytes. func (ns NullSerial) WriteByte(b byte) error { return nil } // ReadByte always returns an error because there aren't any bytes to read. func (ns NullSerial) ReadByte() (byte, error) { return 0, errNoByte } // Buffered returns how many bytes are buffered in the UART. It always returns 0 // as there are no bytes to read. func (ns NullSerial) Buffered() int { return 0 } // Write is a no-op: none of the data is being written and it will not return an // error. func (ns NullSerial) Write(p []byte) (n int, err error) { return len(p), nil } ================================================ FILE: src/machine/spi.go ================================================ //go:build !baremetal || atmega || esp32 || fe310 || k210 || nrf || (nxp && !mk66f18) || rp2040 || rp2350 || sam || (stm32 && !stm32f7x2 && !stm32l5x2) package machine import "errors" // SPI phase and polarity configs CPOL and CPHA const ( Mode0 = 0 Mode1 = 1 Mode2 = 2 Mode3 = 3 ) var ( ErrTxInvalidSliceSize = errors.New("SPI write and read slices must be same size") errSPIInvalidMachineConfig = errors.New("SPI port was not configured properly by the machine") ) // If you are getting a compile error on this line please check to see you've // correctly implemented the methods on the SPI type. They must match // the interface method signatures type to type perfectly. // If not implementing the SPI type please remove your target from the build tags // at the top of this file. var _ interface { // 2 Configure(config SPIConfig) error Tx(w, r []byte) error Transfer(w byte) (byte, error) } = (*SPI)(nil) ================================================ FILE: src/machine/spi_tx.go ================================================ //go:build atmega || fe310 || k210 || (nxp && !mk66f18) || (stm32 && !stm32f7x2 && !stm32l5x2) // This file implements the SPI Tx function for targets that don't have a custom // (faster) implementation for it. package machine // Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read // interface, there must always be the same number of bytes written as bytes read. // The Tx method knows about this, and offers a few different ways of calling it. // // This form sends the bytes in tx buffer, putting the resulting bytes read into the rx buffer. // Note that the tx and rx buffers must be the same size: // // spi.Tx(tx, rx) // // This form sends the tx buffer, ignoring the result. Useful for sending "commands" that return zeros // until all the bytes in the command packet have been received: // // spi.Tx(tx, nil) // // This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": // // spi.Tx(nil, rx) func (spi *SPI) Tx(w, r []byte) error { var err error switch { case w == nil: // read only, so write zero and read a result. for i := range r { r[i], err = spi.Transfer(0) if err != nil { return err } } case r == nil: // write only for _, b := range w { _, err = spi.Transfer(b) if err != nil { return err } } default: // write/read if len(w) != len(r) { return ErrTxInvalidSliceSize } for i, b := range w { r[i], err = spi.Transfer(b) if err != nil { return err } } } return nil } ================================================ FILE: src/machine/uart.go ================================================ //go:build atmega || esp || nrf || sam || sifive || stm32 || k210 || nxp || rp2040 || rp2350 package machine import "errors" var errUARTBufferEmpty = errors.New("UART buffer empty") // UARTParity is the parity setting to be used for UART communication. type UARTParity uint8 const ( // ParityNone means to not use any parity checking. This is // the most common setting. ParityNone UARTParity = iota // ParityEven means to expect that the total number of 1 bits sent // should be an even number. ParityEven // ParityOdd means to expect that the total number of 1 bits sent // should be an odd number. ParityOdd ) // To implement the UART interface for a board, you must declare a concrete type as follows: // // type UART struct { // Buffer *RingBuffer // } // // You can also add additional members to this struct depending on your implementation, // but the *RingBuffer is required. // When you are declaring your UARTs for your board, make sure that you also declare the // RingBuffer using the NewRingBuffer() function when you declare your UART: // // UART{Buffer: NewRingBuffer()} // // Read from the RX buffer. func (uart *UART) Read(data []byte) (n int, err error) { // check if RX buffer is empty size := uart.Buffered() if size == 0 { return 0, nil } // Make sure we do not read more from buffer than the data slice can hold. if len(data) < size { size = len(data) } // only read number of bytes used from buffer for i := 0; i < size; i++ { v, _ := uart.ReadByte() data[i] = v } return size, nil } // WriteByte writes a byte of data over the UART's Tx. // This function blocks until the data is finished being sent. func (uart *UART) WriteByte(c byte) error { err := uart.writeByte(c) if err != nil { return err } uart.flush() // flush() blocks until all data has been transmitted. return nil } // Write data over the UART's Tx. // This function blocks until the data is finished being sent. func (uart *UART) Write(data []byte) (n int, err error) { for i, v := range data { err = uart.writeByte(v) if err != nil { return i, err } } uart.flush() // flush() blocks until all data has been transmitted. return len(data), nil } // ReadByte reads a single byte from the RX buffer. // If there is no data in the buffer, returns an error. func (uart *UART) ReadByte() (byte, error) { // check if RX buffer is empty buf, ok := uart.Buffer.Get() if !ok { return 0, errUARTBufferEmpty } return buf, nil } // Buffered returns the number of bytes currently stored in the RX buffer. func (uart *UART) Buffered() int { return int(uart.Buffer.Used()) } // Receive handles adding data to the UART's data buffer. // Usually called by the IRQ handler for a machine. func (uart *UART) Receive(data byte) { uart.Buffer.Put(data) } ================================================ FILE: src/machine/usb/adc/doc.go ================================================ // package adc is for USB Audio Device Class devices. package adc ================================================ FILE: src/machine/usb/adc/midi/buffer.go ================================================ package midi import ( "runtime/volatile" ) const bufferSize = 128 // RingBuffer is ring buffer implementation inspired by post at // https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php type RingBuffer struct { rxbuffer [bufferSize][4]byte head volatile.Register8 tail volatile.Register8 } // NewRingBuffer returns a new ring buffer. func NewRingBuffer() *RingBuffer { return &RingBuffer{} } // Used returns how many bytes in buffer have been used. func (rb *RingBuffer) Used() uint8 { return uint8(rb.head.Get() - rb.tail.Get()) } // Put stores a byte in the buffer. If the buffer is already // full, the method will return false. func (rb *RingBuffer) Put(val []byte) bool { if rb.Used() != bufferSize { rb.head.Set(rb.head.Get() + 1) copy(rb.rxbuffer[rb.head.Get()%bufferSize][:], val) return true } return false } // Get returns a byte from the buffer. If the buffer is empty, // the method will return a false as the second value. func (rb *RingBuffer) Get() ([]byte, bool) { if rb.Used() != 0 { rb.tail.Set(rb.tail.Get() + 1) return rb.rxbuffer[rb.tail.Get()%bufferSize][:], true } return nil, false } // Clear resets the head and tail pointer to zero. func (rb *RingBuffer) Clear() { rb.head.Set(0) rb.tail.Set(0) } ================================================ FILE: src/machine/usb/adc/midi/messages.go ================================================ package midi import ( "errors" ) // From USB-MIDI section 4.1 "Code Index Number (CIN) Classifications" const ( CINSystemCommon2 = 0x2 CINSystemCommon3 = 0x3 CINSysExStart = 0x4 CINSysExEnd1 = 0x5 CINSysExEnd2 = 0x6 CINSysExEnd3 = 0x7 CINNoteOff = 0x8 CINNoteOn = 0x9 CINPoly = 0xA CINControlChange = 0xB CINProgramChange = 0xC CINChannelPressure = 0xD CINPitchBendChange = 0xE CINSingleByte = 0xF ) // Standard MIDI channel messages const ( MsgNoteOff = 0x80 MsgNoteOn = 0x90 MsgPolyAftertouch = 0xA0 MsgControlChange = 0xB0 MsgProgramChange = 0xC0 MsgChannelAftertouch = 0xD0 MsgPitchBend = 0xE0 MsgSysExStart = 0xF0 MsgSysExEnd = 0xF7 ) // Standard MIDI control change messages const ( CCModulationWheel = 0x01 CCBreathController = 0x02 CCFootPedal = 0x04 CCPortamentoTime = 0x05 CCDataEntry = 0x06 CCVolume = 0x07 CCBalance = 0x08 CCPan = 0x0A CCExpression = 0x0B CCEffectControl1 = 0x0C CCEffectControl2 = 0x0D CCGeneralPurpose1 = 0x10 CCGeneralPurpose2 = 0x11 CCGeneralPurpose3 = 0x12 CCGeneralPurpose4 = 0x13 CCBankSelect = 0x20 CCModulationDepthRange = 0x21 CCBreathControllerDepth = 0x22 CCFootPedalDepth = 0x24 CCEffectsLevel = 0x5B CCTremeloLevel = 0x5C CCChorusLevel = 0x5D CCCelesteLevel = 0x5E CCPhaserLevel = 0x5F CCDataIncrement = 0x60 CCDataDecrement = 0x61 CCNRPNLSB = 0x62 CCNRPNMSB = 0x63 CCRPNLSB = 0x64 CCRPNMSB = 0x65 CCAllSoundOff = 0x78 CCResetAllControllers = 0x79 CCAllNotesOff = 0x7B CCChannelVolume = 0x7F ) var ( errInvalidMIDICable = errors.New("invalid MIDI cable") errInvalidMIDIChannel = errors.New("invalid MIDI channel") errInvalidMIDIVelocity = errors.New("invalid MIDI velocity") errInvalidMIDIControl = errors.New("invalid MIDI control number") errInvalidMIDIControlValue = errors.New("invalid MIDI control value") errInvalidMIDIPatch = errors.New("invalid MIDI patch number") errInvalidMIDIPitchBend = errors.New("invalid MIDI pitch bend value") errInvalidMIDISysExData = errors.New("invalid MIDI SysEx data") ) // NoteOn sends a channel note on message. // The cable parameter is the cable number 0-15. // The channel parameter is the MIDI channel number 1-16. func (m *midi) NoteOn(cable, channel uint8, note Note, velocity uint8) error { switch { case cable > 15: return errInvalidMIDICable case channel == 0 || channel > 16: return errInvalidMIDIChannel case velocity > 127: return errInvalidMIDIVelocity } m.msg[0], m.msg[1], m.msg[2], m.msg[3] = ((cable&0xf)<<4)|CINNoteOn, MsgNoteOn|((channel-1)&0xf), byte(note)&0x7f, velocity&0x7f _, err := m.Write(m.msg[:]) return err } // NoteOff sends a channel note off message. // The cable parameter is the cable number 0-15. // The channel parameter is the MIDI channel number 1-16. func (m *midi) NoteOff(cable, channel uint8, note Note, velocity uint8) error { switch { case cable > 15: return errInvalidMIDICable case channel == 0 || channel > 16: return errInvalidMIDIChannel case velocity > 127: return errInvalidMIDIVelocity } m.msg[0], m.msg[1], m.msg[2], m.msg[3] = ((cable&0xf)<<4)|CINNoteOff, MsgNoteOff|((channel-1)&0xf), byte(note)&0x7f, velocity&0x7f _, err := m.Write(m.msg[:]) return err } // ControlChange sends a channel continuous controller message. // The cable parameter is the cable number 0-15. // The channel parameter is the MIDI channel number 1-16. // The control parameter is the controller number 0-127. // The value parameter is the controller value 0-127. func (m *midi) ControlChange(cable, channel, control, value uint8) error { switch { case cable > 15: return errInvalidMIDICable case channel == 0 || channel > 16: return errInvalidMIDIChannel case control > 127: return errInvalidMIDIControl case value > 127: return errInvalidMIDIControlValue } m.msg[0], m.msg[1], m.msg[2], m.msg[3] = ((cable&0xf)<<4)|CINControlChange, MsgControlChange|((channel-1)&0xf), control&0x7f, value&0x7f _, err := m.Write(m.msg[:]) return err } // ProgramChange sends a channel program change message. // The cable parameter is the cable number 0-15. // The channel parameter is the MIDI channel number 1-16. // The patch parameter is the program number 0-127. func (m *midi) ProgramChange(cable, channel uint8, patch uint8) error { switch { case cable > 15: return errInvalidMIDICable case channel == 0 || channel > 16: return errInvalidMIDIChannel case patch > 127: return errInvalidMIDIPatch } m.msg[0], m.msg[1], m.msg[2] = ((cable&0xf)<<4)|CINProgramChange, MsgProgramChange|((channel-1)&0xf), patch&0x7f _, err := m.Write(m.msg[:3]) return err } // PitchBend sends a channel pitch bend message. // The cable parameter is the cable number 0-15. // The channel parameter is the MIDI channel number 1-16. // The bend parameter is the 14-bit pitch bend value (maximum 0x3FFF). // Setting bend above 0x2000 (up to 0x3FFF) will increase the pitch. // Setting bend below 0x2000 (down to 0x0000) will decrease the pitch. func (m *midi) PitchBend(cable, channel uint8, bend uint16) error { switch { case cable > 15: return errInvalidMIDICable case channel == 0 || channel > 16: return errInvalidMIDIChannel case bend > 0x3FFF: return errInvalidMIDIPitchBend } m.msg[0], m.msg[1], m.msg[2], m.msg[3] = ((cable&0xf)<<4)|CINPitchBendChange, MsgPitchBend|((channel-1)&0xf), byte(bend&0x7f), byte(bend>>7)&0x7f _, err := m.Write(m.msg[:]) return err } // SysEx sends a System Exclusive message. // The cable parameter is the cable number 0-15. // The data parameter is a slice with the data to send. // It needs to start with the manufacturer ID, which is either // 1 or 3 bytes in length. // The data slice should not include the SysEx start (0xF0) or // end (0xF7) bytes, only the data in between. func (m *midi) SysEx(cable uint8, data []byte) error { switch { case cable > 15: return errInvalidMIDICable case len(data) < 3: return errInvalidMIDISysExData } // write start m.msg[0], m.msg[1] = ((cable&0xf)<<4)|CINSysExStart, MsgSysExStart m.msg[2], m.msg[3] = data[0], data[1] if _, err := m.Write(m.msg[:]); err != nil { return err } // write middle i := 2 for ; i < len(data)-2; i += 3 { m.msg[0], m.msg[1] = ((cable&0xf)<<4)|CINSysExStart, data[i] m.msg[2], m.msg[3] = data[i+1], data[i+2] if _, err := m.Write(m.msg[:]); err != nil { return err } } // write end switch len(data) - i { case 2: m.msg[0], m.msg[1] = ((cable&0xf)<<4)|CINSysExEnd3, data[i] m.msg[2], m.msg[3] = data[i+1], MsgSysExEnd case 1: m.msg[0], m.msg[1] = ((cable&0xf)<<4)|CINSysExEnd2, data[i] m.msg[2], m.msg[3] = MsgSysExEnd, 0 case 0: m.msg[0], m.msg[1] = ((cable&0xf)<<4)|CINSysExEnd1, MsgSysExEnd m.msg[2], m.msg[3] = 0, 0 } if _, err := m.Write(m.msg[:]); err != nil { return err } return nil } ================================================ FILE: src/machine/usb/adc/midi/midi.go ================================================ package midi import ( "machine" "machine/usb" "machine/usb/descriptor" ) const ( midiEndpointOut = usb.MIDI_ENDPOINT_OUT // from PC midiEndpointIn = usb.MIDI_ENDPOINT_IN // to PC ) var Midi *midi type midi struct { msg [4]byte buf *RingBuffer rxHandler func([]byte) txHandler func() waitTxc bool } func init() { if Midi == nil { Midi = newMidi() } } // New returns the USB MIDI port. // Deprecated, better to just use Port() func New() *midi { return Port() } // Port returns the USB midi port. func Port() *midi { return Midi } func newMidi() *midi { m := &midi{ buf: NewRingBuffer(), } machine.ConfigureUSBEndpoint(descriptor.CDCMIDI, []usb.EndpointConfig{ { Index: usb.MIDI_ENDPOINT_OUT, IsIn: false, Type: usb.ENDPOINT_TYPE_BULK, RxHandler: m.RxHandler, }, { Index: usb.MIDI_ENDPOINT_IN, IsIn: true, Type: usb.ENDPOINT_TYPE_BULK, TxHandler: m.TxHandler, }, }, []usb.SetupConfig{}, ) return m } // SetHandler is now deprecated, please use SetRxHandler(). func (m *midi) SetHandler(rxHandler func([]byte)) { m.SetRxHandler(rxHandler) } // SetRxHandler sets the handler function for incoming MIDI messages. func (m *midi) SetRxHandler(rxHandler func([]byte)) { m.rxHandler = rxHandler } // SetTxHandler sets the handler function for outgoing MIDI messages. func (m *midi) SetTxHandler(txHandler func()) { m.txHandler = txHandler } func (m *midi) Write(b []byte) (n int, err error) { s, e := 0, 0 for s = 0; s < len(b); s += 4 { e = s + 4 if e > len(b) { e = len(b) } m.tx(b[s:e]) } return e, nil } // sendUSBPacket sends a MIDIPacket. func (m *midi) sendUSBPacket(b []byte) { machine.SendUSBInPacket(midiEndpointIn, b) } // from BulkIn func (m *midi) TxHandler() { if m.txHandler != nil { m.txHandler() } m.waitTxc = false if b, ok := m.buf.Get(); ok { m.waitTxc = true m.sendUSBPacket(b) } } func (m *midi) tx(b []byte) { if machine.USBDev.InitEndpointComplete { if m.waitTxc { m.buf.Put(b) } else { m.waitTxc = true m.sendUSBPacket(b) } } } // from BulkOut func (m *midi) RxHandler(b []byte) { if m.rxHandler != nil { m.rxHandler(b) } } ================================================ FILE: src/machine/usb/adc/midi/notes.go ================================================ package midi // Note represents a MIDI note number. For example, Note(69) is A4 or 440Hz. type Note uint8 // Define all the notes in a format similar to the Tone library in the Arduino // IDE. const ( A0 Note = iota + 21 // 27.5Hz AS0 B0 C1 CS1 D1 DS1 E1 F1 FS1 G1 GS1 A1 // 55Hz AS1 B1 C2 CS2 D2 DS2 E2 F2 FS2 G2 GS2 A2 // 110Hz AS2 B2 C3 CS3 D3 DS3 E3 F3 FS3 G3 GS3 A3 // 220Hz AS3 B3 C4 CS4 D4 DS4 E4 F4 FS4 G4 GS4 A4 // 440Hz AS4 B4 C5 CS5 D5 DS5 E5 F5 FS5 G5 GS5 A5 // 880Hz AS5 B5 C6 CS6 D6 DS6 E6 F6 FS6 G6 GS6 A6 // 1760Hz AS6 B6 C7 CS7 D7 DS7 E7 F7 FS7 G7 GS7 A7 // 3520Hz AS7 B7 C8 CS8 D8 DS8 E8 F8 FS8 G8 GS8 A8 // 7040Hz AS8 B8 ) ================================================ FILE: src/machine/usb/cdc/buffer.go ================================================ package cdc import ( "runtime/volatile" ) const rxRingBufferSize = 128 // rxRingBuffer is ring buffer implementation inspired by post at // https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php type rxRingBuffer struct { buffer [rxRingBufferSize]volatile.Register8 head volatile.Register8 tail volatile.Register8 } // NewRxRingBuffer returns a new ring buffer. func NewRxRingBuffer() *rxRingBuffer { return &rxRingBuffer{} } // Used returns how many bytes in buffer have been used. func (rb *rxRingBuffer) Used() uint8 { return uint8(rb.head.Get() - rb.tail.Get()) } // Put stores a byte in the buffer. If the buffer is already // full, the method will return false. func (rb *rxRingBuffer) Put(val byte) bool { if rb.Used() != rxRingBufferSize { rb.head.Set(rb.head.Get() + 1) rb.buffer[rb.head.Get()%rxRingBufferSize].Set(val) return true } return false } // Get returns a byte from the buffer. If the buffer is empty, // the method will return a false as the second value. func (rb *rxRingBuffer) Get() (byte, bool) { if rb.Used() != 0 { rb.tail.Set(rb.tail.Get() + 1) return rb.buffer[rb.tail.Get()%rxRingBufferSize].Get(), true } return 0, false } // Clear resets the head and tail pointer to zero. func (rb *rxRingBuffer) Clear() { rb.head.Set(0) rb.tail.Set(0) } const txRingBufferSize = 8 // txRingBuffer is ring buffer implementation inspired by post at // https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php type txRingBuffer struct { buffer [txRingBufferSize]struct { buf [64]byte size int } head volatile.Register8 tail volatile.Register8 } // NewTxRingBuffer returns a new ring buffer. func NewTxRingBuffer() *txRingBuffer { return &txRingBuffer{} } // Used returns how many bytes in buffer have been used. func (rb *txRingBuffer) Used() uint8 { return uint8(rb.head.Get() - rb.tail.Get()) } // Put stores a byte in the buffer. If the buffer is already // full, the method will return false. func (rb *txRingBuffer) Put(val []byte) bool { if rb.Used() == txRingBufferSize { return false } if rb.Used() == 0 { rb.head.Set(rb.head.Get() + 1) rb.buffer[rb.head.Get()%txRingBufferSize].size = 0 } buf := &rb.buffer[rb.head.Get()%txRingBufferSize] for i := 0; i < len(val); i++ { if buf.size == 64 { // next // TODO: Make sure that data is not corrupted even when the buffer is full rb.head.Set(rb.head.Get() + 1) buf = &rb.buffer[rb.head.Get()%txRingBufferSize] rb.buffer[rb.head.Get()%txRingBufferSize].size = 0 } buf.buf[buf.size] = val[i] buf.size++ } return true } // Get returns a byte from the buffer. If the buffer is empty, // the method will return a false as the second value. func (rb *txRingBuffer) Get() ([]byte, bool) { if rb.Used() != 0 { rb.tail.Set(rb.tail.Get() + 1) size := rb.buffer[rb.tail.Get()%txRingBufferSize].size return rb.buffer[rb.tail.Get()%txRingBufferSize].buf[:size], true } return nil, false } // Clear resets the head and tail pointer to zero. func (rb *txRingBuffer) Clear() { rb.head.Set(0) rb.tail.Set(0) } ================================================ FILE: src/machine/usb/cdc/cdc.go ================================================ package cdc const ( cdcEndpointACM = 1 cdcEndpointOut = 2 cdcEndpointIn = 3 ) // New returns USBCDC struct. func New() *USBCDC { if USB == nil { USB = &USBCDC{ rxBuffer: NewRxRingBuffer(), txBuffer: NewTxRingBuffer(), } } return USB } const ( // bmRequestType usb_REQUEST_HOSTTODEVICE = 0x00 usb_REQUEST_DEVICETOHOST = 0x80 usb_REQUEST_DIRECTION = 0x80 usb_REQUEST_STANDARD = 0x00 usb_REQUEST_CLASS = 0x20 usb_REQUEST_VENDOR = 0x40 usb_REQUEST_TYPE = 0x60 usb_REQUEST_DEVICE = 0x00 usb_REQUEST_INTERFACE = 0x01 usb_REQUEST_ENDPOINT = 0x02 usb_REQUEST_OTHER = 0x03 usb_REQUEST_RECIPIENT = 0x1F usb_REQUEST_DEVICETOHOST_CLASS_INTERFACE = (usb_REQUEST_DEVICETOHOST | usb_REQUEST_CLASS | usb_REQUEST_INTERFACE) usb_REQUEST_HOSTTODEVICE_CLASS_INTERFACE = (usb_REQUEST_HOSTTODEVICE | usb_REQUEST_CLASS | usb_REQUEST_INTERFACE) usb_REQUEST_DEVICETOHOST_STANDARD_INTERFACE = (usb_REQUEST_DEVICETOHOST | usb_REQUEST_STANDARD | usb_REQUEST_INTERFACE) // CDC Class requests usb_CDC_SET_LINE_CODING = 0x20 usb_CDC_GET_LINE_CODING = 0x21 usb_CDC_SET_CONTROL_LINE_STATE = 0x22 usb_CDC_SEND_BREAK = 0x23 usb_CDC_V1_10 = 0x0110 usb_CDC_COMMUNICATION_INTERFACE_CLASS = 0x02 usb_CDC_CALL_MANAGEMENT = 0x01 usb_CDC_ABSTRACT_CONTROL_MODEL = 0x02 usb_CDC_HEADER = 0x00 usb_CDC_ABSTRACT_CONTROL_MANAGEMENT = 0x02 usb_CDC_UNION = 0x06 usb_CDC_CS_INTERFACE = 0x24 usb_CDC_CS_ENDPOINT = 0x25 usb_CDC_DATA_INTERFACE_CLASS = 0x0A usb_CDC_LINESTATE_DTR = 0x01 usb_CDC_LINESTATE_RTS = 0x02 ) ================================================ FILE: src/machine/usb/cdc/doc.go ================================================ // package cdc is for USB Communication Device Class devices. package cdc ================================================ FILE: src/machine/usb/cdc/usbcdc.go ================================================ package cdc import ( "errors" "machine" "machine/usb" "runtime/interrupt" ) var ( ErrBufferEmpty = errors.New("USB-CDC buffer empty") ) const cdcLineInfoSize = 7 type cdcLineInfo struct { dwDTERate uint32 bCharFormat uint8 bParityType uint8 bDataBits uint8 lineState uint8 } // Read from the RX buffer. func (usbcdc *USBCDC) Read(data []byte) (n int, err error) { // check if RX buffer is empty size := usbcdc.Buffered() if size == 0 { return 0, nil } // Make sure we do not read more from buffer than the data slice can hold. if len(data) < size { size = len(data) } // only read number of bytes used from buffer for i := 0; i < size; i++ { v, _ := usbcdc.ReadByte() data[i] = v } return size, nil } // ReadByte reads a single byte from the RX buffer. // If there is no data in the buffer, returns an error. func (usbcdc *USBCDC) ReadByte() (byte, error) { // check if RX buffer is empty buf, ok := usbcdc.rxBuffer.Get() if !ok { return 0, ErrBufferEmpty } return buf, nil } // Buffered returns the number of bytes currently stored in the RX buffer. func (usbcdc *USBCDC) Buffered() int { return int(usbcdc.rxBuffer.Used()) } // Receive handles adding data to the UART's data buffer. // Usually called by the IRQ handler for a machine. func (usbcdc *USBCDC) Receive(data byte) { usbcdc.rxBuffer.Put(data) } // USBCDC is the USB CDC aka serial over USB interface. type USBCDC struct { rxBuffer *rxRingBuffer txBuffer *txRingBuffer waitTxc bool } var ( // USB is a USB CDC interface. USB *USBCDC usbLineInfo = cdcLineInfo{115200, 0x00, 0x00, 0x08, 0x00} ) // Configure the USB CDC interface. The config is here for compatibility with the UART interface. func (usbcdc *USBCDC) Configure(config machine.UARTConfig) error { return nil } // Flush flushes buffered data. func (usbcdc *USBCDC) Flush() { mask := interrupt.Disable() if b, ok := usbcdc.txBuffer.Get(); ok { machine.SendUSBInPacket(cdcEndpointIn, b) } else { usbcdc.waitTxc = false } interrupt.Restore(mask) } // Write data to the USBCDC. func (usbcdc *USBCDC) Write(data []byte) (n int, err error) { if usbLineInfo.lineState > 0 { mask := interrupt.Disable() usbcdc.txBuffer.Put(data) if !usbcdc.waitTxc { usbcdc.waitTxc = true usbcdc.Flush() } interrupt.Restore(mask) } return len(data), nil } // WriteByte writes a byte of data to the USB CDC interface. func (usbcdc *USBCDC) WriteByte(c byte) error { usbcdc.Write([]byte{c}) return nil } func (usbcdc *USBCDC) DTR() bool { return (usbLineInfo.lineState & usb_CDC_LINESTATE_DTR) > 0 } func (usbcdc *USBCDC) RTS() bool { return (usbLineInfo.lineState & usb_CDC_LINESTATE_RTS) > 0 } func cdcCallbackRx(b []byte) { for i := range b { USB.Receive(b[i]) } } var cdcSetupBuff [cdcLineInfoSize]byte func cdcSetup(setup usb.Setup) bool { if setup.BmRequestType == usb_REQUEST_DEVICETOHOST_CLASS_INTERFACE { if setup.BRequest == usb_CDC_GET_LINE_CODING { cdcSetupBuff[0] = byte(usbLineInfo.dwDTERate) cdcSetupBuff[1] = byte(usbLineInfo.dwDTERate >> 8) cdcSetupBuff[2] = byte(usbLineInfo.dwDTERate >> 16) cdcSetupBuff[3] = byte(usbLineInfo.dwDTERate >> 24) cdcSetupBuff[4] = byte(usbLineInfo.bCharFormat) cdcSetupBuff[5] = byte(usbLineInfo.bParityType) cdcSetupBuff[6] = byte(usbLineInfo.bDataBits) machine.SendUSBInPacket(0, cdcSetupBuff[:]) return true } } if setup.BmRequestType == usb_REQUEST_HOSTTODEVICE_CLASS_INTERFACE { if setup.BRequest == usb_CDC_SET_LINE_CODING { b, err := machine.ReceiveUSBControlPacket() if err != nil { return false } usbLineInfo.dwDTERate = uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 usbLineInfo.bCharFormat = b[4] usbLineInfo.bParityType = b[5] usbLineInfo.bDataBits = b[6] } if setup.BRequest == usb_CDC_SET_CONTROL_LINE_STATE { usbLineInfo.lineState = setup.WValueL } if setup.BRequest == usb_CDC_SET_LINE_CODING || setup.BRequest == usb_CDC_SET_CONTROL_LINE_STATE { // auto-reset into the bootloader if usbLineInfo.dwDTERate == 1200 && usbLineInfo.lineState&usb_CDC_LINESTATE_DTR == 0 { machine.EnterBootloader() } else { // TODO: cancel any reset } machine.SendZlp() } if setup.BRequest == usb_CDC_SEND_BREAK { // TODO: something with this value? // breakValue = ((uint16_t)setup.wValueH << 8) | setup.wValueL; // return false; machine.SendZlp() } return true } return false } func EnableUSBCDC() { machine.USBCDC = New() machine.EnableCDC(USB.Flush, cdcCallbackRx, cdcSetup) } ================================================ FILE: src/machine/usb/config.go ================================================ package usb type EndpointConfig struct { Index uint8 IsIn bool TxHandler func() RxHandler func([]byte) DelayRxHandler func([]byte) bool StallHandler func(Setup) bool Type uint8 } type SetupConfig struct { Index uint8 Handler func(Setup) bool } ================================================ FILE: src/machine/usb/descriptor/cdc.go ================================================ package descriptor const ( cdcFunctionalHeader = 0 cdcFunctionalCallManagement = 0x1 cdcFunctionalACM = 0x2 cdcFunctionalDirect = 0x3 cdcFunctionalRinger = 0x4 cdcFunctionalCall = 0x5 cdcFunctionalUnion = 0x6 cdcFunctionalCountry = 0x7 cdcFunctionalOperational = 0x8 cdcFunctionalUSB = 0x9 cdcFunctionalNetwork = 0xa cdcFunctionalProtocol = 0xb cdcFunctionalExtension = 0xc cdcFunctionalMulti = 0xd cdcFunctionalCAPI = 0xe cdcFunctionalEthernet = 0xf cdcFunctionalATM = 0x10 ) var classSpecificCDCHeader = [classSpecificTypeLen]byte{ classSpecificTypeLen, TypeClassSpecific, cdcFunctionalHeader, 0x10, // 0x1, // } var ClassSpecificCDCHeader = ClassSpecificType{ data: classSpecificCDCHeader[:], } var classSpecificCDCCallManagement = [classSpecificTypeLen]byte{ classSpecificTypeLen, TypeClassSpecific, cdcFunctionalCallManagement, 0x0, // 0x1, // } var ClassSpecificCDCCallManagement = ClassSpecificType{ data: classSpecificCDCCallManagement[:], } var classSpecificCDCACM = [classSpecificTypeLen]byte{ 4, TypeClassSpecific, cdcFunctionalACM, 0x2, // } var ClassSpecificCDCACM = ClassSpecificType{ data: classSpecificCDCACM[:], } var classSpecificCDCUnion = [classSpecificTypeLen]byte{ classSpecificTypeLen, TypeClassSpecific, cdcFunctionalUnion, 0x0, // 0x1, // } var ClassSpecificCDCUnion = ClassSpecificType{ data: classSpecificCDCUnion[:], } var interfaceAssociationCDC = [interfaceAssociationTypeLen]byte{ interfaceAssociationTypeLen, TypeInterfaceAssociation, 0x00, // FirstInterface 0x02, // InterfaceCount 0x02, // FunctionClass 0x02, // FunctionSubClass 0x01, // FunctionProtocol 0x00, // Function } var InterfaceAssociationCDC = InterfaceAssociationType{ data: interfaceAssociationCDC[:], } var deviceCDC = [deviceTypeLen]byte{ deviceTypeLen, TypeDevice, 0x00, 0x02, // USB version 0xef, // device class 0x02, // device subclass 0x01, // protocol 0x40, // maxpacketsize 0x86, 0x28, // vendor id 0x2d, 0x80, // product id 0x00, 0x01, // device 0x01, // manufacturer 0x02, // product 0x03, // SerialNumber 0x01, // NumConfigurations } var DeviceCDC = DeviceType{ data: deviceCDC[:], } var configurationCDC = [configurationTypeLen]byte{ configurationTypeLen, TypeConfiguration, 0x4b, 0x00, // adjust length as needed 0x02, // number of interfaces 0x01, // configuration value 0x00, // index to string description 0xa0, // attributes 0x32, // maxpower } var ConfigurationCDC = ConfigurationType{ data: configurationCDC[:], } var interfaceCDCControl = [interfaceTypeLen]byte{ interfaceTypeLen, TypeInterface, 0x00, // InterfaceNumber 0x00, // AlternateSetting 0x01, // NumEndpoints 0x02, // InterfaceClass 0x02, // InterfaceSubClass 0x01, // InterfaceProtocol 0x00, // Interface } var InterfaceCDCControl = InterfaceType{ data: interfaceCDCControl[:], } var interfaceCDCData = [interfaceTypeLen]byte{ interfaceTypeLen, TypeInterface, 0x01, // InterfaceNumber 0x00, // AlternateSetting 0x02, // NumEndpoints 0x0a, // InterfaceClass 0x00, // InterfaceSubClass 0x00, // InterfaceProtocol 0x00, // Interface } var InterfaceCDCData = InterfaceType{ data: interfaceCDCData[:], } var CDC = Descriptor{ Device: DeviceCDC.Bytes(), Configuration: Append([][]byte{ ConfigurationCDC.Bytes(), InterfaceAssociationCDC.Bytes(), InterfaceCDCControl.Bytes(), ClassSpecificCDCHeader.Bytes(), ClassSpecificCDCCallManagement.Bytes(), ClassSpecificCDCACM.Bytes(), ClassSpecificCDCUnion.Bytes(), EndpointEP1IN.Bytes(), InterfaceCDCData.Bytes(), EndpointEP2OUT.Bytes(), EndpointEP3IN.Bytes(), }), } ================================================ FILE: src/machine/usb/descriptor/classspecific.go ================================================ package descriptor const ( classSpecificTypeLen = 5 ) type ClassSpecificType struct { data []byte } func (d ClassSpecificType) Bytes() []byte { return d.data[:d.data[0]] } func (d ClassSpecificType) Length(v uint8) { d.data[0] = byte(v) } ================================================ FILE: src/machine/usb/descriptor/configuration.go ================================================ package descriptor import ( "internal/binary" ) const ( configurationTypeLen = 9 ) type ConfigurationType struct { data []byte } func (d ConfigurationType) Bytes() []byte { return d.data } func (d ConfigurationType) Length(v uint8) { d.data[0] = byte(v) } func (d ConfigurationType) Type(v uint8) { d.data[1] = byte(v) } func (d ConfigurationType) TotalLength(v uint16) { binary.LittleEndian.PutUint16(d.data[2:4], v) } func (d ConfigurationType) NumInterfaces(v uint8) { d.data[4] = byte(v) } func (d ConfigurationType) ConfigurationValue(v uint8) { d.data[5] = byte(v) } func (d ConfigurationType) Configuration(v uint8) { d.data[6] = byte(v) } func (d ConfigurationType) Attributes(v uint8) { d.data[7] = byte(v) } func (d ConfigurationType) MaxPower(v uint8) { d.data[8] = byte(v) } ================================================ FILE: src/machine/usb/descriptor/descriptor.go ================================================ package descriptor import ( "runtime/volatile" ) const ( TypeDevice = 0x1 TypeConfiguration = 0x2 TypeString = 0x3 TypeInterface = 0x4 TypeEndpoint = 0x5 TypeDeviceQualifier = 0x6 TypeInterfaceAssociation = 0xb TypeClassHID = 0x21 TypeHIDReport = 0x22 TypeClassSpecific = 0x24 TypeClassSpecificEndpoint = 0x25 ) // DeviceDescBank is the USB device endpoint . type DeviceDescBank struct { ADDR volatile.Register32 PCKSIZE volatile.Register32 EXTREG volatile.Register16 STATUS_BK volatile.Register8 _reserved [5]volatile.Register8 } type Device struct { DeviceDescBank [2]DeviceDescBank } type Descriptor struct { Device []byte Configuration []byte HID map[uint16][]byte } func (d *Descriptor) Configure(idVendor, idProduct uint16) { dev := DeviceType{d.Device} dev.VendorID(idVendor) dev.ProductID(idProduct) conf := ConfigurationType{d.Configuration} conf.TotalLength(uint16(len(d.Configuration))) } func Append[T any](slices [][]T) []T { var size, pos int for _, s := range slices { size += len(s) } result := make([]T, size) for _, s := range slices { pos += copy(result[pos:], s) } return result } ================================================ FILE: src/machine/usb/descriptor/device.go ================================================ package descriptor import ( "internal/binary" ) const ( deviceTypeLen = 18 ) type DeviceType struct { data []byte } func (d DeviceType) Bytes() []byte { return d.data } func (d DeviceType) Length(v uint8) { d.data[0] = byte(v) } func (d DeviceType) Type(v uint8) { d.data[1] = byte(v) } func (d DeviceType) USB(v uint16) { binary.LittleEndian.PutUint16(d.data[2:4], v) } func (d DeviceType) DeviceClass(v uint8) { d.data[4] = byte(v) } func (d DeviceType) DeviceSubClass(v uint8) { d.data[5] = byte(v) } func (d DeviceType) DeviceProtocol(v uint8) { d.data[6] = byte(v) } func (d DeviceType) MaxPacketSize0(v uint8) { d.data[7] = byte(v) } func (d DeviceType) VendorID(v uint16) { binary.LittleEndian.PutUint16(d.data[8:10], v) } func (d DeviceType) ProductID(v uint16) { binary.LittleEndian.PutUint16(d.data[10:12], v) } func (d DeviceType) Device(v uint16) { binary.LittleEndian.PutUint16(d.data[12:14], v) } func (d DeviceType) Manufacturer(v uint8) { d.data[14] = byte(v) } func (d DeviceType) Product(v uint8) { d.data[15] = byte(v) } func (d DeviceType) SerialNumber(v uint8) { d.data[16] = byte(v) } func (d DeviceType) NumConfigurations(v uint8) { d.data[17] = byte(v) } ================================================ FILE: src/machine/usb/descriptor/doc.go ================================================ // package descriptor is for the USB descriptor definitions. // For the actual implementations, see the individual packages. package descriptor ================================================ FILE: src/machine/usb/descriptor/endpoint.go ================================================ package descriptor import ( "internal/binary" ) /* Endpoint Descriptor USB 2.0 Specification: 9.6.6 Endpoint */ const ( TransferTypeControl uint8 = iota TransferTypeIsochronous TransferTypeBulk TransferTypeInterrupt ) var endpointEP1IN = [endpointTypeLen]byte{ endpointTypeLen, TypeEndpoint, 0x81, // EndpointAddress 0x03, // Attributes 0x10, // MaxPacketSizeL 0x00, // MaxPacketSizeH 0x10, // Interval } var EndpointEP1IN = EndpointType{ data: endpointEP1IN[:], } var endpointEP2OUT = [endpointTypeLen]byte{ endpointTypeLen, TypeEndpoint, 0x02, // EndpointAddress 0x02, // Attributes 0x40, // MaxPacketSizeL 0x00, // MaxPacketSizeH 0x00, // Interval } var EndpointEP2OUT = EndpointType{ data: endpointEP2OUT[:], } var endpointEP3IN = [endpointTypeLen]byte{ endpointTypeLen, TypeEndpoint, 0x83, // EndpointAddress 0x02, // Attributes 0x40, // MaxPacketSizeL 0x00, // MaxPacketSizeH 0x00, // Interval } var EndpointEP3IN = EndpointType{ data: endpointEP3IN[:], } var endpointEP4IN = [endpointTypeLen]byte{ endpointTypeLen, TypeEndpoint, 0x84, // EndpointAddress 0x03, // Attributes 0x40, // MaxPacketSizeL 0x00, // MaxPacketSizeH 0x01, // Interval } var EndpointEP4IN = EndpointType{ data: endpointEP4IN[:], } var endpointEP5OUT = [endpointTypeLen]byte{ endpointTypeLen, TypeEndpoint, 0x05, // EndpointAddress 0x03, // Attributes 0x40, // MaxPacketSizeL 0x00, // MaxPacketSizeH 0x01, // Interval } var EndpointEP5OUT = EndpointType{ data: endpointEP5OUT[:], } // Mass Storage Class bulk in endpoint var endpointMSCIN = [endpointTypeLen]byte{ endpointTypeLen, TypeEndpoint, 0x86, // EndpointAddress TransferTypeBulk, // Attributes 0x40, // MaxPacketSizeL (64 bytes) 0x00, // MaxPacketSizeH 0x00, // Interval } var EndpointMSCIN = EndpointType{ data: endpointMSCIN[:], } // Mass Storage Class bulk out endpoint var endpointMSCOUT = [endpointTypeLen]byte{ endpointTypeLen, TypeEndpoint, 0x07, // EndpointAddress TransferTypeBulk, // Attributes 0x40, // MaxPacketSizeL (64 bytes) 0x00, // MaxPacketSizeH 0x00, // Interval } var EndpointMSCOUT = EndpointType{ data: endpointMSCOUT[:], } const ( endpointTypeLen = 7 ) type EndpointType struct { data []byte } func (d EndpointType) Bytes() []byte { return d.data } func (d EndpointType) Length(v uint8) { d.data[0] = byte(v) } func (d EndpointType) Type(v uint8) { d.data[1] = byte(v) } func (d EndpointType) EndpointAddress(v uint8) { d.data[2] = byte(v) } func (d EndpointType) Attributes(v uint8) { d.data[3] = byte(v) } func (d EndpointType) MaxPacketSize(v uint16) { binary.LittleEndian.PutUint16(d.data[4:6], v) } func (d EndpointType) Interval(v uint8) { d.data[6] = byte(v) } func (d EndpointType) GetMaxPacketSize() uint16 { return binary.LittleEndian.Uint16(d.data[4:6]) } ================================================ FILE: src/machine/usb/descriptor/hid.go ================================================ package descriptor import ( "errors" "internal/binary" "internal/bytealg" ) var configurationCDCHID = [configurationTypeLen]byte{ configurationTypeLen, TypeConfiguration, 0x64, 0x00, // adjust length as needed 0x03, // number of interfaces 0x01, // configuration value 0x00, // index to string description 0xa0, // attributes 0x32, // maxpower } var ConfigurationCDCHID = ConfigurationType{ data: configurationCDCHID[:], } var interfaceHID = [interfaceTypeLen]byte{ interfaceTypeLen, TypeInterface, 0x02, // InterfaceNumber 0x00, // AlternateSetting 0x02, // NumEndpoints 0x03, // InterfaceClass 0x00, // InterfaceSubClass 0x00, // InterfaceProtocol 0x00, // Interface } var InterfaceHID = InterfaceType{ data: interfaceHID[:], } const ( ClassHIDTypeLen = 9 ) type ClassHIDType struct { data []byte } func (d ClassHIDType) Bytes() []byte { return d.data[:] } func (d ClassHIDType) Length(v uint8) { d.data[0] = byte(v) } func (d ClassHIDType) Type(v uint8) { d.data[1] = byte(v) } func (d ClassHIDType) HID(v uint16) { binary.LittleEndian.PutUint16(d.data[2:4], v) } func (d ClassHIDType) CountryCode(v uint8) { d.data[4] = byte(v) } func (d ClassHIDType) NumDescriptors(v uint8) { d.data[5] = byte(v) } func (d ClassHIDType) ClassType(v uint8) { d.data[6] = byte(v) } func (d ClassHIDType) ClassLength(v uint16) { binary.LittleEndian.PutUint16(d.data[7:9], v) } var errNoClassHIDFound = errors.New("no classHID found") // FindClassHIDType tries to find the ClassHID class in the descriptor. func FindClassHIDType(des, class []byte) (ClassHIDType, error) { if len(des) < ClassHIDTypeLen || len(class) == 0 { return ClassHIDType{}, errNoClassHIDFound } // search only for ClassHIDType without the ClassLength, // in case it has already been set. idx := bytealg.Index(des, class[:ClassHIDTypeLen-2]) if idx == -1 { return ClassHIDType{}, errNoClassHIDFound } return ClassHIDType{data: des[idx : idx+ClassHIDTypeLen]}, nil } var classHID = [ClassHIDTypeLen]byte{ ClassHIDTypeLen, TypeClassHID, 0x11, // HID version L 0x01, // HID version H 0x00, // CountryCode 0x01, // NumDescriptors 0x22, // ClassType 0x91, // ClassLength L 0x00, // ClassLength H } var ClassHID = ClassHIDType{ data: classHID[:], } var CDCHID = Descriptor{ Device: DeviceCDC.Bytes(), Configuration: Append([][]byte{ ConfigurationCDCHID.Bytes(), InterfaceAssociationCDC.Bytes(), InterfaceCDCControl.Bytes(), ClassSpecificCDCHeader.Bytes(), ClassSpecificCDCACM.Bytes(), ClassSpecificCDCUnion.Bytes(), ClassSpecificCDCCallManagement.Bytes(), EndpointEP1IN.Bytes(), InterfaceCDCData.Bytes(), EndpointEP2OUT.Bytes(), EndpointEP3IN.Bytes(), InterfaceHID.Bytes(), ClassHID.Bytes(), EndpointEP4IN.Bytes(), EndpointEP5OUT.Bytes(), }), HID: map[uint16][]byte{ 2: Append([][]byte{ // Update ClassLength in classHID whenever the array length is modified! HIDUsagePageGenericDesktop, HIDUsageDesktopKeyboard, HIDCollectionApplication, HIDReportID(2), HIDUsagePageKeyboard, HIDUsageMinimum(224), HIDUsageMaximum(231), HIDLogicalMinimum(0), HIDLogicalMaximum(1), HIDReportSize(1), HIDReportCount(8), HIDInputDataVarAbs, HIDReportCount(1), HIDReportSize(8), HIDInputConstVarAbs, HIDReportCount(3), HIDReportSize(1), HIDUsagePageLED, HIDUsageMinimum(1), HIDUsageMaximum(3), HIDOutputDataVarAbs, HIDReportCount(5), HIDReportSize(1), HIDOutputConstVarAbs, HIDReportCount(6), HIDReportSize(8), HIDLogicalMinimum(0), HIDLogicalMaximum(255), HIDUsagePageKeyboard, HIDUsageMinimum(0), HIDUsageMaximum(255), HIDInputDataAryAbs, HIDCollectionEnd, HIDUsagePageGenericDesktop, HIDUsageDesktopMouse, HIDCollectionApplication, HIDUsageDesktopPointer, HIDCollectionPhysical, HIDReportID(1), HIDUsagePageButton, HIDUsageMinimum(1), HIDUsageMaximum(5), HIDLogicalMinimum(0), HIDLogicalMaximum(1), HIDReportCount(5), HIDReportSize(1), HIDInputDataVarAbs, HIDReportCount(1), HIDReportSize(3), HIDInputConstVarAbs, HIDUsagePageGenericDesktop, HIDUsageDesktopX, HIDUsageDesktopY, HIDUsageDesktopWheel, HIDLogicalMinimum(-127), HIDLogicalMaximum(127), HIDReportSize(8), HIDReportCount(3), HIDInputDataVarRel, HIDCollectionEnd, HIDCollectionEnd, HIDUsagePageConsumer, HIDUsageConsumerControl, HIDCollectionApplication, HIDReportID(3), HIDLogicalMinimum(0), HIDLogicalMaximum(8191), HIDUsageMinimum(0), HIDUsageMaximum(0x1FFF), HIDReportSize(16), HIDReportCount(1), HIDInputDataAryAbs, HIDCollectionEnd, }), }, } ================================================ FILE: src/machine/usb/descriptor/hidreport.go ================================================ package descriptor import "math" const ( hidUsagePage = 0x04 hidUsage = 0x08 hidLogicalMinimum = 0x14 hidLogicalMaximum = 0x24 hidUsageMinimum = 0x18 hidUsageMaximum = 0x28 hidPhysicalMinimum = 0x34 hidPhysicalMaximum = 0x44 hidUnitExponent = 0x54 hidUnit = 0x64 hidCollection = 0xA0 hidInput = 0x80 hidOutput = 0x90 hidReportSize = 0x74 hidReportCount = 0x94 hidReportID = 0x84 ) const ( hidSizeValue0 = 0x00 hidSizeValue1 = 0x01 hidSizeValue2 = 0x02 hidSizeValue4 = 0x03 ) var ( HIDUsagePageGenericDesktop = HIDUsagePage(0x01) HIDUsagePageSimulationControls = HIDUsagePage(0x02) HIDUsagePageVRControls = HIDUsagePage(0x03) HIDUsagePageSportControls = HIDUsagePage(0x04) HIDUsagePageGameControls = HIDUsagePage(0x05) HIDUsagePageGenericControls = HIDUsagePage(0x06) HIDUsagePageKeyboard = HIDUsagePage(0x07) HIDUsagePageLED = HIDUsagePage(0x08) HIDUsagePageButton = HIDUsagePage(0x09) HIDUsagePageOrdinal = HIDUsagePage(0x0A) HIDUsagePageTelephony = HIDUsagePage(0x0B) HIDUsagePageConsumer = HIDUsagePage(0x0C) HIDUsagePageDigitizers = HIDUsagePage(0x0D) HIDUsagePageHaptics = HIDUsagePage(0x0E) HIDUsagePagePhysicalInput = HIDUsagePage(0x0F) HIDUsagePageUnicode = HIDUsagePage(0x10) HIDUsagePageSoC = HIDUsagePage(0x11) HIDUsagePageEyeHeadTrackers = HIDUsagePage(0x12) HIDUsagePageAuxDisplay = HIDUsagePage(0x14) HIDUsagePageSensors = HIDUsagePage(0x20) HIDUsagePageMedicalInstrument = HIDUsagePage(0x40) HIDUsagePageBrailleDisplay = HIDUsagePage(0x41) HIDUsagePageLighting = HIDUsagePage(0x59) HIDUsagePageMonitor = HIDUsagePage(0x80) HIDUsagePageMonitorEnum = HIDUsagePage(0x81) HIDUsagePageVESA = HIDUsagePage(0x82) HIDUsagePagePower = HIDUsagePage(0x84) HIDUsagePageBatterySystem = HIDUsagePage(0x85) HIDUsagePageBarcodeScanner = HIDUsagePage(0x8C) HIDUsagePageScales = HIDUsagePage(0x8D) HIDUsagePageMagneticStripe = HIDUsagePage(0x8E) HIDUsagePageCameraControl = HIDUsagePage(0x90) HIDUsagePageArcade = HIDUsagePage(0x91) HIDUsagePageGaming = HIDUsagePage(0x92) ) var ( HIDUsageDesktopPointer = HIDUsage(0x01) HIDUsageDesktopMouse = HIDUsage(0x02) HIDUsageDesktopJoystick = HIDUsage(0x04) HIDUsageDesktopGamepad = HIDUsage(0x05) HIDUsageDesktopKeyboard = HIDUsage(0x06) HIDUsageDesktopKeypad = HIDUsage(0x07) HIDUsageDesktopMultiaxis = HIDUsage(0x08) HIDUsageDesktopTablet = HIDUsage(0x09) HIDUsageDesktopWaterCooling = HIDUsage(0x0A) HIDUsageDesktopChassis = HIDUsage(0x0B) HIDUsageDesktopWireless = HIDUsage(0x0C) HIDUsageDesktopPortable = HIDUsage(0x0D) HIDUsageDesktopSystemMultiaxis = HIDUsage(0x0E) HIDUsageDesktopSpatial = HIDUsage(0x0F) HIDUsageDesktopAssistive = HIDUsage(0x10) HIDUsageDesktopDock = HIDUsage(0x11) HIDUsageDesktopDockable = HIDUsage(0x12) HIDUsageDesktopCallState = HIDUsage(0x13) HIDUsageDesktopX = HIDUsage(0x30) HIDUsageDesktopY = HIDUsage(0x31) HIDUsageDesktopZ = HIDUsage(0x32) HIDUsageDesktopRx = HIDUsage(0x33) HIDUsageDesktopRy = HIDUsage(0x34) HIDUsageDesktopRz = HIDUsage(0x35) HIDUsageDesktopSlider = HIDUsage(0x36) HIDUsageDesktopDial = HIDUsage(0x37) HIDUsageDesktopWheel = HIDUsage(0x38) HIDUsageDesktopHatSwitch = HIDUsage(0x39) HIDUsageDesktopCountedBuffer = HIDUsage(0x3A) ) var ( HIDUsageConsumerControl = HIDUsage(0x01) HIDUsageConsumerNumericKeypad = HIDUsage(0x02) HIDUsageConsumerProgrammableButtons = HIDUsage(0x03) HIDUsageConsumerMicrophone = HIDUsage(0x04) HIDUsageConsumerHeadphone = HIDUsage(0x05) HIDUsageConsumerGraphicEqualizer = HIDUsage(0x06) ) var ( HIDCollectionPhysical = HIDCollection(0x00) HIDCollectionApplication = HIDCollection(0x01) HIDCollectionEnd = []byte{0xC0} ) var ( // Input (Data,Ary,Abs), Key arrays (6 bytes) HIDInputDataAryAbs = HIDInput(0x00) // Input (Data, Variable, Absolute), Modifier byte HIDInputDataVarAbs = HIDInput(0x02) // Input (Const,Var,Abs), Modifier byte HIDInputConstVarAbs = HIDInput(0x03) // Input (Data, Variable, Relative), 2 position bytes (X & Y) HIDInputDataVarRel = HIDInput(0x06) // Output (Data, Variable, Absolute), Modifier byte HIDOutputDataVarAbs = HIDOutput(0x02) // Output (Const, Variable, Absolute), Modifier byte HIDOutputConstVarAbs = HIDOutput(0x03) ) func hidShortItem(tag byte, value uint32) []byte { switch { case value <= math.MaxUint8: return []byte{tag | hidSizeValue1, byte(value)} case value <= math.MaxUint16: return []byte{tag | hidSizeValue2, byte(value), byte(value >> 8)} default: return []byte{tag | hidSizeValue4, byte(value), byte(value >> 8), byte(value >> 16), byte(value >> 24)} } } func hidShortItemSigned(tag byte, value int32) []byte { switch { case math.MinInt8 <= value && value <= math.MaxInt8: return []byte{tag | hidSizeValue1, byte(value)} case math.MinInt16 <= value && value <= math.MaxInt16: return []byte{tag | hidSizeValue2, byte(value), byte(value >> 8)} default: return []byte{tag | hidSizeValue4, byte(value), byte(value >> 8), byte(value >> 16), byte(value >> 24)} } } func HIDReportSize(size int) []byte { return hidShortItem(hidReportSize, uint32(size)) } func HIDReportCount(count int) []byte { return hidShortItem(hidReportCount, uint32(count)) } func HIDReportID(id int) []byte { return hidShortItem(hidReportID, uint32(id)) } func HIDLogicalMinimum(min int) []byte { return hidShortItemSigned(hidLogicalMinimum, int32(min)) } func HIDLogicalMaximum(max int) []byte { return hidShortItemSigned(hidLogicalMaximum, int32(max)) } func HIDUsageMinimum(min int) []byte { return hidShortItem(hidUsageMinimum, uint32(min)) } func HIDUsageMaximum(max int) []byte { return hidShortItem(hidUsageMaximum, uint32(max)) } func HIDPhysicalMinimum(min int) []byte { return hidShortItemSigned(hidPhysicalMinimum, int32(min)) } func HIDPhysicalMaximum(max int) []byte { return hidShortItemSigned(hidPhysicalMaximum, int32(max)) } func HIDUnitExponent(exp int) []byte { // 4 Bit two's complement return hidShortItem(hidUnitExponent, uint32(exp&0xF)) } func HIDUnit(unit uint32) []byte { return hidShortItem(hidUnit, unit) } func HIDUsagePage(id uint16) []byte { return hidShortItem(hidUsagePage, uint32(id)) } func HIDUsage(id uint32) []byte { return hidShortItem(hidUsage, id) } func HIDCollection(id uint32) []byte { return hidShortItem(hidCollection, id) } func HIDInput(flags uint32) []byte { return hidShortItem(hidInput, flags) } func HIDOutput(flags uint32) []byte { return hidShortItem(hidOutput, flags) } ================================================ FILE: src/machine/usb/descriptor/interface.go ================================================ package descriptor const ( interfaceTypeLen = 9 ) type InterfaceType struct { data []byte } func (d InterfaceType) Bytes() []byte { return d.data } func (d InterfaceType) Length(v uint8) { d.data[0] = byte(v) } func (d InterfaceType) Type(v uint8) { d.data[1] = byte(v) } func (d InterfaceType) InterfaceNumber(v uint8) { d.data[2] = byte(v) } func (d InterfaceType) AlternateSetting(v uint8) { d.data[3] = byte(v) } func (d InterfaceType) NumEndpoints(v uint8) { d.data[4] = byte(v) } func (d InterfaceType) InterfaceClass(v uint8) { d.data[5] = byte(v) } func (d InterfaceType) InterfaceSubClass(v uint8) { d.data[6] = byte(v) } func (d InterfaceType) InterfaceProtocol(v uint8) { d.data[7] = byte(v) } func (d InterfaceType) Interface(v uint8) { d.data[8] = byte(v) } ================================================ FILE: src/machine/usb/descriptor/interfaceassociation.go ================================================ package descriptor const ( interfaceAssociationTypeLen = 8 ) type InterfaceAssociationType struct { data []byte } func (d InterfaceAssociationType) Bytes() []byte { return d.data } func (d InterfaceAssociationType) Length(v uint8) { d.data[0] = byte(v) } func (d InterfaceAssociationType) Type(v uint8) { d.data[1] = byte(v) } func (d InterfaceAssociationType) FirstInterface(v uint8) { d.data[2] = byte(v) } func (d InterfaceAssociationType) InterfaceCount(v uint8) { d.data[3] = byte(v) } func (d InterfaceAssociationType) FunctionClass(v uint8) { d.data[4] = byte(v) } func (d InterfaceAssociationType) FunctionSubClass(v uint8) { d.data[5] = byte(v) } func (d InterfaceAssociationType) FunctionProtocol(v uint8) { d.data[6] = byte(v) } func (d InterfaceAssociationType) Function(v uint8) { d.data[7] = byte(v) } ================================================ FILE: src/machine/usb/descriptor/joystick.go ================================================ package descriptor var deviceJoystick = [deviceTypeLen]byte{ deviceTypeLen, TypeDevice, 0x00, 0x02, // USB version 0xef, // device class 0x02, // subclass 0x01, // protocol 0x40, // maxpacketsize 0x41, 0x23, // vendor id 0x36, 0x80, // product id 0x00, 0x01, // device 0x01, // manufacturer 0x02, // product 0x03, // SerialNumber 0x01, // NumConfigurations } var DeviceJoystick = DeviceType{ data: deviceJoystick[:], } var configurationCDCJoystick = [configurationTypeLen]byte{ configurationTypeLen, TypeConfiguration, 0x6b, 0x00, // adjust length as needed 0x03, // number of interfaces 0x01, // configuration value 0x00, // index to string description 0xa0, // attributes 0xfa, // maxpower } var ConfigurationCDCJoystick = ConfigurationType{ data: configurationCDCJoystick[:], } var interfaceHIDJoystick = [interfaceTypeLen]byte{ interfaceTypeLen, TypeInterface, 0x02, // InterfaceNumber 0x00, // AlternateSetting 0x02, // NumEndpoints 0x03, // InterfaceClass 0x00, // InterfaceSubClass 0x00, // InterfaceProtocol 0x00, // Interface } var InterfaceHIDJoystick = InterfaceType{ data: interfaceHIDJoystick[:], } var classHIDJoystick = [ClassHIDTypeLen]byte{ ClassHIDTypeLen, TypeClassHID, 0x11, // HID version L 0x01, // HID version H 0x00, // CountryCode 0x01, // NumDescriptors 0x22, // ClassType 0x00, // ClassLength L 0x00, // ClassLength H } var ClassHIDJoystick = ClassHIDType{ data: classHIDJoystick[:], } var JoystickDefaultHIDReport = Append([][]byte{ HIDUsagePageGenericDesktop, HIDUsageDesktopJoystick, HIDCollectionApplication, HIDReportID(1), HIDUsagePageButton, HIDUsageMinimum(1), HIDUsageMaximum(16), HIDLogicalMinimum(0), HIDLogicalMaximum(1), HIDReportSize(1), HIDReportCount(16), HIDUnitExponent(0), HIDUnit(0), HIDInputDataVarAbs, HIDUsagePageGenericDesktop, HIDUsageDesktopHatSwitch, HIDLogicalMinimum(0), HIDLogicalMaximum(7), HIDPhysicalMinimum(0), HIDPhysicalMaximum(315), HIDUnit(0x14), HIDReportCount(1), HIDReportSize(4), HIDInputDataVarAbs, HIDUsageDesktopHatSwitch, HIDReportCount(1), HIDReportSize(4), HIDInputConstVarAbs, HIDUsageDesktopPointer, HIDLogicalMinimum(-32767), HIDLogicalMaximum(32767), HIDPhysicalMinimum(0), HIDPhysicalMaximum(0), HIDUnit(0), HIDReportCount(6), HIDReportSize(16), HIDCollectionPhysical, HIDUsageDesktopX, HIDUsageDesktopY, HIDUsageDesktopZ, HIDUsageDesktopRx, HIDUsageDesktopRy, HIDUsageDesktopRz, HIDInputDataVarAbs, HIDCollectionEnd, HIDCollectionEnd, }) // CDCJoystick requires that you append the JoystickDescriptor // to the Configuration before using. This is in order to support // custom configurations. var CDCJoystick = Descriptor{ Device: DeviceJoystick.Bytes(), Configuration: Append([][]byte{ ConfigurationCDCJoystick.Bytes(), InterfaceAssociationCDC.Bytes(), InterfaceCDCControl.Bytes(), ClassSpecificCDCHeader.Bytes(), ClassSpecificCDCACM.Bytes(), ClassSpecificCDCUnion.Bytes(), ClassSpecificCDCCallManagement.Bytes(), EndpointEP1IN.Bytes(), InterfaceCDCData.Bytes(), EndpointEP2OUT.Bytes(), EndpointEP3IN.Bytes(), InterfaceHIDJoystick.Bytes(), ClassHIDJoystick.Bytes(), EndpointEP4IN.Bytes(), EndpointEP5OUT.Bytes(), }), HID: map[uint16][]byte{}, } ================================================ FILE: src/machine/usb/descriptor/midi.go ================================================ package descriptor var interfaceAssociationMIDI = [interfaceAssociationTypeLen]byte{ interfaceAssociationTypeLen, TypeInterfaceAssociation, 0x02, // EndpointAddress 0x02, // Attributes 0x01, // MaxPacketSizeL 0x01, // MaxPacketSizeH 0x00, // Interval 0x00, // Interval } var InterfaceAssociationMIDI = InterfaceAssociationType{ data: interfaceAssociationMIDI[:], } var interfaceAudio = [interfaceTypeLen]byte{ interfaceTypeLen, TypeInterface, 0x02, // InterfaceNumber 0x00, // AlternateSetting 0x00, // NumEndpoints 0x01, // InterfaceClass 0x01, // InterfaceSubClass 0x00, // InterfaceProtocol 0x00, // Interface } var InterfaceAudio = InterfaceType{ data: interfaceAudio[:], } var interfaceMIDIStreaming = [interfaceTypeLen]byte{ interfaceTypeLen, TypeInterface, 0x03, // InterfaceNumber 0x00, // AlternateSetting 0x02, // NumEndpoints 0x01, // InterfaceClass 0x03, // InterfaceSubClass 0x00, // InterfaceProtocol 0x00, // Interface } var InterfaceMIDIStreaming = InterfaceType{ data: interfaceMIDIStreaming[:], } const classSpecificAudioTypeLen = 9 var classSpecificAudioInterface = [classSpecificAudioTypeLen]byte{ classSpecificAudioTypeLen, TypeClassSpecific, 0x01, 0x00, // 0x01, // 0x09, 0x00, 0x01, 0x03, } var ClassSpecificAudioInterface = ClassSpecificType{ data: classSpecificAudioInterface[:], } const classSpecificMIDIHeaderLen = 7 var classSpecificMIDIHeader = [classSpecificMIDIHeaderLen]byte{ classSpecificMIDIHeaderLen, TypeClassSpecific, 0x01, 0x00, // 0x01, // 0x41, 0x00, } var ClassSpecificMIDIHeader = ClassSpecificType{ data: classSpecificMIDIHeader[:], } const classSpecificMIDIInJackLen = 6 var classSpecificMIDIInJack1 = [classSpecificMIDIInJackLen]byte{ classSpecificMIDIInJackLen, TypeClassSpecific, 0x02, // MIDI In jack 0x01, // Jack type (embedded) 0x01, // Jack ID 0x00, // iJack } var ClassSpecificMIDIInJack1 = ClassSpecificType{ data: classSpecificMIDIInJack1[:], } var classSpecificMIDIInJack2 = [classSpecificMIDIInJackLen]byte{ classSpecificMIDIInJackLen, TypeClassSpecific, 0x02, // MIDI In jack 0x02, // Jack type (external) 0x02, // Jack ID 0x00, // iJack } var ClassSpecificMIDIInJack2 = ClassSpecificType{ data: classSpecificMIDIInJack2[:], } const classSpecificMIDIOutJackLen = 9 var classSpecificMIDIOutJack1 = [classSpecificMIDIOutJackLen]byte{ classSpecificMIDIOutJackLen, TypeClassSpecific, 0x03, // MIDI Out jack 0x01, // Jack type (embedded) 0x03, // Jack ID 0x01, // number of input pins 0x02, // source ID 0x01, // source pin 0x00, // iJack } var ClassSpecificMIDIOutJack1 = ClassSpecificType{ data: classSpecificMIDIOutJack1[:], } var classSpecificMIDIOutJack2 = [classSpecificMIDIOutJackLen]byte{ classSpecificMIDIOutJackLen, TypeClassSpecific, 0x03, // MIDI Out jack 0x02, // Jack type (external) 0x04, // Jack ID 0x01, // number of input pins 0x01, // source ID 0x01, // source pin 0x00, // iJack } var ClassSpecificMIDIOutJack2 = ClassSpecificType{ data: classSpecificMIDIOutJack2[:], } const classSpecificMIDIEndpointLen = 5 var classSpecificMIDIOutEndpoint = [classSpecificMIDIEndpointLen]byte{ classSpecificMIDIEndpointLen, TypeClassSpecificEndpoint, 0x01, // CS endpoint 0x01, // number MIDI IN jacks 0x01, // assoc Jack ID } var ClassSpecificMIDIOutEndpoint = ClassSpecificType{ data: classSpecificMIDIOutEndpoint[:], } var classSpecificMIDIInEndpoint = [classSpecificMIDIEndpointLen]byte{ classSpecificMIDIEndpointLen, TypeClassSpecificEndpoint, 0x01, // CS endpoint 0x01, // number MIDI Out jacks 0x03, // assoc Jack ID } var ClassSpecificMIDIInEndpoint = ClassSpecificType{ data: classSpecificMIDIInEndpoint[:], } const endpointMIDITypeLen = 9 var endpointEP6IN = [endpointMIDITypeLen]byte{ endpointMIDITypeLen, TypeEndpoint, 0x86, // EndpointAddress 0x02, // Attributes 0x40, // MaxPacketSizeL 0x00, // MaxPacketSizeH 0x00, // Interval 0x00, // refresh 0x00, // sync address } var EndpointEP6IN = EndpointType{ data: endpointEP6IN[:], } var endpointEP7OUT = [endpointMIDITypeLen]byte{ endpointMIDITypeLen, TypeEndpoint, 0x07, // EndpointAddress 0x02, // Attributes 0x40, // MaxPacketSizeL 0x00, // MaxPacketSizeH 0x00, // Interval 0x00, // refresh 0x00, // sync address } var EndpointEP7OUT = EndpointType{ data: endpointEP7OUT[:], } var configurationCDCMIDI = [configurationTypeLen]byte{ configurationTypeLen, TypeConfiguration, 0xaf, 0x00, // adjust length as needed 0x04, // number of interfaces 0x01, // configuration value 0x00, // index to string description 0xa0, // attributes 0x32, // maxpower } var ConfigurationCDCMIDI = ConfigurationType{ data: configurationCDCMIDI[:], } var CDCMIDI = Descriptor{ Device: DeviceCDC.Bytes(), Configuration: Append([][]byte{ ConfigurationCDCMIDI.Bytes(), InterfaceAssociationCDC.Bytes(), InterfaceCDCControl.Bytes(), ClassSpecificCDCHeader.Bytes(), ClassSpecificCDCACM.Bytes(), ClassSpecificCDCUnion.Bytes(), ClassSpecificCDCCallManagement.Bytes(), EndpointEP1IN.Bytes(), InterfaceCDCData.Bytes(), EndpointEP2OUT.Bytes(), EndpointEP3IN.Bytes(), InterfaceAssociationMIDI.Bytes(), InterfaceAudio.Bytes(), ClassSpecificAudioInterface.Bytes(), InterfaceMIDIStreaming.Bytes(), ClassSpecificMIDIHeader.Bytes(), ClassSpecificMIDIInJack1.Bytes(), ClassSpecificMIDIInJack2.Bytes(), ClassSpecificMIDIOutJack1.Bytes(), ClassSpecificMIDIOutJack2.Bytes(), EndpointEP7OUT.Bytes(), ClassSpecificMIDIOutEndpoint.Bytes(), EndpointEP6IN.Bytes(), ClassSpecificMIDIInEndpoint.Bytes(), }), } ================================================ FILE: src/machine/usb/descriptor/msc.go ================================================ package descriptor const ( interfaceClassMSC = 0x08 mscSubclassSCSI = 0x06 mscProtocolBOT = 0x50 ) var interfaceAssociationMSC = [interfaceAssociationTypeLen]byte{ interfaceAssociationTypeLen, TypeInterfaceAssociation, 0x02, // FirstInterface 0x01, // InterfaceCount interfaceClassMSC, // FunctionClass mscSubclassSCSI, // FunctionSubClass mscProtocolBOT, // FunctionProtocol 0x00, // Function } var InterfaceAssociationMSC = InterfaceAssociationType{ data: interfaceAssociationMSC[:], } var interfaceMSC = [interfaceTypeLen]byte{ interfaceTypeLen, // Length TypeInterface, // DescriptorType 0x02, // InterfaceNumber 0x00, // AlternateSetting 0x02, // NumEndpoints interfaceClassMSC, // InterfaceClass (Mass Storage) mscSubclassSCSI, // InterfaceSubClass (SCSI Transparent) mscProtocolBOT, // InterfaceProtocol (Bulk-Only Transport) 0x00, // Interface } var InterfaceMSC = InterfaceType{ data: interfaceMSC[:], } var configurationMSC = [configurationTypeLen]byte{ configurationTypeLen, TypeConfiguration, 0x6a, 0x00, // wTotalLength 0x03, // number of interfaces (bNumInterfaces) 0x01, // configuration value (bConfigurationValue) 0x00, // index to string description (iConfiguration) 0xa0, // attributes (bmAttributes) 0x32, // maxpower (100 mA) (bMaxPower) } var ConfigurationMSC = ConfigurationType{ data: configurationMSC[:], } // Mass Storage Class var MSC = Descriptor{ Device: DeviceCDC.Bytes(), Configuration: Append([][]byte{ ConfigurationMSC.Bytes(), InterfaceAssociationCDC.Bytes(), InterfaceCDCControl.Bytes(), ClassSpecificCDCHeader.Bytes(), ClassSpecificCDCACM.Bytes(), ClassSpecificCDCUnion.Bytes(), ClassSpecificCDCCallManagement.Bytes(), EndpointEP1IN.Bytes(), InterfaceCDCData.Bytes(), EndpointEP2OUT.Bytes(), EndpointEP3IN.Bytes(), InterfaceAssociationMSC.Bytes(), InterfaceMSC.Bytes(), EndpointMSCIN.Bytes(), EndpointMSCOUT.Bytes(), }), } ================================================ FILE: src/machine/usb/doc.go ================================================ // package usb contains the subpackages with USB descriptors and device // implementations for standard USB device classes such as the Communcation // Data Class (CDC), Human Interface Device (HID), and Audio Device Class (ADC). package usb ================================================ FILE: src/machine/usb/hid/buffer.go ================================================ package hid import ( "runtime/volatile" ) const bufferSize = 128 // RingBuffer is ring buffer implementation inspired by post at // https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php type RingBuffer struct { rxbuffer [bufferSize][9]byte head volatile.Register8 tail volatile.Register8 } // NewRingBuffer returns a new ring buffer. func NewRingBuffer() *RingBuffer { return &RingBuffer{} } // Used returns how many bytes in buffer have been used. func (rb *RingBuffer) Used() uint8 { return uint8(rb.head.Get() - rb.tail.Get()) } // Put stores a byte in the buffer. If the buffer is already // full, the method will return false. func (rb *RingBuffer) Put(val []byte) bool { if rb.Used() != bufferSize { rb.head.Set(rb.head.Get() + 1) copy(rb.rxbuffer[rb.head.Get()%bufferSize][:], val) return true } return false } // Get returns a byte from the buffer. If the buffer is empty, // the method will return a false as the second value. func (rb *RingBuffer) Get() ([]byte, bool) { if rb.Used() != 0 { rb.tail.Set(rb.tail.Get() + 1) return rb.rxbuffer[rb.tail.Get()%bufferSize][:], true } return nil, false } // Clear resets the head and tail pointer to zero. func (rb *RingBuffer) Clear() { rb.head.Set(0) rb.tail.Set(0) } ================================================ FILE: src/machine/usb/hid/doc.go ================================================ // package hid is for USB Human Interface Devices. package hid ================================================ FILE: src/machine/usb/hid/hid.go ================================================ package hid import ( "errors" "machine" "machine/usb" "machine/usb/descriptor" ) // from usb-hid.go var ( ErrHIDInvalidPort = errors.New("invalid USB port") ErrHIDInvalidCore = errors.New("invalid USB core") ErrHIDReportTransfer = errors.New("failed to transfer HID report") ) const ( hidEndpoint = usb.HID_ENDPOINT_IN REPORT_TYPE_INPUT = 1 REPORT_TYPE_OUTPUT = 2 REPORT_TYPE_FEATURE = 3 ) type hidDevicer interface { TxHandler() bool RxHandler([]byte) bool } var devices [5]hidDevicer var size int // SetHandler sets the handler. Only the first time it is called, it // calls machine.EnableHID for USB configuration func SetHandler(d hidDevicer) { if size == 0 { machine.ConfigureUSBEndpoint(descriptor.CDCHID, []usb.EndpointConfig{ { Index: usb.HID_ENDPOINT_OUT, IsIn: false, Type: usb.ENDPOINT_TYPE_INTERRUPT, RxHandler: rxHandler, }, { Index: usb.HID_ENDPOINT_IN, IsIn: true, Type: usb.ENDPOINT_TYPE_INTERRUPT, TxHandler: txHandler, }, }, []usb.SetupConfig{ { Index: usb.HID_INTERFACE, Handler: setupHandler, }, }) } devices[size] = d size++ } func txHandler() { for _, d := range devices { if d == nil { continue } if done := d.TxHandler(); done { return } } } func rxHandler(b []byte) { for _, d := range devices { if d == nil { continue } if done := d.RxHandler(b); done { return } } } var DefaultSetupHandler = setupHandler func setupHandler(setup usb.Setup) bool { ok := false if setup.BmRequestType == usb.SET_REPORT_TYPE && setup.BRequest == usb.SET_IDLE { machine.SendZlp() ok = true } return ok } // SendUSBPacket sends a HIDPacket. func SendUSBPacket(b []byte) { machine.SendUSBInPacket(hidEndpoint, b) } ================================================ FILE: src/machine/usb/hid/joystick/joystick.go ================================================ package joystick import ( "machine" "machine/usb" "machine/usb/descriptor" "machine/usb/hid" ) var Joystick *joystick type joystick struct { State buf *hid.RingBuffer waitTxc bool rxHandlerFunc func(b []byte) setupFunc func(setup usb.Setup) bool } func init() { if Joystick == nil { Joystick = newDefaultJoystick() } } // UseSettings overrides the Joystick settings. This function must be // called from init(). func UseSettings(def Definitions, rxHandlerFunc func(b []byte), setupFunc func(setup usb.Setup) bool, hidDesc []byte) *joystick { js := &joystick{ buf: hid.NewRingBuffer(), State: def.NewState(), } if setupFunc == nil { setupFunc = hid.DefaultSetupHandler } if rxHandlerFunc == nil { rxHandlerFunc = js.rxHandlerFunc } if len(hidDesc) == 0 { hidDesc = descriptor.JoystickDefaultHIDReport } class, err := descriptor.FindClassHIDType(descriptor.CDCJoystick.Configuration, descriptor.ClassHIDJoystick.Bytes()) if err != nil { // TODO: some way to notify about error return nil } class.ClassLength(uint16(len(hidDesc))) descriptor.CDCJoystick.HID[2] = hidDesc machine.ConfigureUSBEndpoint(descriptor.CDCJoystick, []usb.EndpointConfig{ { Index: usb.HID_ENDPOINT_OUT, IsIn: false, Type: usb.ENDPOINT_TYPE_INTERRUPT, RxHandler: rxHandlerFunc, }, { Index: usb.HID_ENDPOINT_IN, IsIn: true, Type: usb.ENDPOINT_TYPE_INTERRUPT, TxHandler: js.handler, }, }, []usb.SetupConfig{ { Index: usb.HID_INTERFACE, Handler: setupFunc, }, }, ) Joystick = js return js } func newDefaultJoystick() *joystick { def := DefaultDefinitions() js := UseSettings(def, nil, nil, nil) return js } // Port returns the USB Joystick port. func Port() *joystick { return Joystick } func (m *joystick) handler() { m.waitTxc = false if b, ok := m.buf.Get(); ok { m.waitTxc = true hid.SendUSBPacket(b) } } func (m *joystick) tx(b []byte) { if machine.USBDev.InitEndpointComplete { if m.waitTxc { m.buf.Put(b) } else { m.waitTxc = true hid.SendUSBPacket(b) } } } func (m *joystick) ready() bool { return true } func (m *joystick) rxHandler(b []byte) { if m.rxHandlerFunc != nil { m.rxHandlerFunc(b) } } // to InterruptOut func (m *joystick) SendReport(reportID byte, b []byte) { m.tx(append([]byte{reportID}, b...)) } func (m *joystick) SendState() { b, _ := m.State.MarshalBinary() m.tx(b) } ================================================ FILE: src/machine/usb/hid/joystick/state.go ================================================ package joystick import ( "machine/usb/descriptor" "encoding/binary" ) type HatDirection uint8 const ( HatUp HatDirection = iota HatRightUp HatRight HatRightDown HatDown HatLeftDown HatLeft HatLeftUp HatCenter ) type Constraint struct { MinIn int MaxIn int MinOut int16 MaxOut int16 } type AxisValue struct { constraint Constraint Value int } func fit(x, in_min, in_max int, out_min, out_max int16) int16 { return int16((x-in_min)*(int(out_max)-int(out_min))/(in_max-in_min) + int(out_min)) } func fitf(x, in_min, in_max, out_min, out_max float32) float32 { return x - in_min*(out_max-out_min)/(in_max-in_min) + out_min } func limit(v, max int) int { if v > max { v = max } else if v < -max { v = -max } return v } type Definitions struct { ReportID byte ButtonCnt int HatSwitchCnt int AxisDefs []Constraint descriptor []byte } func (c Definitions) Descriptor() []byte { if len(c.descriptor) > 0 { return c.descriptor } // TODO: build hid descriptor return nil } func (c Definitions) NewState() State { bufSize := 1 hatSwitches := make([]HatDirection, c.HatSwitchCnt) for i := range hatSwitches { hatSwitches[i] = HatCenter } axises := make([]*AxisValue, 0, len(c.AxisDefs)) for _, v := range c.AxisDefs { axises = append(axises, &AxisValue{ constraint: v, Value: 0, }) } btnSize := (c.ButtonCnt + 7) / 8 bufSize += btnSize bufSize += (len(hatSwitches) + 1) / 2 bufSize += len(axises) * 2 initBuf := make([]byte, bufSize) initBuf[0] = c.ReportID return State{ buf: initBuf, Buttons: make([]byte, btnSize), HatSwitches: hatSwitches, Axises: axises, } } type State struct { buf []byte Buttons []byte HatSwitches []HatDirection Axises []*AxisValue } func (s State) MarshalBinary() ([]byte, error) { s.buf = s.buf[0:1] s.buf = append(s.buf, s.Buttons...) if len(s.HatSwitches) > 0 { hat := byte(0) for i, v := range s.HatSwitches { if i != 0 && i%2 == 0 { s.buf = append(s.buf, hat) hat = 0 } hat <<= 4 hat |= byte(v & 0xf) } s.buf = append(s.buf, hat) } for _, v := range s.Axises { c := v.constraint val := fit(v.Value, c.MinIn, c.MaxIn, c.MinOut, c.MaxOut) s.buf = binary.LittleEndian.AppendUint16(s.buf, uint16(val)) } return s.buf, nil } func (s State) Button(index int) bool { idx := index / 8 bit := uint8(1 << (index % 8)) return s.Buttons[idx]&bit > 0 } func (s State) SetButton(index int, push bool) { idx := index / 8 bit := uint8(1 << (index % 8)) b := s.Buttons[idx] b &= ^bit if push { b |= bit } s.Buttons[idx] = b } func (s State) Hat(index int) HatDirection { return s.HatSwitches[index] } func (s State) SetHat(index int, dir HatDirection) { s.HatSwitches[index] = dir } func (s State) Axis(index int) int { return s.Axises[index].Value } func (s State) SetAxis(index int, v int) { s.Axises[index].Value = v } func DefaultDefinitions() Definitions { return Definitions{ ReportID: 1, ButtonCnt: 16, HatSwitchCnt: 1, AxisDefs: []Constraint{ {MinIn: -32767, MaxIn: 32767, MinOut: -32767, MaxOut: 32767}, {MinIn: -32767, MaxIn: 32767, MinOut: -32767, MaxOut: 32767}, {MinIn: -32767, MaxIn: 32767, MinOut: -32767, MaxOut: 32767}, {MinIn: -32767, MaxIn: 32767, MinOut: -32767, MaxOut: 32767}, {MinIn: -32767, MaxIn: 32767, MinOut: -32767, MaxOut: 32767}, {MinIn: -32767, MaxIn: 32767, MinOut: -32767, MaxOut: 32767}, }, descriptor: descriptor.JoystickDefaultHIDReport, } } ================================================ FILE: src/machine/usb/hid/keyboard/keyboard.go ================================================ package keyboard import ( "errors" "machine" "machine/usb/hid" ) // from usb-hid-keyboard.go var ( ErrInvalidCodepoint = errors.New("invalid Unicode codepoint") ErrInvalidKeycode = errors.New("invalid keyboard keycode") ErrInvalidUTF8 = errors.New("invalid UTF-8 encoding") ErrKeypressMaximum = errors.New("maximum keypresses exceeded") ) var Keyboard *keyboard // Keyboard represents a USB HID keyboard device with support for international // layouts and various control, system, multimedia, and consumer keycodes. // // Keyboard implements the io.Writer interface that translates UTF-8 encoded // byte strings into sequences of keypress events. type keyboard struct { // led holds the current state of all keyboard LEDs: // 1=NumLock 2=CapsLock 4=ScrollLock 8=Compose 16=Kana led uint8 // mod holds the current state of all keyboard modifier keys: // 1=LeftCtrl 2=LeftShift 4=LeftAlt 8=LeftGUI // 16=RightCtrl 32=RightShift 64=RightAlt 128=RightGUI mod uint8 // key holds a list of all keyboard keys currently pressed. key [hidKeyboardKeyCount]uint8 con [hidKeyboardConCount]uint16 sys [hidKeyboardSysCount]uint8 // decode holds the current state of the UTF-8 decoder. decode decodeState // wideChar holds high bits for the UTF-8 decoder. wideChar uint16 buf *hid.RingBuffer waitTxc bool } // decodeState represents a state in the UTF-8 decode state machine. type decodeState uint8 // Constant enumerated values of type decodeState. const ( decodeReset decodeState = iota decodeByte1 decodeByte2 decodeByte3 ) func init() { if Keyboard == nil { Keyboard = newKeyboard() hid.SetHandler(Keyboard) } } // New returns the USB hid-keyboard port. // Deprecated, better to just use Port() func New() *keyboard { return Port() } // Port returns the USB hid-keyboard port. func Port() *keyboard { return Keyboard } func newKeyboard() *keyboard { return &keyboard{ buf: hid.NewRingBuffer(), } } func (kb *keyboard) TxHandler() bool { kb.waitTxc = false if b, ok := kb.buf.Get(); ok { kb.waitTxc = true hid.SendUSBPacket(b) return true } return false } func (kb *keyboard) RxHandler(b []byte) bool { if len(b) >= 2 && b[0] == 2 /* ReportID */ { kb.led = b[1] } return false } func (kb *keyboard) tx(b []byte) { if machine.USBDev.InitEndpointComplete { if kb.waitTxc { kb.buf.Put(b) } else { kb.waitTxc = true hid.SendUSBPacket(b) } } } func (kb *keyboard) NumLockLed() bool { return kb.led&1 != 0 } func (kb *keyboard) CapsLockLed() bool { return kb.led&2 != 0 } func (kb *keyboard) ScrollLockLed() bool { return kb.led&4 != 0 } func (kb *keyboard) ready() bool { return true } // Write transmits press-and-release key sequences for each Keycode translated // from the given UTF-8 byte string. Write implements the io.Writer interface // and conforms to all documented conventions for arguments and return values. func (kb *keyboard) Write(b []byte) (n int, err error) { for _, c := range b { if err = kb.WriteByte(c); nil != err { break } n += 1 } return } // WriteByte processes a single byte from a UTF-8 byte string. This method is a // stateful method with respect to the receiver Keyboard, meaning that its exact // behavior will depend on the current state of its UTF-8 decode state machine: // // 1. If the given byte is a valid ASCII encoding (0-127), then a keypress // sequence is immediately transmitted for the respective Keycode. // 2. If the given byte represents the final byte in a multi-byte codepoint, // then a keypress sequence is immediately transmitted by translating the // multi-byte codepoint to its respective Keycode. // 3. If the given byte appears to represent high bits for a multi-byte // codepoint, then the bits are copied to the receiver's internal state // machine buffer for use by a subsequent call to WriteByte() (or Write()) // that completes the codepoint. // 4. If the given byte is out of range, or contains illegal bits for the // current state of the UTF-8 decoder, then the UTF-8 decode state machine // is reset to its initial state. // // In cases 3 and 4, a keypress sequence is not generated and no data is // transmitted. In case 3, additional bytes must be received via WriteByte() // (or Write()) to complete or discard the current codepoint. func (kb *keyboard) WriteByte(b byte) error { switch { case b < 0x80: // 1-byte encoding (0x00-0x7F) kb.decode = decodeByte1 return kb.write(uint16(b)) case b < 0xC0: // 2nd, 3rd, or 4th byte (0x80-0xBF) b = Keycode(b).key() switch kb.decode { case decodeByte2: kb.decode = decodeByte1 return kb.write(kb.wideChar | uint16(b)) case decodeByte3: kb.decode = decodeByte2 kb.wideChar |= uint16(b) << 6 } case b < 0xE0: // 2-byte encoding (0xC2-0xDF), or illegal byte 2 (0xC0-0xC1) kb.decode = decodeByte2 kb.wideChar = uint16(b&0x1F) << 6 case b < 0xF0: // 3-byte encoding (0xE0-0xEF) kb.decode = decodeByte3 kb.wideChar = uint16(b&0x0F) << 12 default: // 4-byte encoding unsupported (0xF0-0xF4), or illegal byte 4 (0xF5-0xFF) kb.decode = decodeReset return ErrInvalidUTF8 } return nil } func (kb *keyboard) write(p uint16) error { c := keycode(p) if 0 == c { return ErrInvalidCodepoint } if d := deadkey(c); 0 != d { if err := kb.writeKeycode(d); nil != err { return err } } return kb.writeKeycode(c) } func (kb *keyboard) writeKeycode(c Keycode) error { var b [9]byte b[0] = 0x02 b[1] = c.mod() b[2] = 0 b[3] = c.key() b[4] = 0 b[5] = 0 b[6] = 0 b[7] = 0 b[8] = 0 if !kb.sendKey(false, b[:]) { return hid.ErrHIDReportTransfer } b[1] = 0 b[3] = 0 if !kb.sendKey(false, b[:]) { return hid.ErrHIDReportTransfer } return nil } // Press transmits a press-and-release sequence for the given Keycode, which // simulates a discrete keypress event. // // The following values of Keycode are supported: // // 0x0020 - 0x007F ASCII (U+0020 to U+007F) [USES LAYOUT] // 0x0080 - 0xC1FF Unicode (U+0080 to U+C1FF) [USES LAYOUT] // 0xC200 - 0xDFFF UTF-8 packed (U+0080 to U+07FF) [USES LAYOUT] // 0xE000 - 0xE0FF Modifier key (bitmap, 8 keys, Shift/Ctrl/Alt/GUI) // 0xE200 - 0xE2FF System key (HID usage code, page 1) // 0xE400 - 0xE7FF Media/Consumer key (HID usage code, page 12) // 0xF000 - 0xFFFF Normal key (HID usage code, page 7) func (kb *keyboard) Press(c Keycode) error { if err := kb.Down(c); nil != err { return err } return kb.Up(c) } func (kb *keyboard) sendKey(consumer bool, b []byte) bool { kb.tx(b) return true } func (kb *keyboard) keyboardSendKeys(consumer bool) bool { var b [9]byte if !consumer { b[0] = 0x02 // REPORT_ID b[1] = kb.mod b[2] = 0x02 b[3] = kb.key[0] b[4] = kb.key[1] b[5] = kb.key[2] b[6] = kb.key[3] b[7] = kb.key[4] b[8] = kb.key[5] return kb.sendKey(consumer, b[:]) } else { b[0] = 0x03 // REPORT_ID b[1] = uint8(kb.con[0]) b[2] = uint8((kb.con[0] & 0x0300) >> 8) return kb.sendKey(consumer, b[:3]) } } // Down transmits a key-down event for the given Keycode. // // The host will interpret the key as being held down continuously until a // corresponding key-up event is transmitted, e.g., via method Up(). // // See godoc comment on method Press() for details on what input is accepted and // how it is interpreted. func (kb *keyboard) Down(c Keycode) error { var res uint8 msb := c >> 8 if msb >= 0xC2 { if msb < 0xE0 { c = ((msb & 0x1F) << 6) | Keycode(c.key()) } else { switch msb { case 0xF0: return kb.down(uint8(c), 0) case 0xE0: return kb.down(0, uint8(c)) case 0xE2: return kb.downSys(uint8(c)) default: if 0xE4 <= msb && msb <= 0xE7 { return kb.downCon(uint16(c & 0x03FF)) } return ErrInvalidKeycode } } } c = keycode(uint16(c)) if 0 == c { return ErrInvalidCodepoint } if d := deadkey(c); 0 != d { res = kb.mod if 0 != res { kb.mod = 0 kb.keyboardSendKeys(false) } kb.down(d.key(), d.mod()) kb.up(d.key(), d.mod()) } return kb.down(c.key(), c.mod()|res) } func (kb *keyboard) down(key uint8, mod uint8) error { send := false if 0 != mod { if kb.mod&mod != mod { kb.mod |= mod send = true } } if 0 != key { for _, k := range kb.key { if k == key { goto end } } for i, k := range kb.key { if 0 == k { kb.key[i] = key send = true goto end } } return ErrKeypressMaximum } end: if send { if !kb.keyboardSendKeys(false) { return hid.ErrHIDReportTransfer } } return nil } func (kb *keyboard) downCon(key uint16) error { if 0 == key { return ErrInvalidKeycode } for _, k := range kb.con { if key == k { return nil // already pressed } } for i, k := range kb.con { if 0 == k { kb.con[i] = key if !kb.keyboardSendKeys(true) { return hid.ErrHIDReportTransfer } return nil } } return ErrKeypressMaximum } func (kb *keyboard) downSys(key uint8) error { if 0 == key { return ErrInvalidKeycode } for _, k := range kb.sys { if key == k { return nil // already pressed } } for i, k := range kb.sys { if 0 == k { kb.sys[i] = key if !kb.keyboardSendKeys(true) { return hid.ErrHIDReportTransfer } return nil } } return ErrKeypressMaximum } // Up transmits a key-up event for the given Keycode. // // See godoc comment on method Press() for details on what input is accepted and // how it is interpreted. func (kb *keyboard) Up(c Keycode) error { msb := c >> 8 if msb >= 0xC2 { if msb < 0xE0 { c = ((msb & 0x1F) << 6) | Keycode(c.key()) } else { switch msb { case 0xF0: return kb.up(uint8(c), 0) case 0xE0: return kb.up(0, uint8(c)) case 0xE2: return kb.upSys(uint8(c)) default: if 0xE4 <= msb && msb <= 0xE7 { return kb.upCon(uint16(c & 0x03FF)) } return ErrInvalidKeycode } } } c = keycode(uint16(c)) if 0 == c { return ErrInvalidCodepoint } return kb.up(c.key(), c.mod()) } // Release transmits a key-up event for all keyboard keys currently pressed as // if the user removed his/her hands from the keyboard entirely. func (kb *keyboard) Release() error { bits := uint16(kb.mod) kb.mod = 0 for i, k := range kb.key { bits |= uint16(k) kb.key[i] = 0 } if 0 != bits { if !kb.keyboardSendKeys(false) { return hid.ErrHIDReportTransfer } } bits = 0 for i, k := range kb.con { bits |= k kb.con[i] = 0 } for i, k := range kb.sys { bits |= uint16(k) kb.sys[i] = 0 } if 0 != bits { if !kb.keyboardSendKeys(true) { return hid.ErrHIDReportTransfer } } return nil } func (kb *keyboard) up(key uint8, mod uint8) error { send := false if 0 != mod { if kb.mod&mod != 0 { kb.mod &^= mod send = true } } if 0 != key { for i, k := range kb.key { if key == k { kb.key[i] = 0 send = true } } } if send { if !kb.keyboardSendKeys(false) { return hid.ErrHIDReportTransfer } } return nil } func (kb *keyboard) upCon(key uint16) error { if 0 == key { return ErrInvalidKeycode } for i, k := range kb.con { if key == k { kb.con[i] = 0 if !kb.keyboardSendKeys(true) { return hid.ErrHIDReportTransfer } return nil } } return nil } func (kb *keyboard) upSys(key uint8) error { if 0 == key { return ErrInvalidKeycode } for i, k := range kb.sys { if key == k { kb.sys[i] = 0 if !kb.keyboardSendKeys(true) { return hid.ErrHIDReportTransfer } return nil } } return nil } ================================================ FILE: src/machine/usb/hid/keyboard/keycode.go ================================================ package keyboard // Keycode is a package-defined bitmap used to encode the value of a given key. type Keycode uint16 // keycode returns the given Unicode codepoint translated to a Keycode sequence. // Unicode codepoints greater than U+FFFF are unsupported. // //go:inline func keycode(p uint16) Keycode { if p < 0x80 { return ascii[p] } else if p >= 0xA0 && p < 0x0100 { return iso88591[p-0xA0] } else if uint16(UNICODE20AC) == p { return UNICODE20AC.mask() } return 0 } //go:inline func deadkey(c Keycode) Keycode { switch c & deadkeysMask { case acuteAccentBits: return deadkeyAcuteAccent case circumflexBits: return deadkeyCircumflex case diaeresisBits: return deadkeyDiaeresis case graveAccentBits: return deadkeyGraveAccent case tildeBits: return deadkeyTilde } return 0 } //go:inline func (c Keycode) mask() Keycode { return c & keycodeMask } //go:inline func (c Keycode) key() uint8 { return uint8(c & keyMask) } //go:inline func (c Keycode) mod() uint8 { var m Keycode if 0 != c&shiftMask { m |= KeyModifierShift } if 0 != c&altgrMask { m |= KeyModifierRightAlt } return uint8(m) } //go:inline func (c Keycode) Shift() Keycode { return c | KeyModifierShift } const ( hidKeyboardKeyCount = 6 // Max number of simultaneous keypresses hidKeyboardSysCount = 3 hidKeyboardConCount = 4 ) // Keycodes common to all Keyboard layouts const ( KeyModifierCtrl Keycode = 0x01 | 0xE000 KeyModifierShift Keycode = 0x02 | 0xE000 KeyModifierAlt Keycode = 0x04 | 0xE000 KeyModifierGUI Keycode = 0x08 | 0xE000 KeyModifierLeftCtrl Keycode = 0x01 | 0xE000 KeyModifierLeftShift Keycode = 0x02 | 0xE000 KeyModifierLeftAlt Keycode = 0x04 | 0xE000 KeyModifierLeftGUI Keycode = 0x08 | 0xE000 KeyModifierRightCtrl Keycode = 0x10 | 0xE000 KeyModifierRightShift Keycode = 0x20 | 0xE000 KeyModifierRightAlt Keycode = 0x40 | 0xE000 KeyModifierRightGUI Keycode = 0x80 | 0xE000 // KeySystemXXX is not supported now KeySystemPowerDown Keycode = 0x81 | 0xE200 KeySystemSleep Keycode = 0x82 | 0xE200 KeySystemWakeUp Keycode = 0x83 | 0xE200 KeyMediaPlay Keycode = 0xB0 | 0xE400 KeyMediaPause Keycode = 0xB1 | 0xE400 KeyMediaRecord Keycode = 0xB2 | 0xE400 KeyMediaFastForward Keycode = 0xB3 | 0xE400 KeyMediaRewind Keycode = 0xB4 | 0xE400 KeyMediaNextTrack Keycode = 0xB5 | 0xE400 KeyMediaPrevTrack Keycode = 0xB6 | 0xE400 KeyMediaStop Keycode = 0xB7 | 0xE400 KeyMediaEject Keycode = 0xB8 | 0xE400 KeyMediaRandomPlay Keycode = 0xB9 | 0xE400 KeyMediaPlayPause Keycode = 0xCD | 0xE400 KeyMediaPlaySkip Keycode = 0xCE | 0xE400 KeyMediaMute Keycode = 0xE2 | 0xE400 KeyMediaVolumeInc Keycode = 0xE9 | 0xE400 KeyMediaVolumeDec Keycode = 0xEA | 0xE400 KeyA Keycode = 4 | 0xF000 KeyB Keycode = 5 | 0xF000 KeyC Keycode = 6 | 0xF000 KeyD Keycode = 7 | 0xF000 KeyE Keycode = 8 | 0xF000 KeyF Keycode = 9 | 0xF000 KeyG Keycode = 10 | 0xF000 KeyH Keycode = 11 | 0xF000 KeyI Keycode = 12 | 0xF000 KeyJ Keycode = 13 | 0xF000 KeyK Keycode = 14 | 0xF000 KeyL Keycode = 15 | 0xF000 KeyM Keycode = 16 | 0xF000 KeyN Keycode = 17 | 0xF000 KeyO Keycode = 18 | 0xF000 KeyP Keycode = 19 | 0xF000 KeyQ Keycode = 20 | 0xF000 KeyR Keycode = 21 | 0xF000 KeyS Keycode = 22 | 0xF000 KeyT Keycode = 23 | 0xF000 KeyU Keycode = 24 | 0xF000 KeyV Keycode = 25 | 0xF000 KeyW Keycode = 26 | 0xF000 KeyX Keycode = 27 | 0xF000 KeyY Keycode = 28 | 0xF000 KeyZ Keycode = 29 | 0xF000 Key1 Keycode = 30 | 0xF000 Key2 Keycode = 31 | 0xF000 Key3 Keycode = 32 | 0xF000 Key4 Keycode = 33 | 0xF000 Key5 Keycode = 34 | 0xF000 Key6 Keycode = 35 | 0xF000 Key7 Keycode = 36 | 0xF000 Key8 Keycode = 37 | 0xF000 Key9 Keycode = 38 | 0xF000 Key0 Keycode = 39 | 0xF000 KeyEnter Keycode = 40 | 0xF000 KeyEsc Keycode = 41 | 0xF000 KeyBackspace Keycode = 42 | 0xF000 KeyTab Keycode = 43 | 0xF000 KeySpace Keycode = 44 | 0xF000 KeyMinus Keycode = 45 | 0xF000 KeyEqual Keycode = 46 | 0xF000 KeyLeftBrace Keycode = 47 | 0xF000 KeyRightBrace Keycode = 48 | 0xF000 KeyBackslash Keycode = 49 | 0xF000 KeyNonUsNum Keycode = 50 | 0xF000 KeySemicolon Keycode = 51 | 0xF000 KeyQuote Keycode = 52 | 0xF000 KeyTilde Keycode = 53 | 0xF000 KeyComma Keycode = 54 | 0xF000 KeyPeriod Keycode = 55 | 0xF000 KeySlash Keycode = 56 | 0xF000 KeyCapsLock Keycode = 57 | 0xF000 KeyF1 Keycode = 58 | 0xF000 KeyF2 Keycode = 59 | 0xF000 KeyF3 Keycode = 60 | 0xF000 KeyF4 Keycode = 61 | 0xF000 KeyF5 Keycode = 62 | 0xF000 KeyF6 Keycode = 63 | 0xF000 KeyF7 Keycode = 64 | 0xF000 KeyF8 Keycode = 65 | 0xF000 KeyF9 Keycode = 66 | 0xF000 KeyF10 Keycode = 67 | 0xF000 KeyF11 Keycode = 68 | 0xF000 KeyF12 Keycode = 69 | 0xF000 KeyPrintscreen Keycode = 70 | 0xF000 KeyScrollLock Keycode = 71 | 0xF000 KeyPause Keycode = 72 | 0xF000 KeyInsert Keycode = 73 | 0xF000 KeyHome Keycode = 74 | 0xF000 KeyPageUp Keycode = 75 | 0xF000 KeyDelete Keycode = 76 | 0xF000 KeyEnd Keycode = 77 | 0xF000 KeyPageDown Keycode = 78 | 0xF000 KeyRight Keycode = 79 | 0xF000 KeyLeft Keycode = 80 | 0xF000 KeyDown Keycode = 81 | 0xF000 KeyUp Keycode = 82 | 0xF000 KeyNumLock Keycode = 83 | 0xF000 KeypadSlash Keycode = 84 | 0xF000 KeypadAsterisk Keycode = 85 | 0xF000 KeypadMinus Keycode = 86 | 0xF000 KeypadPlus Keycode = 87 | 0xF000 KeypadEnter Keycode = 88 | 0xF000 Keypad1 Keycode = 89 | 0xF000 Keypad2 Keycode = 90 | 0xF000 Keypad3 Keycode = 91 | 0xF000 Keypad4 Keycode = 92 | 0xF000 Keypad5 Keycode = 93 | 0xF000 Keypad6 Keycode = 94 | 0xF000 Keypad7 Keycode = 95 | 0xF000 Keypad8 Keycode = 96 | 0xF000 Keypad9 Keycode = 97 | 0xF000 Keypad0 Keycode = 98 | 0xF000 KeypadPeriod Keycode = 99 | 0xF000 KeyNonUSBS Keycode = 100 | 0xF000 KeyMenu Keycode = 101 | 0xF000 KeyF13 Keycode = 104 | 0xF000 KeyF14 Keycode = 105 | 0xF000 KeyF15 Keycode = 106 | 0xF000 KeyF16 Keycode = 107 | 0xF000 KeyF17 Keycode = 108 | 0xF000 KeyF18 Keycode = 109 | 0xF000 KeyF19 Keycode = 110 | 0xF000 KeyF20 Keycode = 111 | 0xF000 KeyF21 Keycode = 112 | 0xF000 KeyF22 Keycode = 113 | 0xF000 KeyF23 Keycode = 114 | 0xF000 KeyF24 Keycode = 115 | 0xF000 // International keys for Japanese and other language keyboards KeyInternational1 Keycode = 0x87 | 0xF000 // JIS "\" and "_" KeyInternational2 Keycode = 0x88 | 0xF000 // JIS Katakana/Hiragana KeyInternational3 Keycode = 0x89 | 0xF000 // JIS "¥" and "|" KeyInternational4 Keycode = 0x8A | 0xF000 // JIS Henkan KeyInternational5 Keycode = 0x8B | 0xF000 // JIS Muhenkan KeyInternational6 Keycode = 0x8C | 0xF000 // JIS Numpad "," // Language keys for input method switching KeyLanguage1 Keycode = 0x90 | 0xF000 // Hangul/English KeyLanguage2 Keycode = 0x91 | 0xF000 // Hanja KeyLanguage3 Keycode = 0x92 | 0xF000 // JIS Katakana KeyLanguage4 Keycode = 0x93 | 0xF000 // JIS Hiragana KeyLanguage5 Keycode = 0x94 | 0xF000 // JIS Zenkaku/Hankaku KeyUpArrow Keycode = KeyUp KeyDownArrow Keycode = KeyDown KeyLeftArrow Keycode = KeyLeft KeyRightArrow Keycode = KeyRight KeyReturn Keycode = KeyEnter KeyLeftCtrl Keycode = KeyModifierLeftCtrl KeyLeftShift Keycode = KeyModifierLeftShift KeyLeftAlt Keycode = KeyModifierLeftAlt KeyLeftGUI Keycode = KeyModifierLeftGUI KeyRightCtrl Keycode = KeyModifierRightCtrl KeyRightShift Keycode = KeyModifierRightShift KeyRightAlt Keycode = KeyModifierRightAlt KeyRightGUI Keycode = KeyModifierRightGUI // QMK compatibility aliases for international keys KeyInt1 Keycode = KeyInternational1 KeyInt2 Keycode = KeyInternational2 KeyInt3 Keycode = KeyInternational3 KeyInt4 Keycode = KeyInternational4 KeyInt5 Keycode = KeyInternational5 KeyInt6 Keycode = KeyInternational6 // QMK compatibility aliases for language keys KeyLng1 Keycode = KeyLanguage1 KeyLng2 Keycode = KeyLanguage2 KeyLng3 Keycode = KeyLanguage3 KeyLng4 Keycode = KeyLanguage4 KeyLng5 Keycode = KeyLanguage5 // Common keyboard layout aliases KeyRo Keycode = KeyInternational1 // Japanese "ろ" KeyKatakanaHiragana Keycode = KeyInternational2 // Japanese Katakana/Hiragana KeyYen Keycode = KeyInternational3 // Japanese "¥" KeyHenkan Keycode = KeyInternational4 // Japanese Henkan KeyMuhenkan Keycode = KeyInternational5 // Japanese Muhenkan KeyKpJpComma Keycode = KeyInternational6 // Japanese Numpad "," KeyHangeul Keycode = KeyLanguage1 // Korean Hangul/English KeyHanja Keycode = KeyLanguage2 // Korean Hanja KeyKatakana Keycode = KeyLanguage3 // Japanese Katakana KeyHiragana Keycode = KeyLanguage4 // Japanese Hiragana KeyZenkakuHankaku Keycode = KeyLanguage5 // Japanese Zenkaku/Hankaku ) // Keycodes for layout US English (0x0904) const ( keycodeMask Keycode = 0x07FF keyMask Keycode = 0x003F shiftMask Keycode = 0x0040 altgrMask Keycode = 0x0080 deadkeysMask Keycode = 0x0700 circumflexBits Keycode = 0x0100 acuteAccentBits Keycode = 0x0200 graveAccentBits Keycode = 0x0300 tildeBits Keycode = 0x0400 diaeresisBits Keycode = 0x0500 deadkeyCircumflex Keycode = Key6 | shiftMask deadkeyAcuteAccent Keycode = KeyQuote deadkeyGraveAccent Keycode = KeyTilde deadkeyTilde Keycode = KeyTilde | shiftMask deadkeyDiaeresis Keycode = KeyQuote | shiftMask ASCII00 Keycode = 0 // 0 NUL ASCII01 Keycode = 0 // 1 SOH ASCII02 Keycode = 0 // 2 STX ASCII03 Keycode = 0 // 3 ETX ASCII04 Keycode = 0 // 4 EOT ASCII05 Keycode = 0 // 5 ENQ ASCII06 Keycode = 0 // 6 ACK ASCII07 Keycode = 0 // 7 BEL ASCII08 Keycode = KeyBackspace // 8 BS ASCII09 Keycode = KeyTab // 9 TAB ASCII0A Keycode = KeyEnter // 10 LF ASCII0B Keycode = 0 // 11 VT ASCII0C Keycode = 0 // 12 FF ASCII0D Keycode = 0 // 13 CR ASCII0E Keycode = 0 // 14 SO ASCII0F Keycode = 0 // 15 SI ASCII10 Keycode = 0 // 16 DEL ASCII11 Keycode = 0 // 17 DC1 ASCII12 Keycode = 0 // 18 DC2 ASCII13 Keycode = 0 // 19 DC3 ASCII14 Keycode = 0 // 20 DC4 ASCII15 Keycode = 0 // 21 NAK ASCII16 Keycode = 0 // 22 SYN ASCII17 Keycode = 0 // 23 ETB ASCII18 Keycode = 0 // 24 CAN ASCII19 Keycode = 0 // 25 EM ASCII1A Keycode = 0 // 26 SUB ASCII1B Keycode = 0 // 27 ESC ASCII1C Keycode = 0 // 28 FS ASCII1D Keycode = 0 // 29 GS ASCII1E Keycode = 0 // 30 RS ASCII1F Keycode = 0 // 31 US ASCII20 Keycode = KeySpace // 32 SPACE ASCII21 Keycode = Key1 | shiftMask // 33 ! ASCII22 Keycode = diaeresisBits | KeySpace // 34 " ASCII23 Keycode = Key3 | shiftMask // 35 # ASCII24 Keycode = Key4 | shiftMask // 36 $ ASCII25 Keycode = Key5 | shiftMask // 37 % ASCII26 Keycode = Key7 | shiftMask // 38 & ASCII27 Keycode = acuteAccentBits | KeySpace // 39 ' ASCII28 Keycode = Key9 | shiftMask // 40 ( ASCII29 Keycode = Key0 | shiftMask // 41 ) ASCII2A Keycode = Key8 | shiftMask // 42 * ASCII2B Keycode = KeyEqual | shiftMask // 43 + ASCII2C Keycode = KeyComma // 44 , ASCII2D Keycode = KeyMinus // 45 - ASCII2E Keycode = KeyPeriod // 46 . ASCII2F Keycode = KeySlash // 47 / ASCII30 Keycode = Key0 // 48 0 ASCII31 Keycode = Key1 // 49 1 ASCII32 Keycode = Key2 // 50 2 ASCII33 Keycode = Key3 // 51 3 ASCII34 Keycode = Key4 // 52 4 ASCII35 Keycode = Key5 // 53 5 ASCII36 Keycode = Key6 // 54 6 ASCII37 Keycode = Key7 // 55 7 ASCII38 Keycode = Key8 // 55 8 ASCII39 Keycode = Key9 // 57 9 ASCII3A Keycode = KeySemicolon | shiftMask // 58 : ASCII3B Keycode = KeySemicolon // 59 ; ASCII3C Keycode = KeyComma | shiftMask // 60 < ASCII3D Keycode = KeyEqual // 61 = ASCII3E Keycode = KeyPeriod | shiftMask // 62 > ASCII3F Keycode = KeySlash | shiftMask // 63 ? ASCII40 Keycode = Key2 | shiftMask // 64 @ ASCII41 Keycode = KeyA | shiftMask // 65 A ASCII42 Keycode = KeyB | shiftMask // 66 B ASCII43 Keycode = KeyC | shiftMask // 67 C ASCII44 Keycode = KeyD | shiftMask // 68 D ASCII45 Keycode = KeyE | shiftMask // 69 E ASCII46 Keycode = KeyF | shiftMask // 70 F ASCII47 Keycode = KeyG | shiftMask // 71 G ASCII48 Keycode = KeyH | shiftMask // 72 H ASCII49 Keycode = KeyI | shiftMask // 73 I ASCII4A Keycode = KeyJ | shiftMask // 74 J ASCII4B Keycode = KeyK | shiftMask // 75 K ASCII4C Keycode = KeyL | shiftMask // 76 L ASCII4D Keycode = KeyM | shiftMask // 77 M ASCII4E Keycode = KeyN | shiftMask // 78 N ASCII4F Keycode = KeyO | shiftMask // 79 O ASCII50 Keycode = KeyP | shiftMask // 80 P ASCII51 Keycode = KeyQ | shiftMask // 81 Q ASCII52 Keycode = KeyR | shiftMask // 82 R ASCII53 Keycode = KeyS | shiftMask // 83 S ASCII54 Keycode = KeyT | shiftMask // 84 T ASCII55 Keycode = KeyU | shiftMask // 85 U ASCII56 Keycode = KeyV | shiftMask // 86 V ASCII57 Keycode = KeyW | shiftMask // 87 W ASCII58 Keycode = KeyX | shiftMask // 88 X ASCII59 Keycode = KeyY | shiftMask // 89 Y ASCII5A Keycode = KeyZ | shiftMask // 90 Z ASCII5B Keycode = KeyLeftBrace // 91 [ ASCII5C Keycode = KeyBackslash // 92 \ ASCII5D Keycode = KeyRightBrace // 93 ] ASCII5E Keycode = circumflexBits | KeySpace // 94 ^ ASCII5F Keycode = KeyMinus | shiftMask // 95 ASCII60 Keycode = graveAccentBits | KeySpace // 96 ` ASCII61 Keycode = KeyA // 97 a ASCII62 Keycode = KeyB // 98 b ASCII63 Keycode = KeyC // 99 c ASCII64 Keycode = KeyD // 100 d ASCII65 Keycode = KeyE // 101 e ASCII66 Keycode = KeyF // 102 f ASCII67 Keycode = KeyG // 103 g ASCII68 Keycode = KeyH // 104 h ASCII69 Keycode = KeyI // 105 i ASCII6A Keycode = KeyJ // 106 j ASCII6B Keycode = KeyK // 107 k ASCII6C Keycode = KeyL // 108 l ASCII6D Keycode = KeyM // 109 m ASCII6E Keycode = KeyN // 110 n ASCII6F Keycode = KeyO // 111 o ASCII70 Keycode = KeyP // 112 p ASCII71 Keycode = KeyQ // 113 q ASCII72 Keycode = KeyR // 114 r ASCII73 Keycode = KeyS // 115 s ASCII74 Keycode = KeyT // 116 t ASCII75 Keycode = KeyU // 117 u ASCII76 Keycode = KeyV // 118 v ASCII77 Keycode = KeyW // 119 w ASCII78 Keycode = KeyX // 120 x ASCII79 Keycode = KeyY // 121 y ASCII7A Keycode = KeyZ // 122 z ASCII7B Keycode = KeyLeftBrace | shiftMask // 123 { ASCII7C Keycode = KeyBackslash | shiftMask // 124 | ASCII7D Keycode = KeyRightBrace | shiftMask // 125 } ASCII7E Keycode = tildeBits | KeySpace // 126 ~ ASCII7F Keycode = KeyBackspace // 127 DEL ISO88591A0 Keycode = KeySpace // 160 Nonbreakng Space ISO88591A1 Keycode = Key1 | altgrMask // 161 ¡ Inverted Exclamation ISO88591A2 Keycode = KeyC | altgrMask | shiftMask // 162 ¢ Cent SIGN ISO88591A3 Keycode = Key4 | altgrMask | shiftMask // 163 £ Pound Sign ISO88591A4 Keycode = Key4 | altgrMask // 164 ¤ Currency or Euro Sign ISO88591A5 Keycode = KeyMinus | altgrMask // 165 ¥ YEN SIGN ISO88591A6 Keycode = KeyBackslash | altgrMask | shiftMask // 166 ¦ BROKEN BAR ?? ISO88591A7 Keycode = KeyS | altgrMask | shiftMask // 167 § SECTION SIGN ISO88591A8 Keycode = KeyQuote | altgrMask | shiftMask // 168 ¨ DIAERESIS ISO88591A9 Keycode = KeyC | altgrMask // 169 © COPYRIGHT SIGN ISO88591AA Keycode = 0 // 170 ª FEMININE ORDINAL ISO88591AB Keycode = KeyLeftBrace | altgrMask // 171 « LEFT DOUBLE ANGLE QUOTE ISO88591AC Keycode = KeyBackslash | altgrMask // 172 ¬ NOT SIGN ?? ISO88591AD Keycode = 0 // 173 SOFT HYPHEN ISO88591AE Keycode = KeyR | altgrMask // 174 ® REGISTERED SIGN ISO88591AF Keycode = 0 // 175 ¯ MACRON ISO88591B0 Keycode = KeySemicolon | altgrMask | shiftMask // 176 ° DEGREE SIGN ISO88591B1 Keycode = 0 // 177 ± PLUS-MINUS SIGN ISO88591B2 Keycode = Key2 | altgrMask // 178 ² SUPERSCRIPT TWO ISO88591B3 Keycode = Key3 | altgrMask // 179 ³ SUPERSCRIPT THREE ISO88591B4 Keycode = KeyQuote | altgrMask // 180 ´ ACUTE ACCENT ISO88591B5 Keycode = KeyM | altgrMask // 181 µ MICRO SIGN ISO88591B6 Keycode = KeySemicolon | altgrMask // 182 ¶ PILCROW SIGN ISO88591B7 Keycode = 0 // 183 · MIDDLE DOT ISO88591B8 Keycode = 0 // 184 ¸ CEDILLA ISO88591B9 Keycode = Key1 | altgrMask | shiftMask // 185 ¹ SUPERSCRIPT ONE ISO88591BA Keycode = 0 // 186 º MASCULINE ORDINAL ISO88591BB Keycode = KeyRightBrace | altgrMask // 187 » RIGHT DOUBLE ANGLE QUOTE ISO88591BC Keycode = Key6 | altgrMask // 188 ¼ FRACTION ONE QUARTER ISO88591BD Keycode = Key7 | altgrMask // 189 ½ FRACTION ONE HALF ISO88591BE Keycode = Key8 | altgrMask // 190 ¾ FRACTION THREE QUARTERS ISO88591BF Keycode = KeySlash | altgrMask // 191 ¿ INVERTED QUESTION MARK ISO88591C0 Keycode = graveAccentBits | KeyA | shiftMask // 192 À A GRAVE ISO88591C1 Keycode = KeyA | altgrMask | shiftMask // 193 Á A ACUTE ISO88591C2 Keycode = circumflexBits | KeyA | shiftMask // 194 Â A CIRCUMFLEX ISO88591C3 Keycode = tildeBits | KeyA | shiftMask // 195 Ã A TILDE ISO88591C4 Keycode = KeyQ | altgrMask | shiftMask // 196 Ä A DIAERESIS ISO88591C5 Keycode = KeyW | altgrMask | shiftMask // 197 Å A RING ABOVE ISO88591C6 Keycode = KeyZ | altgrMask | shiftMask // 198 Æ AE ISO88591C7 Keycode = KeyComma | altgrMask | shiftMask // 199 Ç C CEDILLA ISO88591C8 Keycode = graveAccentBits | KeyE | shiftMask // 200 È E GRAVE ISO88591C9 Keycode = KeyE | altgrMask | shiftMask // 201 É E ACUTE ISO88591CA Keycode = circumflexBits | KeyE | shiftMask // 202 Ê E CIRCUMFLEX ISO88591CB Keycode = diaeresisBits | KeyE | shiftMask // 203 Ë E DIAERESIS ISO88591CC Keycode = graveAccentBits | KeyI | shiftMask // 204 Ì I GRAVE ISO88591CD Keycode = KeyI | altgrMask | shiftMask // 205 Í I ACUTE ISO88591CE Keycode = circumflexBits | KeyI | shiftMask // 206 Î I CIRCUMFLEX ISO88591CF Keycode = diaeresisBits | KeyI | shiftMask // 207 Ï I DIAERESIS ISO88591D0 Keycode = KeyD | altgrMask | shiftMask // 208 Ð ETH ISO88591D1 Keycode = KeyN | altgrMask | shiftMask // 209 Ñ N TILDE ISO88591D2 Keycode = graveAccentBits | KeyO | shiftMask // 210 Ò O GRAVE ISO88591D3 Keycode = KeyO | altgrMask | shiftMask // 211 Ó O ACUTE ISO88591D4 Keycode = circumflexBits | KeyO | shiftMask // 212 Ô O CIRCUMFLEX ISO88591D5 Keycode = tildeBits | KeyO | shiftMask // 213 Õ O TILDE ISO88591D6 Keycode = KeyP | altgrMask | shiftMask // 214 Ö O DIAERESIS ISO88591D7 Keycode = KeyEqual | altgrMask // 215 × MULTIPLICATION ISO88591D8 Keycode = KeyL | altgrMask | shiftMask // 216 Ø O STROKE ISO88591D9 Keycode = graveAccentBits | KeyU | shiftMask // 217 Ù U GRAVE ISO88591DA Keycode = KeyU | altgrMask | shiftMask // 218 Ú U ACUTE ISO88591DB Keycode = circumflexBits | KeyU | shiftMask // 219 Û U CIRCUMFLEX ISO88591DC Keycode = KeyY | altgrMask | shiftMask // 220 Ü U DIAERESIS ISO88591DD Keycode = acuteAccentBits | KeyY | shiftMask // 221 Ý Y ACUTE ISO88591DE Keycode = KeyT | altgrMask | shiftMask // 222 Þ THORN ISO88591DF Keycode = KeyS | altgrMask // 223 ß SHARP S ISO88591E0 Keycode = graveAccentBits | KeyA // 224 à a GRAVE ISO88591E1 Keycode = KeyA | altgrMask // 225 á a ACUTE ISO88591E2 Keycode = circumflexBits | KeyA // 226 â a CIRCUMFLEX ISO88591E3 Keycode = tildeBits | KeyA // 227 ã a TILDE ISO88591E4 Keycode = diaeresisBits | KeyA // 228 ä a DIAERESIS ISO88591E5 Keycode = KeyW | altgrMask // 229 å a RING ABOVE ISO88591E6 Keycode = KeyZ | altgrMask // 230 æ ae ISO88591E7 Keycode = KeyComma | altgrMask // 231 ç c CEDILLA ISO88591E8 Keycode = graveAccentBits | KeyE // 232 è e GRAVE ISO88591E9 Keycode = acuteAccentBits | KeyE // 233 é e ACUTE ISO88591EA Keycode = circumflexBits | KeyE // 234 ê e CIRCUMFLEX ISO88591EB Keycode = diaeresisBits | KeyE // 235 ë e DIAERESIS ISO88591EC Keycode = graveAccentBits | KeyI // 236 ì i GRAVE ISO88591ED Keycode = KeyI | altgrMask // 237 í i ACUTE ISO88591EE Keycode = circumflexBits | KeyI // 238 î i CIRCUMFLEX ISO88591EF Keycode = diaeresisBits | KeyI // 239 ï i DIAERESIS ISO88591F0 Keycode = KeyD | altgrMask // 240 ð ETH ISO88591F1 Keycode = KeyN | altgrMask // 241 ñ n TILDE ISO88591F2 Keycode = graveAccentBits | KeyO // 242 ò o GRAVE ISO88591F3 Keycode = KeyO | altgrMask // 243 ó o ACUTE ISO88591F4 Keycode = circumflexBits | KeyO // 244 ô o CIRCUMFLEX ISO88591F5 Keycode = tildeBits | KeyO // 245 õ o TILDE ISO88591F6 Keycode = KeyP | altgrMask // 246 ö o DIAERESIS ISO88591F7 Keycode = KeyEqual | altgrMask | shiftMask // 247 ÷ DIVISION ISO88591F8 Keycode = KeyL | altgrMask // 248 ø o STROKE ISO88591F9 Keycode = graveAccentBits | KeyU // 249 ù u GRAVE ISO88591FA Keycode = KeyU | altgrMask // 250 ú u ACUTE ISO88591FB Keycode = circumflexBits | KeyU // 251 û u CIRCUMFLEX ISO88591FC Keycode = KeyY | altgrMask // 252 ü u DIAERESIS ISO88591FD Keycode = acuteAccentBits | KeyY // 253 ý y ACUTE ISO88591FE Keycode = KeyT | altgrMask // 254 þ THORN ISO88591FF Keycode = diaeresisBits | KeyY // 255 ÿ y DIAERESIS UNICODE20AC Keycode = Key5 | altgrMask // 20AC € Euro Sign ) var ascii = [...]Keycode{ ASCII00.mask(), ASCII01.mask(), ASCII02.mask(), ASCII03.mask(), ASCII04.mask(), ASCII05.mask(), ASCII06.mask(), ASCII07.mask(), ASCII08.mask(), ASCII09.mask(), ASCII0A.mask(), ASCII0B.mask(), ASCII0C.mask(), ASCII0D.mask(), ASCII0E.mask(), ASCII0F.mask(), ASCII10.mask(), ASCII11.mask(), ASCII12.mask(), ASCII13.mask(), ASCII14.mask(), ASCII15.mask(), ASCII16.mask(), ASCII17.mask(), ASCII18.mask(), ASCII19.mask(), ASCII1A.mask(), ASCII1B.mask(), ASCII1C.mask(), ASCII1D.mask(), ASCII1E.mask(), ASCII1F.mask(), ASCII20.mask(), ASCII21.mask(), ASCII22.mask(), ASCII23.mask(), ASCII24.mask(), ASCII25.mask(), ASCII26.mask(), ASCII27.mask(), ASCII28.mask(), ASCII29.mask(), ASCII2A.mask(), ASCII2B.mask(), ASCII2C.mask(), ASCII2D.mask(), ASCII2E.mask(), ASCII2F.mask(), ASCII30.mask(), ASCII31.mask(), ASCII32.mask(), ASCII33.mask(), ASCII34.mask(), ASCII35.mask(), ASCII36.mask(), ASCII37.mask(), ASCII38.mask(), ASCII39.mask(), ASCII3A.mask(), ASCII3B.mask(), ASCII3C.mask(), ASCII3D.mask(), ASCII3E.mask(), ASCII3F.mask(), ASCII40.mask(), ASCII41.mask(), ASCII42.mask(), ASCII43.mask(), ASCII44.mask(), ASCII45.mask(), ASCII46.mask(), ASCII47.mask(), ASCII48.mask(), ASCII49.mask(), ASCII4A.mask(), ASCII4B.mask(), ASCII4C.mask(), ASCII4D.mask(), ASCII4E.mask(), ASCII4F.mask(), ASCII50.mask(), ASCII51.mask(), ASCII52.mask(), ASCII53.mask(), ASCII54.mask(), ASCII55.mask(), ASCII56.mask(), ASCII57.mask(), ASCII58.mask(), ASCII59.mask(), ASCII5A.mask(), ASCII5B.mask(), ASCII5C.mask(), ASCII5D.mask(), ASCII5E.mask(), ASCII5F.mask(), ASCII60.mask(), ASCII61.mask(), ASCII62.mask(), ASCII63.mask(), ASCII64.mask(), ASCII65.mask(), ASCII66.mask(), ASCII67.mask(), ASCII68.mask(), ASCII69.mask(), ASCII6A.mask(), ASCII6B.mask(), ASCII6C.mask(), ASCII6D.mask(), ASCII6E.mask(), ASCII6F.mask(), ASCII70.mask(), ASCII71.mask(), ASCII72.mask(), ASCII73.mask(), ASCII74.mask(), ASCII75.mask(), ASCII76.mask(), ASCII77.mask(), ASCII78.mask(), ASCII79.mask(), ASCII7A.mask(), ASCII7B.mask(), ASCII7C.mask(), ASCII7D.mask(), ASCII7E.mask(), ASCII7F.mask(), } var iso88591 = [...]Keycode{ ISO88591A0.mask(), ISO88591A1.mask(), ISO88591A2.mask(), ISO88591A3.mask(), ISO88591A4.mask(), ISO88591A5.mask(), ISO88591A6.mask(), ISO88591A7.mask(), ISO88591A8.mask(), ISO88591A9.mask(), ISO88591AA.mask(), ISO88591AB.mask(), ISO88591AC.mask(), ISO88591AD.mask(), ISO88591AE.mask(), ISO88591AF.mask(), ISO88591B0.mask(), ISO88591B1.mask(), ISO88591B2.mask(), ISO88591B3.mask(), ISO88591B4.mask(), ISO88591B5.mask(), ISO88591B6.mask(), ISO88591B7.mask(), ISO88591B8.mask(), ISO88591B9.mask(), ISO88591BA.mask(), ISO88591BB.mask(), ISO88591BC.mask(), ISO88591BD.mask(), ISO88591BE.mask(), ISO88591BF.mask(), ISO88591C0.mask(), ISO88591C1.mask(), ISO88591C2.mask(), ISO88591C3.mask(), ISO88591C4.mask(), ISO88591C5.mask(), ISO88591C6.mask(), ISO88591C7.mask(), ISO88591C8.mask(), ISO88591C9.mask(), ISO88591CA.mask(), ISO88591CB.mask(), ISO88591CC.mask(), ISO88591CD.mask(), ISO88591CE.mask(), ISO88591CF.mask(), ISO88591D0.mask(), ISO88591D1.mask(), ISO88591D2.mask(), ISO88591D3.mask(), ISO88591D4.mask(), ISO88591D5.mask(), ISO88591D6.mask(), ISO88591D7.mask(), ISO88591D8.mask(), ISO88591D9.mask(), ISO88591DA.mask(), ISO88591DB.mask(), ISO88591DC.mask(), ISO88591DD.mask(), ISO88591DE.mask(), ISO88591DF.mask(), ISO88591E0.mask(), ISO88591E1.mask(), ISO88591E2.mask(), ISO88591E3.mask(), ISO88591E4.mask(), ISO88591E5.mask(), ISO88591E6.mask(), ISO88591E7.mask(), ISO88591E8.mask(), ISO88591E9.mask(), ISO88591EA.mask(), ISO88591EB.mask(), ISO88591EC.mask(), ISO88591ED.mask(), ISO88591EE.mask(), ISO88591EF.mask(), ISO88591F0.mask(), ISO88591F1.mask(), ISO88591F2.mask(), ISO88591F3.mask(), ISO88591F4.mask(), ISO88591F5.mask(), ISO88591F6.mask(), ISO88591F7.mask(), ISO88591F8.mask(), ISO88591F9.mask(), ISO88591FA.mask(), ISO88591FB.mask(), ISO88591FC.mask(), ISO88591FD.mask(), ISO88591FE.mask(), ISO88591FF.mask(), } ================================================ FILE: src/machine/usb/hid/mouse/mouse.go ================================================ package mouse import ( "machine" "machine/usb/hid" ) var Mouse *mouse type Button byte const ( Left Button = 1 << iota Right Middle Back Forward ) type mouse struct { buf *hid.RingBuffer button Button waitTxc bool } func init() { if Mouse == nil { Mouse = newMouse() hid.SetHandler(Mouse) } } // New returns the USB hid-mouse port. // Deprecated, better to just use Port() func New() *mouse { return Port() } // Port returns the USB hid-mouse port. func Port() *mouse { return Mouse } func newMouse() *mouse { return &mouse{ buf: hid.NewRingBuffer(), } } func (m *mouse) TxHandler() bool { m.waitTxc = false if b, ok := m.buf.Get(); ok { m.waitTxc = true hid.SendUSBPacket(b[:5]) return true } return false } func (m *mouse) RxHandler(b []byte) bool { return false } func (m *mouse) tx(b []byte) { if machine.USBDev.InitEndpointComplete { if m.waitTxc { m.buf.Put(b) } else { m.waitTxc = true hid.SendUSBPacket(b) } } } // Move is a function that moves the mouse cursor. func (m *mouse) Move(vx, vy int) { if vx == 0 && vy == 0 { return } if vx < -128 { vx = -128 } if vx > 127 { vx = 127 } if vy < -128 { vy = -128 } if vy > 127 { vy = 127 } m.tx([]byte{ 0x01, byte(m.button), byte(vx), byte(vy), 0x00, }) } // Click clicks the mouse button. func (m *mouse) Click(btn Button) { m.Press(btn) m.Release(btn) } // Press presses the given mouse buttons. func (m *mouse) Press(btn Button) { m.button |= btn m.tx([]byte{ 0x01, byte(m.button), 0x00, 0x00, 0x00, }) } // Release releases the given mouse buttons. func (m *mouse) Release(btn Button) { m.button &= ^btn m.tx([]byte{ 0x01, byte(m.button), 0x00, 0x00, 0x00, }) } // Wheel controls the mouse wheel. func (m *mouse) Wheel(v int) { if v == 0 { return } if v < -128 { v = -128 } if v > 127 { v = 127 } m.tx([]byte{ 0x01, byte(m.button), 0x00, 0x00, byte(v), }) } // WheelDown turns the mouse wheel down. func (m *mouse) WheelDown() { m.Wheel(-1) } // WheelUp turns the mouse wheel up. func (m *mouse) WheelUp() { m.Wheel(1) } ================================================ FILE: src/machine/usb/msc/cbw.go ================================================ package msc import ( "encoding/binary" "machine/usb/msc/csw" "machine/usb/msc/scsi" ) const ( cbwMsgLen = 31 // Command Block Wrapper (CBW) message length Signature = 0x43425355 // "USBC" in little endian ) type CBW struct { HasCmd bool Data []byte } func (c *CBW) Tag() uint32 { return binary.LittleEndian.Uint32(c.Data[4:8]) } func (c *CBW) length() int { return len(c.Data) } func (c *CBW) validLength() bool { return len(c.Data) == cbwMsgLen } func (c *CBW) validSignature() bool { return binary.LittleEndian.Uint32(c.Data[:4]) == Signature } func (c *CBW) SCSICmd() scsi.Cmd { return scsi.Cmd{Data: c.Data[15:]} } func (c *CBW) transferLength() uint32 { return binary.LittleEndian.Uint32(c.Data[8:12]) } // isIn returns true if the command direction is from the device to the host. func (c *CBW) isIn() bool { return c.Data[12]>>7 != 0 } // isOut returns true if the command direction is from the host to the device. func (c *CBW) isOut() bool { return !c.isIn() } func (c *CBW) CSW(status csw.Status, residue uint32, b []byte) { // Signature: "USBS" 53425355h (little endian) binary.LittleEndian.PutUint32(b[:4], csw.Signature) // Tag: (same as CBW) copy(b[4:8], c.Data[4:8]) // Data Residue: (untransferred bytes) binary.LittleEndian.PutUint32(b[8:12], residue) // Status: b[12] = byte(status) } ================================================ FILE: src/machine/usb/msc/csw/csw.go ================================================ package csw type Status uint8 const ( StatusPassed Status = iota StatusFailed StatusPhaseError ) const ( MsgLen = 13 Signature = 0x53425355 // "USBS" in little endian ) ================================================ FILE: src/machine/usb/msc/disk.go ================================================ package msc import ( "encoding/binary" "errors" "fmt" "machine" "time" ) var ( errWriteOutOfBounds = errors.New("WriteAt offset out of bounds") ) // RegisterBlockDevice registers a BlockDevice provider with the MSC driver func (m *msc) RegisterBlockDevice(dev machine.BlockDevice) { m.dev = dev if cap(m.blockCache) != int(dev.WriteBlockSize()) { m.blockCache = make([]byte, dev.WriteBlockSize()) m.buf = make([]byte, dev.WriteBlockSize()) } m.blockSizeRaw = uint32(m.dev.WriteBlockSize()) m.blockCount = uint32(m.dev.Size()) / m.blockSizeUSB // Read/write/erase operations must be aligned to the underlying hardware blocks. In order to align // them we assume the provided block device is aligned to the end of the underlying hardware block // device and offset all reads/writes by the remaining bytes that don't make up a full block. m.blockOffset = uint32(m.dev.Size()) % m.blockSizeUSB // FIXME: Figure out what to do if the emulated write block size is larger than the erase block size // Set VPD UNMAP fields for i := range vpdPages { if vpdPages[i].PageCode == 0xb0 { // 0xb0 - 5.4.5 Block Limits VPD page (B0h) if len(vpdPages[i].Data) >= 28 { // Set the OPTIMAL UNMAP GRANULARITY (write blocks per erase block) granularity := uint32(dev.EraseBlockSize()) / m.blockSizeUSB binary.BigEndian.PutUint32(vpdPages[i].Data[24:28], granularity) } if len(vpdPages[i].Data) >= 32 { // Set the UNMAP GRANULARITY ALIGNMENT (first sector of first full erase block) // The unmap granularity alignment is used to calculate an optimal unmap request starting LBA as follows: // optimal unmap request starting LBA = (n * OPTIMAL UNMAP GRANULARITY) + UNMAP GRANULARITY ALIGNMENT // where n is zero or any positive integer value // https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf // We assume the block device is aligned to the end of the underlying block device blockOffset := uint32(dev.EraseBlockSize()) % m.blockSizeUSB // Set the UGAVALID bit to indicate that the UNMAP GRANULARITY ALIGNMENT is valid blockOffset |= 0x80000000 binary.BigEndian.PutUint32(vpdPages[i].Data[28:32], blockOffset) } break } } } var _ machine.BlockDevice = (*RecorderDisk)(nil) // RecorderDisk is a block device that records actions taken on it type RecorderDisk struct { dev machine.BlockDevice log []RecorderRecord last time.Time time time.Time } type RecorderRecord struct { OpCode RecorderOpCode Offset int64 Length int Data []byte Time int64 valid bool } type RecorderOpCode uint8 const ( RecorderOpCodeRead RecorderOpCode = iota RecorderOpCodeWrite RecorderOpCodeEraseBlocks ) // NewRecorderDisk creates a new RecorderDisk instance func NewRecorderDisk(dev machine.BlockDevice, count int) *RecorderDisk { d := &RecorderDisk{ dev: dev, log: make([]RecorderRecord, 0, count), last: time.Now(), } for i := 0; i < count; i++ { d.log = append(d.log, RecorderRecord{ OpCode: RecorderOpCodeRead, Offset: 0, Length: 0, Data: make([]byte, dev.WriteBlockSize()), Time: 0, }) } return d } func (d *RecorderDisk) Size() int64 { return d.dev.Size() } func (d *RecorderDisk) WriteBlockSize() int64 { return d.dev.WriteBlockSize() } func (d *RecorderDisk) EraseBlockSize() int64 { return d.dev.EraseBlockSize() } func (d *RecorderDisk) EraseBlocks(startBlock, numBlocks int64) error { d.Record(RecorderOpCodeEraseBlocks, startBlock, int(numBlocks), []byte{}) return d.dev.EraseBlocks(startBlock, numBlocks) } func (d *RecorderDisk) ReadAt(buffer []byte, offset int64) (int, error) { n, err := d.dev.ReadAt(buffer, offset) d.Record(RecorderOpCodeRead, offset, n, buffer) return n, err } func (d *RecorderDisk) WriteAt(buffer []byte, offset int64) (int, error) { n, err := d.dev.WriteAt(buffer, offset) d.Record(RecorderOpCodeWrite, offset, n, buffer) return n, err } func (d *RecorderDisk) Record(opCode RecorderOpCode, offset int64, length int, data []byte) { n := len(d.log) - 1 // Shift the log entries up to make room for a new entry for i := 0; i < n; i++ { d.log[i].OpCode = d.log[i+1].OpCode d.log[i].Offset = d.log[i+1].Offset d.log[i].Length = d.log[i+1].Length d.log[i].Data = d.log[i].Data[:len(d.log[i+1].Data)] copy(d.log[i].Data, d.log[i+1].Data) d.log[i].Time = d.log[i+1].Time d.log[i].valid = d.log[i+1].valid } // Append the new record d.log[n].OpCode = opCode d.log[n].Offset = offset d.log[n].Length = length d.log[n].Data = d.log[n].Data[:len(data)] copy(d.log[n].Data, data) d.log[n].Time = time.Since(d.time).Microseconds() d.time = d.time.Add(time.Since(d.time)) d.log[n].valid = true } func (d *RecorderDisk) ClearLog() { for i := range d.log { d.log[i].valid = false } d.time = time.Now() } func (d *RecorderDisk) GetLog() []RecorderRecord { return d.log } func (r *RecorderRecord) String() (string, bool) { opCode := "Unknown" switch r.OpCode { case RecorderOpCodeRead: opCode = "Read" case RecorderOpCodeWrite: opCode = "Write" case RecorderOpCodeEraseBlocks: opCode = "EraseBlocks" } return fmt.Sprintf("%s: %05d+%02d t:%d | % 0x", opCode, r.Offset, r.Length, r.Time, r.Data), r.valid } ================================================ FILE: src/machine/usb/msc/msc.go ================================================ package msc import ( "machine" "machine/usb" "machine/usb/descriptor" "machine/usb/msc/csw" "machine/usb/msc/scsi" "time" ) type mscState uint8 const ( mscStateCmd mscState = iota mscStateData mscStateStatus mscStateStatusSent mscStateNeedReset ) const ( mscInterface = 2 ) var MSC *msc type msc struct { buf []byte // Buffer for incoming/outgoing data blockCache []byte // Buffer for block read/write data taskQueued bool // Flag to indicate if the buffer has a task queued rxStalled bool // Flag to indicate if the RX endpoint is stalled txStalled bool // Flag to indicate if the TX endpoint is stalled maxPacketSize uint32 // Maximum packet size for the IN endpoint respStatus csw.Status // Response status for the last command sendZLP bool // Flag to indicate if a zero-length packet should be sent before sending CSW cbw *CBW // Last received Command Block Wrapper queuedBytes uint32 // Number of bytes queued for sending sentBytes uint32 // Number of bytes sent transferBytes uint32 // Total bytes to send cswBuf []byte // CSW response buffer state mscState maxLUN uint8 // Maximum Logical Unit Number (n-1 for n LUNs) dev machine.BlockDevice blockCount uint32 // Number of blocks in the device blockOffset uint32 // Byte offset of the first block in the device for aligned writes blockSizeUSB uint32 // Write block size as presented to the host over USB blockSizeRaw uint32 // Write block size of the underlying device hardware readOnly bool vendorID [8]byte // Max 8 ASCII characters productID [16]byte // Max 16 ASCII characters productRev [4]byte // Max 4 ASCII characters senseKey scsi.Sense addlSenseCode scsi.SenseCode addlSenseQualifier uint8 } // Port returns the USB Mass Storage port func Port(dev machine.BlockDevice) *msc { if MSC == nil { MSC = newMSC(dev) } return MSC } func newMSC(dev machine.BlockDevice) *msc { // Size our buffer to match the maximum packet size of the IN endpoint maxPacketSize := descriptor.EndpointMSCIN.GetMaxPacketSize() m := &msc{ // Some platforms require reads/writes to be aligned to the full underlying hardware block blockCache: make([]byte, dev.WriteBlockSize()), blockSizeUSB: 512, buf: make([]byte, dev.WriteBlockSize()), cswBuf: make([]byte, csw.MsgLen), cbw: &CBW{Data: make([]byte, 31)}, maxPacketSize: uint32(maxPacketSize), } m.RegisterBlockDevice(dev) // Set default inquiry data fields m.SetVendorID("TinyGo") m.SetProductID("Mass Storage") m.SetProductRev("1.0") // Initialize the USB Mass Storage Class (MSC) port machine.ConfigureUSBEndpoint(descriptor.MSC, []usb.EndpointConfig{ { Index: usb.MSC_ENDPOINT_IN, IsIn: true, Type: usb.ENDPOINT_TYPE_BULK, TxHandler: txHandler, StallHandler: setupPacketHandler, }, { Index: usb.MSC_ENDPOINT_OUT, IsIn: false, Type: usb.ENDPOINT_TYPE_BULK, DelayRxHandler: rxHandler, StallHandler: setupPacketHandler, }, }, []usb.SetupConfig{ { Index: mscInterface, Handler: setupPacketHandler, }, }, ) go m.processTasks() return m } func (m *msc) processTasks() { // Process tasks that cannot be done in an interrupt context for { if m.taskQueued { cmd := m.cbw.SCSICmd() switch cmd.CmdType() { case scsi.CmdWrite: m.scsiWrite(cmd, m.buf) case scsi.CmdUnmap: m.scsiUnmap(m.buf) } // Acknowledge the received data from the host m.queuedBytes = 0 m.taskQueued = false machine.AckUsbOutTransfer(usb.MSC_ENDPOINT_OUT) } time.Sleep(100 * time.Microsecond) } } func (m *msc) ready() bool { return m.dev != nil } func (m *msc) resetBuffer(length int) { // Reset the buffer to the specified length m.buf = m.buf[:length] for i := 0; i < length; i++ { m.buf[i] = 0 } } func (m *msc) sendUSBPacket(b []byte) { if machine.USBDev.InitEndpointComplete { // Send the USB packet machine.SendUSBInPacket(usb.MSC_ENDPOINT_IN, b) } } func (m *msc) sendCSW(status csw.Status) { // Generate CSW packet into m.cswBuf and send it residue := uint32(0) expected := m.cbw.transferLength() if expected >= m.sentBytes { residue = expected - m.sentBytes } m.cbw.CSW(status, residue, m.cswBuf) m.state = mscStateStatusSent m.sendUSBPacket(m.cswBuf) } func txHandler() { if MSC != nil { MSC.txHandler() } } func (m *msc) txHandler() { m.run([]byte{}, false) } func rxHandler(b []byte) bool { ack := true if MSC != nil { ack = MSC.run(b, true) } return ack } /* Connection Happy Path Overview: 0. MSC starts out in mscStateCmd status. 1. Host sends CBW (Command Block Wrapper) packet to MSC. - CBW contains the SCSI command to be executed, the length of the data to be transferred, etc. 2. MSC receives CBW. - CBW is validated and saved. - State is changed to mscStateData. - MSC routes the command to the appropriate SCSI command handler. 3. The MSC SCSI command handler responds with the initial data packet (if applicable). - If no data packet is needed, state is changed to mscStateStatus and step 4 is skipped. 4. The host acks the data packet and MSC calls m.scsiDataTransfer() to continue sending (or receiving) data. - This cycle continues until all data requested in the CBW is sent/received. - State is changed to mscStateStatus. - MSC waits for the host to ACK the final data packet. 5. MSC then sends a CSW (Command Status Wrapper) to the host to report the final status of the command execution and moves to mscStateStatusSent. 6. The host ACKs the CSW and the MSC moves back to mscStateCmd, waiting for the next CBW. */ func (m *msc) run(b []byte, isEpOut bool) bool { ack := true switch m.state { case mscStateCmd: // Receiving a new command block wrapper (CBW) // IN endpoint transfer complete confirmation, no action needed if !isEpOut { return ack } // Create a temporary CBW wrapper to validate the incoming data. Has to be temporary // to avoid it escaping into the heap since we're in interrupt context cbw := CBW{Data: b} // Verify size and signature if !cbw.validLength() || !cbw.validSignature() { // 6.6.1 CBW Not Valid // https://usb.org/sites/default/files/usbmassbulk_10.pdf m.state = mscStateNeedReset m.stallEndpoint(usb.MSC_ENDPOINT_IN) m.stallEndpoint(usb.MSC_ENDPOINT_OUT) m.stallEndpoint(usb.CONTROL_ENDPOINT) return ack } // Save the validated CBW for later reference copy(m.cbw.Data, b) // Move on to the data transfer phase next go around (after sending the first message) m.state = mscStateData m.transferBytes = cbw.transferLength() m.queuedBytes = 0 m.sentBytes = 0 m.respStatus = csw.StatusPassed m.scsiCmdBegin() case mscStateData: // Transfer data ack = m.scsiDataTransfer(b) case mscStateStatus: // Sending CSW status response // Placed after the switch statement so we can send the CSW without having to send a packet // to cycle back through this block, e.g. with TEST UNIT READY which sends only a CSW after // setting the sense key/add'l code/qualifier internally case mscStateStatusSent: // Wait for the status phase to complete if !isEpOut && m.queuedBytes == csw.MsgLen { // Status confirmed sent, wait for next CBW m.state = mscStateCmd } else { // We're not expecting any data here, ignore it. Original log line: // TU_LOG1(" Warning expect SCSI Status but received unknown data\r\n"); } case mscStateNeedReset: // Received an invalid CBW message, stop everything until we get reset } // Send CSW status response // Placed after the switch statement so we can send the CSW without having to send a packet // to cycle back through this block, e.g. with TEST UNIT READY which sends only a CSW after // setting the sense key/add'l code/qualifier internally if m.state == mscStateStatus && !m.txStalled { if m.cbw.transferLength() > m.sentBytes && m.cbw.isIn() { // 6.7.2 The Thirteen Cases - Case 5 (Hi > Di): STALL before status m.stallEndpoint(usb.MSC_ENDPOINT_IN) } else if m.sendZLP { // Send a zero-length packet to force the end of the transfer before we send a CSW m.queuedBytes = 0 m.sendZLP = false m.sendUSBPacket(m.buf[:0]) } else { m.sendCSW(m.respStatus) m.state = mscStateCmd } } return ack } ================================================ FILE: src/machine/usb/msc/scsi/scsi.go ================================================ package scsi import ( "encoding/binary" "fmt" ) type Cmd struct { Data []byte } func (c *Cmd) CmdType() CmdType { return CmdType(c.Data[0]) } func (c *Cmd) BlockCount() uint32 { return uint32(binary.BigEndian.Uint16(c.Data[7:9])) } func (c *Cmd) LBA() uint32 { return binary.BigEndian.Uint32(c.Data[2:6]) } func (c Cmd) String() string { cmdType := c.CmdType() switch cmdType { case CmdRead: return fmt.Sprintf("%-28s LBA: % 3d, Block Count: %d", cmdType, c.LBA(), c.BlockCount()) case CmdWrite: return fmt.Sprintf("%-28s LBA: % 3d, Block Count: %d", cmdType, c.LBA(), c.BlockCount()) default: return fmt.Sprintf("%-28s % x", cmdType, c.Data) } } type CmdType uint8 const ( CmdTestUnitReady CmdType = 0x00 // TEST UNIT READY is used to determine if a device is ready to transfer data (read/write). The device does not perform a self-test operation CmdRequestSense CmdType = 0x03 // REQUEST SENSE returns the current sense data (status or error information) CmdInquiry CmdType = 0x12 // INQUIRY is used to obtain basic information from a target device CmdModeSelect6 CmdType = 0x15 // MODE SELECT (6) provides a means for the application client to specify medium, logical unit, or peripheral device parameters to the device server CmdModeSelect10 CmdType = 0x55 // MODE SELECT (10) provides a means for the application client to specify medium, logical unit, or peripheral device parameters to the device server CmdModeSense6 CmdType = 0x1A // MODE SENSE (6) provides a means for a device server to report parameters to an application client CmdModeSense10 CmdType = 0x5A // MODE SENSE (10) provides a means for a device server to report parameters to an application client with 64-bit logical block addressing CmdStartStopUnit CmdType = 0x1B // START STOP UNIT is used to start or stop the medium in a device server CmdPreventAllowMediumRemoval CmdType = 0x1E // PREVENT ALLOW MEDIUM REMOVAL is used to prevent or allow the removal of storage medium from a device server CmdReadFormatCapacity CmdType = 0x23 // READ FORMAT CAPACITY allows the Host to request a list of the possible format capacities for an installed writable media CmdReadCapacity CmdType = 0x25 // READ CAPACITY command is used to obtain data capacity information from a target device CmdRead CmdType = 0x28 // READ (10) requests that the device server read the specified logical block(s) and transfer them to the data-in buffer CmdWrite CmdType = 0x2A // WRITE (10) requests that the device server transfer the specified logical block(s) from the data-out buffer and write them CmdUnmap CmdType = 0x42 // UNMAP command is used to inform the device server that the specified logical block(s) are no longer in use ) func (c CmdType) String() string { switch c { case CmdTestUnitReady: return "TEST UNIT READY" case CmdRequestSense: return "REQUEST SENSE" case CmdInquiry: return "INQUIRY" case CmdModeSelect6: return "MODE SELECT (6)" case CmdModeSelect10: return "MODE SELECT (10)" case CmdModeSense6: return "MODE SENSE (6)" case CmdModeSense10: return "MODE SENSE (10)" case CmdStartStopUnit: return "START STOP UNIT" case CmdPreventAllowMediumRemoval: return "PREVENT ALLOW MEDIUM REMOVAL" case CmdReadFormatCapacity: return "READ FORMAT CAPACITY" case CmdReadCapacity: return "READ CAPACITY" case CmdRead: return "READ (10)" case CmdWrite: return "WRITE (10)" case CmdUnmap: return "UNMAP" default: return fmt.Sprintf("Unknown Command (0x%0x)", byte(c)) } } type Sense uint8 const ( // 4.5.6 Sense key and sense code definitions // https://www.t10.org/ftp/t10/document.08/08-309r0.pdf SenseNone Sense = 0x00 // No specific Sense Key. This indicates no error condition SenseRecoveredError Sense = 0x01 // The last command completed successfully, but with some recovery action performed SenseNotReady Sense = 0x02 // The LUN addressed is not ready to be accessed SenseMediumError Sense = 0x03 // The command terminated with an unrecoverable error condition SenseHardwareError Sense = 0x04 // The drive detected an unrecoverable hardware failure while performing the command or during a self test SenseIllegalRequest Sense = 0x05 // An illegal parameter was provided in the command descriptor block or the additional parameters SenseUnitAttention Sense = 0x06 // The disk drive may have been reset SenseDataProtect Sense = 0x07 // A read or write command was attempted on a block that is protected from this operation and was not performed SenseBlankCheck Sense = 0x08 // A write-once device or a sequential-access device encountered blank medium or format-defined end-of-data indication while reading or that a write-once device encountered a non-blank medium while writing SenseFirmwareError Sense = 0x09 // Vendor specific sense key SenseAbortedCommand Sense = 0x0B // The disk drive aborted the command SenseVolumeOverflow Sense = 0x0D // A buffered peripheral device has reached the end of medium partition and data remains in the buffer that has not been written to the medium SenseMiscompare Sense = 0x0E // The source data did not match the data read from the medium ) type SenseCode uint8 const ( // SenseNotReady SenseCodeMediumNotPresent SenseCode = 0x3A // The storage medium is not present in the device (e.g. empty CD-ROM drive or flash card reader) // SenseIllegalRequest SenseCodeInvalidCmdOpCode SenseCode = 0x20 // The command operation code is not supported by the device SenseCodeInvalidFieldInCDB SenseCode = 0x24 // The command descriptor block (CDB) contains an invalid field // SenseDataProtect SenseCodeWriteProtected SenseCode = 0x27 // The media is write protected // SenseAbortedCommand SenseCodeLUNCommFailure SenseCode = 0x08 // LUN communication failure SenseCodeAbortedCmd SenseCode = 0x0B // The command was aborted by the device SenseCodeMsgReject SenseCode = 0x43 // The command was rejected by the device SenseCodeOverlapCmdAttempted SenseCode = 0x4E // The command was rejected by the device because it was overlapped by another command // SenseVolumeOverflow SenseCodeLBAOutOfRange SenseCode = 0x21 // The logical block address (LBA) is beyond the end of the volume ) const ( InquiryRespLen = 36 ModeSense6RespLen = 4 ModeSense10RespLen = 8 ReadCapacityRespLen = 8 ReadFormatCapacityRespLen = 12 RequestSenseRespLen = 18 ) ================================================ FILE: src/machine/usb/msc/scsi.go ================================================ package msc import ( "encoding/binary" "machine/usb" "machine/usb/msc/csw" "machine/usb/msc/scsi" ) func (m *msc) scsiCmdBegin() { cmd := m.cbw.SCSICmd() cmdType := cmd.CmdType() // Handle multi-packet commands switch cmdType { case scsi.CmdRead, scsi.CmdWrite: m.scsiCmdReadWrite(cmd) return case scsi.CmdUnmap: m.scsiCmdUnmap(cmd) return } if m.transferBytes > 0 && m.cbw.isOut() { // Reject any other multi-packet commands if m.transferBytes > m.maxPacketSize { m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidCmdOpCode) return } else { // Original comment from TinyUSB: // Didn't check for case 9 (Ho > Dn), which requires examining scsi command first // but it is OK to just receive data then responded with failed status } } switch cmdType { case scsi.CmdTestUnitReady: m.scsiTestUnitReady() case scsi.CmdReadCapacity: m.scsiCmdReadCapacity(cmd) case scsi.CmdReadFormatCapacity: m.scsiCmdReadFormatCapacity(cmd) case scsi.CmdInquiry: m.scsiCmdInquiry(cmd) case scsi.CmdModeSense6, scsi.CmdModeSense10: m.scsiCmdModeSense(cmd) case scsi.CmdRequestSense: m.scsiCmdRequestSense() case scsi.CmdPreventAllowMediumRemoval: m.scsiCmdPreventAllowMediumRemoval(cmd) default: // We don't support this command, error out m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidCmdOpCode) } if len(m.buf) == 0 { if m.transferBytes > 0 { // 6.7.2 The Thirteen Cases - Case 4 (Hi > Dn) // https://usb.org/sites/default/files/usbmassbulk_10.pdf m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, 0) } else { // 6.7.1 The Thirteen Cases - Case 1 Hn = Dn: all good // https://usb.org/sites/default/files/usbmassbulk_10.pdf m.state = mscStateStatus } } else { if m.transferBytes == 0 { // 6.7.1 The Thirteen Cases - Case 2 (Hn < Di) // https://usb.org/sites/default/files/usbmassbulk_10.pdf m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, 0) } else { // Make sure we don't return more data than the host is expecting if m.cbw.transferLength() < uint32(len(m.buf)) { m.buf = m.buf[:m.cbw.transferLength()] } m.queuedBytes = uint32(len(m.buf)) m.sendUSBPacket(m.buf) } } } func (m *msc) scsiDataTransfer(b []byte) bool { cmd := m.cbw.SCSICmd() cmdType := cmd.CmdType() switch cmdType { case scsi.CmdWrite, scsi.CmdUnmap: if m.readOnly { m.sendScsiError(csw.StatusFailed, scsi.SenseDataProtect, scsi.SenseCodeWriteProtected) return true } return m.scsiQueueTask(cmdType, b) } // Update our sent bytes count to include the just-confirmed bytes m.sentBytes += m.queuedBytes if m.sentBytes >= m.transferBytes { // Transfer complete, send CSW after transfer confirmed m.state = mscStateStatus } else if cmdType == scsi.CmdRead { m.scsiRead(cmd) } else { // Other multi-packet commands are rejected in m.scsiCmdBegin() } return true } func (m *msc) scsiTestUnitReady() { m.resetBuffer(0) m.queuedBytes = 0 // Check if the device is ready if !m.ready() { // If not ready set sense data m.senseKey = scsi.SenseNotReady m.addlSenseCode = scsi.SenseCodeMediumNotPresent m.addlSenseQualifier = 0x00 } else { m.senseKey = 0 m.addlSenseCode = 0 m.addlSenseQualifier = 0 } } func (m *msc) scsiCmdReadCapacity(cmd scsi.Cmd) { m.resetBuffer(scsi.ReadCapacityRespLen) m.queuedBytes = scsi.ReadCapacityRespLen // Last LBA address (big endian) binary.BigEndian.PutUint32(m.buf[:4], m.blockCount-1) // Block size (big endian) binary.BigEndian.PutUint32(m.buf[4:8], m.blockSizeUSB) } func (m *msc) scsiCmdReadFormatCapacity(cmd scsi.Cmd) { m.resetBuffer(scsi.ReadFormatCapacityRespLen) m.queuedBytes = scsi.ReadFormatCapacityRespLen // bytes 0-2 - reserved m.buf[3] = 8 // Capacity list length // Number of blocks (big endian) binary.BigEndian.PutUint32(m.buf[4:8], m.blockCount) // Block size (24-bit, big endian) binary.BigEndian.PutUint32(m.buf[8:12], m.blockSizeUSB) // Descriptor Type - formatted media m.buf[8] = 2 } // MODE SENSE(6) / MODE SENSE(10) - Only used here to indicate that the device is write protected func (m *msc) scsiCmdModeSense(cmd scsi.Cmd) { respLen := uint32(scsi.ModeSense6RespLen) if cmd.CmdType() == scsi.CmdModeSense10 { respLen = scsi.ModeSense10RespLen } m.resetBuffer(int(respLen)) m.queuedBytes = respLen // The host allows a good amount of leeway in response size // Reset total bytes to what we'll actually send if m.transferBytes > respLen { m.transferBytes = respLen m.sendZLP = true } readOnly := byte(0) if m.readOnly { readOnly = 0x80 } switch cmd.CmdType() { case scsi.CmdModeSense6: // byte 0 - Number of bytes after this one m.buf[0] = byte(respLen) - 1 // byte 1 - Medium type (0x00 for direct access block device) // Bit 7 indicates write protected m.buf[2] = readOnly // byte 3 - Block descriptor length: 0 (not supported) case scsi.CmdModeSense10: // bytes 0-1 - Number of bytes after this one m.buf[1] = byte(respLen) - 2 // byte 2 - Medium type (0x00 for direct access block device) // Bit 7 indicates write protected m.buf[3] = readOnly } } // PREVENT/ALLOW MEDIUM REMOVAL - A flash drive doesn't have a removable medium, so this is a no-op func (m *msc) scsiCmdPreventAllowMediumRemoval(cmd scsi.Cmd) { m.resetBuffer(0) m.queuedBytes = 0 // Check if the device is ready if !m.ready() { // If not ready set sense data m.senseKey = scsi.SenseNotReady m.addlSenseCode = scsi.SenseCodeMediumNotPresent m.addlSenseQualifier = 0x00 } else { m.senseKey = 0 m.addlSenseCode = 0 m.addlSenseQualifier = 0 } m.state = mscStateStatus } // REQUEST SENSE - Returns error status codes when an error status is sent func (m *msc) scsiCmdRequestSense() { // Set the buffer size to the SCSI sense message size and clear m.resetBuffer(scsi.RequestSenseRespLen) m.queuedBytes = scsi.RequestSenseRespLen m.transferBytes = scsi.RequestSenseRespLen // 0x70 - current error, 0x71 - deferred error (not used) m.buf[0] = 0xF0 // 0x70 for current error plus 0x80 for valid flag bit // byte 1 - reserved m.buf[2] = uint8(m.senseKey) & 0x0F // Incorrect Length Indicator bit not supported // bytes 3-6 - Information (not used) // byte 7 - Additional Sense Length (bytes remaining in the message) m.buf[7] = scsi.RequestSenseRespLen - 8 // bytes 8-11 - Command Specific Information (not used) m.buf[12] = byte(m.addlSenseCode) // Additional Sense Code (optional) m.buf[13] = m.addlSenseQualifier // Additional Sense Code Qualifier (optional) // bytes 14-17 - reserved // Clear sense data after copied to buffer m.senseKey = 0 m.addlSenseCode = 0 m.addlSenseQualifier = 0 } func (m *msc) scsiCmdUnmap(cmd scsi.Cmd) { // Unmap sends a header in the CBW and a parameter list in the data stage // The parameter list has an 8 byte header and 16 bytes per item. If it's less than 24 bytes it's // not the format we're expecting and we won't be able to decode it. Same for if there isn't a // 8 byte header plus multiples of 16 bytes after that paramLen := binary.BigEndian.Uint16(m.cbw.Data[7:9]) if paramLen < 24 || (paramLen-8)%16 != 0 { m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidFieldInCDB) return } } func (m *msc) scsiQueueTask(cmdType scsi.CmdType, b []byte) bool { // Check if the incoming data is larger than our buffer if int(m.queuedBytes)+len(b) > cap(m.buf) { m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidFieldInCDB) return true } // Save the incoming data in our buffer for processing outside of interrupt context. if m.taskQueued { // If we already have a full task queue we can't accept this data m.sendScsiError(csw.StatusFailed, scsi.SenseAbortedCommand, scsi.SenseCodeMsgReject) return true } // Copy the queued task data into our buffer start := m.queuedBytes end := start + uint32(len(b)) m.buf = m.buf[:end] copy(m.buf[start:end], b) m.queuedBytes += uint32(len(b)) switch cmdType { case scsi.CmdWrite: // If we're writing data wait until we have a full write block of data that can be processed. if m.queuedBytes == uint32(cap(m.blockCache)) || (m.sentBytes+m.queuedBytes >= m.transferBytes) { m.taskQueued = true } case scsi.CmdUnmap: m.taskQueued = true } // Don't acknowledge the incoming data until we can process it. return !m.taskQueued } func (m *msc) sendScsiError(status csw.Status, key scsi.Sense, code scsi.SenseCode) { // Generate CSW into m.cswBuf expected := m.cbw.transferLength() residue := uint32(0) if expected > m.sentBytes { residue = expected - m.sentBytes } // Prepare to send CSW m.sendZLP = true // Ensure the transaction is signaled as ended before a CSW is sent m.respStatus = status m.state = mscStateStatus // Set the sense data m.senseKey = key m.addlSenseCode = code m.addlSenseQualifier = 0x00 // Not used if expected > 0 && residue > 0 { if m.cbw.isIn() { m.stallEndpoint(usb.MSC_ENDPOINT_IN) } else { m.stallEndpoint(usb.MSC_ENDPOINT_OUT) } } } ================================================ FILE: src/machine/usb/msc/scsi_inquiry.go ================================================ package msc import ( "encoding/binary" "machine/usb/msc/csw" "machine/usb/msc/scsi" ) type vpdPage struct { PageCode uint8 PageLength uint8 // Page data // First four bytes are always Device Type, Page Code, and Page Length (2 bytes) and are omitted here Data []byte } // These must be sorted in ascending order by PageCode var vpdPages = []vpdPage{ { // 0xb0 - 5.4.5 Block Limits VPD page (B0h) // https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf PageCode: 0xb0, PageLength: 0x3c, // 60 bytes Data: []byte{ 0x00, 0x00, // WSNZ, MAXIMUM COMPARE AND WRITE LENGTH - Not supported 0x00, 0x00, // OPTIMAL TRANSFER LENGTH GRANULARITY - Not supported 0x00, 0x00, 0x00, 0x00, // MAXIMUM TRANSFER LENGTH - Not supported 0x00, 0x00, 0x00, 0x00, // OPTIMAL TRANSFER LENGTH - Not supported 0x00, 0x00, 0x00, 0x00, // MAXIMUM PREFETCH LENGTH - Not supported 0xFF, 0xFF, 0xFF, 0xFF, // MAXIMUM UNMAP LBA COUNT - Maximum count supported 0x00, 0x00, 0x00, 0x03, // MAXIMUM UNMAP BLOCK DESCRIPTOR COUNT - Max 3 descriptors 0x00, 0x00, 0x00, 0x00, // OPTIMAL UNMAP GRANULARITY 0x00, 0x00, 0x00, 0x00, // UNMAP GRANULARITY ALIGNMENT (bit 7 on byte 28 sets UGAVALID) // From here on all bytes are zero and can be omitted from the response // 0x00, 0x00, 0x00, 0x00, // MAXIMUM WRITE SAME LENGTH - Not supported // 0x00, 0x00, 0x00, 0x00, // (8-bytes) // 0x00, 0x00, 0x00, 0x00, // MAXIMUM ATOMIC TRANSFER LENGTH - Not supported // 0x00, 0x00, 0x00, 0x00, // ATOMIC ALIGNMENT - Not supported // 0x00, 0x00, 0x00, 0x00, // ATOMIC TRANSFER LENGTH GRANULARITY - Not supported // 0x00, 0x00, 0x00, 0x00, // MAXIMUM ATOMIC TRANSFER LENGTH WITH ATOMIC BOUNDARY - Not supported // 0x00, 0x00, 0x00, 0x00, // MAXIMUM ATOMIC BOUNDARY SIZE - Not supported }, }, { // 0xb1 - 5.4.3 Block Device Characteristics VPD page (B1h) // https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf PageCode: 0xb1, PageLength: 0x3c, // 60 bytes (bytes 9+ are all reserved/zero) Data: []byte{ 0x00, 0x01, // Rotation rate (0x0001 - non-rotating medium) 0x00, // Product type - 0x00: Not indicated, 0x04: MMC/eMMC, 0x05: SD card 0x00, // WABEREQ/WACEREQ/Form Factor - Not specified 0x00, // ZBC/BOCS/FUAB/VBULS // Reserved (55 bytes) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, }, { // 0xb2 - 5.4.13 Logical Block Provisioning VPD page (B2h) // https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf PageCode: 0xB2, PageLength: 0x04, Data: []byte{ 0x00, // Logical Block Provisioning Threshold Exponent 0x80, // 0x80 - LBPU (UNMAP command supported) 0x00, // Minimum percentage/Provisioning type - Not specified 0x00, // Threshold percentage - Not supported }, }, } func (m *msc) scsiCmdInquiry(cmd scsi.Cmd) { evpd := cmd.Data[1] & 0x01 pageCode := cmd.Data[2] // PAGE CODE (byte 2) can't be set if the EVPD bit is not set if evpd == 0 { if pageCode == 0 { // Standard INQUIRY command m.scsiStdInquiry(cmd) } else { // 3.6.1 INQUIRY command introduction // https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidFieldInCDB) return } } else { m.scsiEvpdInquiry(cmd, pageCode) } } func (m *msc) scsiEvpdInquiry(cmd scsi.Cmd, pageCode uint8) { var pageLength int switch pageCode { case 0x00: // 5.4.18 Supported Vital Product Data pages (00h) // https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf pageLength = len(vpdPages) + 1 // Number of pages + 1 for 0x00 (excluded from vpdPages[]) m.resetBuffer(pageLength + 4) // n+4 supported VPD pages // bytes 4+ - Supported VPD pages in ascending order for i := 0; i < len(vpdPages); i++ { m.buf[4+i] = vpdPages[i].PageCode } default: found := false for i := range vpdPages { if vpdPages[i].PageCode == pageCode { // Our advertised page length is "for entertainment use only". Some pages have dozens of // reserved (zero) bytes at the end that don't actually need to be sent. If we omit them // from our response they are (correctly) presumed to be zero bytes by the host pageLength = int(vpdPages[i].PageLength) // We actually just send the length of the bytes we have plus the same four byte header, // but declare the length of the response according to the spec as appropriate m.resetBuffer(len(vpdPages[i].Data) + 4) copy(m.buf[4:], vpdPages[i].Data) found = true break } } if !found { // VPD page not found, send error m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidFieldInCDB) return } } // byte 0 - Peripheral Qualifier/Peripheral Device Type (0x00 for direct access block device) m.buf[1] = pageCode binary.BigEndian.PutUint16(m.buf[2:4], uint16(pageLength)) // Set total bytes to the length of our response m.queuedBytes = uint32(len(m.buf)) m.transferBytes = uint32(len(m.buf)) } func (m *msc) scsiStdInquiry(cmd scsi.Cmd) { m.resetBuffer(scsi.InquiryRespLen) m.queuedBytes = scsi.InquiryRespLen m.transferBytes = scsi.InquiryRespLen // byte 0 - Device Type (0x00 for direct access block device) // byte 1 - Removable media bit m.buf[1] = 0x80 // byte 2 - Version 0x00 - We claim conformance to no standard // byte 3 - Response data format m.buf[3] = 2 // byte 4 - Additional length (number of bytes after this one) m.buf[4] = scsi.InquiryRespLen - 5 // byte 5 - Not used // byte 6 - Not used // byte 7 - Not used // bytes 8-15 - Vendor ID copy(m.buf[8:16], m.vendorID[:]) // bytes 16-31 - Product ID copy(m.buf[16:32], m.productID[:]) // bytes 32-35 - Product revision level copy(m.buf[32:36], m.productRev[:]) } ================================================ FILE: src/machine/usb/msc/scsi_readwrite.go ================================================ package msc import ( "errors" "machine/usb/msc/csw" "machine/usb/msc/scsi" ) var invalidWriteError = errors.New("invalid write offset or length") func (m *msc) scsiCmdReadWrite(cmd scsi.Cmd) { status := m.validateScsiReadWrite(cmd) if status != csw.StatusPassed { m.sendScsiError(status, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidCmdOpCode) } else if m.transferBytes > 0 { if cmd.CmdType() == scsi.CmdRead { m.scsiRead(cmd) } else { // WRITE(10) and UNMAP commands don't take any action until the data stage begins } } else { // Zero byte transfer. No practical use case m.state = mscStateStatus } } // Validate SCSI READ(10) and WRITE(10) commands func (m *msc) validateScsiReadWrite(cmd scsi.Cmd) csw.Status { blockCount := cmd.BlockCount() // CBW wrapper transfer length if m.transferBytes == 0 { // If the SCSI command's block count doesn't loosely match the wrapper's transfer length something's wrong if blockCount > 0 { return csw.StatusPhaseError } // Zero length transfer. No practical use case, but explicitly not an error according to the spec return csw.StatusPassed } if (cmd.CmdType() == scsi.CmdRead && m.cbw.isOut()) || (cmd.CmdType() == scsi.CmdWrite && m.cbw.isIn()) { // If the command is READ(10) and the data direction is from host to device that's a problem // 6.7.3 The Thirteen Cases - Case 10 (Ho <> Di) // If the command is WRITE(10) and the data direction is from device to host that's also a problem // 6.7.2 The Thirteen Cases - Case 8 (Hi <> Do) // https://usb.org/sites/default/files/usbmassbulk_10.pdf return csw.StatusPhaseError } if blockCount == 0 { // We already checked for zero length transfer above, so this is a problem // 6.7.2 The Thirteen Cases - Case 4 (Hi > Dn) // https://usb.org/sites/default/files/usbmassbulk_10.pdf return csw.StatusFailed } if m.transferBytes/blockCount == 0 { // Block size shouldn't be small enough to round to zero // 6.7.2 The Thirteen Cases - Case 7 (Hi < Di) READ(10) or // 6.7.3 The Thirteen Cases - Case 13 (Ho < Do) WRITE(10) // https://usb.org/sites/default/files/usbmassbulk_10.pdf return csw.StatusPhaseError } return csw.StatusPassed } func (m *msc) usbToRawOffset(lba, offset uint32) (int64, int64) { // Convert the emulated block address to the underlying hardware block's start and offset rawLBA := (lba*m.blockSizeUSB + offset) / m.blockSizeRaw rawBlockOffset := int64((lba*m.blockSizeUSB + offset) % m.blockSizeRaw) return int64(m.blockOffset + rawLBA*m.blockSizeRaw), rawBlockOffset } func (m *msc) readBlock(b []byte, lba, offset uint32) (n int, err error) { // Convert the emulated block address to the underlying hardware block's start and offset blockStart, blockOffset := m.usbToRawOffset(lba, offset) // Read a full block from the underlying device into the block cache n, err = m.dev.ReadAt(m.blockCache, blockStart) n -= int(blockOffset) if n > len(b) { n = len(b) } copy(b, m.blockCache[blockOffset:]) return n, err } func (m *msc) writeBlock(b []byte, lba, offset uint32) (n int, err error) { // Convert the emulated block address to the underlying hardware block's start and offset blockStart, blockOffset := m.usbToRawOffset(lba, offset) if blockOffset != 0 || len(b) != int(m.blockSizeRaw) { return 0, invalidWriteError } // Write the full block to the underlying device n, err = m.dev.WriteAt(b, blockStart) n -= int(blockOffset) if n > len(b) { n = len(b) } return n, err } func (m *msc) scsiRead(cmd scsi.Cmd) { // Make sure we don't exceed the buffer size readEnd := m.transferBytes - m.sentBytes if readEnd > m.maxPacketSize { readEnd = m.maxPacketSize } // Resize the buffer to fit the read size m.resetBuffer(int(readEnd)) // Read data from the emulated block device n, err := m.readBlock(m.buf[:readEnd], cmd.LBA(), m.sentBytes) if err != nil || n == 0 { m.sendScsiError(csw.StatusFailed, scsi.SenseNotReady, scsi.SenseCodeMediumNotPresent) return } m.queuedBytes = readEnd m.sendUSBPacket(m.buf) } func (m *msc) scsiWrite(cmd scsi.Cmd, b []byte) { if m.readOnly { m.sendScsiError(csw.StatusFailed, scsi.SenseDataProtect, scsi.SenseCodeWriteProtected) return } // Write data to the block device n, err := m.writeBlock(b, cmd.LBA(), m.sentBytes) if err != nil || n < len(b) { m.sentBytes += uint32(n) m.sendScsiError(csw.StatusFailed, scsi.SenseNotReady, scsi.SenseCodeMediumNotPresent) } else { m.sentBytes += uint32(len(b)) } if m.sentBytes >= m.transferBytes { // Data transfer is complete, send CSW m.state = mscStateStatus m.run([]byte{}, true) } } ================================================ FILE: src/machine/usb/msc/scsi_unmap.go ================================================ package msc import ( "encoding/binary" "machine/usb/msc/csw" "machine/usb/msc/scsi" ) type Error int const ( errorLBAOutOfRange Error = iota ) func (e Error) Error() string { switch e { case errorLBAOutOfRange: return "LBA out of range" default: return "unknown error" } } func (m *msc) scsiUnmap(b []byte) { // Execute Order 66 (0x42) to wipe out the blocks // 3.54 Unmap Command (SBC-4) // https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf if m.readOnly { m.sendScsiError(csw.StatusFailed, scsi.SenseDataProtect, scsi.SenseCodeWriteProtected) return } // blockDescLen is the remaining length of block descriptors in the message, offset 8 bytes from // the start of this packet var blockDescLen uint16 // Decode the parameter list msgLen := binary.BigEndian.Uint16(b[:2]) // Length of the block descriptor portion of the message blockDescLen = binary.BigEndian.Uint16(b[2:4]) // Do some sanity checks on the message lengths (max 3 block descriptors to fit in one 64 byte packet) if msgLen < 8 || blockDescLen < 16 || msgLen-blockDescLen != 6 || blockDescLen > (3*16) { m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidFieldInCDB) return } // descEnd marks the end of the last full block descriptor in this packet descEnd := int(blockDescLen + 8) // Unmap the blocks we can from this packet for i := 8; i < descEnd; i += 16 { err := m.unmapBlocksFromDescriptor(b[i:], uint64(m.blockCount)) if err != nil { // TODO: Might need a better error code here for device errors? m.sendScsiError(csw.StatusFailed, scsi.SenseVolumeOverflow, scsi.SenseCodeLBAOutOfRange) return } } // FIXME: We need to handle erase block alignment m.sentBytes += uint32(len(b)) if m.sentBytes >= m.transferBytes { // Order 66 complete, send CSW to establish galactic empire m.state = mscStateStatus m.run([]byte{}, true) } } func (m *msc) unmapBlocksFromDescriptor(b []byte, numBlocks uint64) error { blockCount := binary.BigEndian.Uint32(b[8:12]) if blockCount == 0 { // No blocks to unmap. Explicitly not an error per the spec return nil } // This is technically a 64-bit LBA, but we can't address that many bytes // let alone blocks, so we just use the lower 32 bits lba := binary.BigEndian.Uint32(b[4:8]) // Make sure the unmap command doesn't extend past the end of the volume if lba+blockCount > m.blockCount { return errorLBAOutOfRange } // Convert the emulated block size to the underlying hardware erase block size blockStart := int64(lba*m.blockSizeUSB) / m.dev.EraseBlockSize() rawBlockCount := int64(blockCount*m.blockSizeUSB) / m.dev.EraseBlockSize() // Unmap the blocks return m.dev.EraseBlocks(blockStart, rawBlockCount) } ================================================ FILE: src/machine/usb/msc/setup.go ================================================ package msc import ( "machine" "machine/usb" ) func setupPacketHandler(setup usb.Setup) bool { if MSC != nil { return MSC.setupPacketHandler(setup) } return false } func (m *msc) setupPacketHandler(setup usb.Setup) bool { ok := false wValue := (uint16(setup.WValueH) << 8) | uint16(setup.WValueL) switch setup.BRequest { case usb.CLEAR_FEATURE: if setup.BmRequestType == 0x02 { // Host-to-Device | Standard | Endpoint ok = m.handleClearFeature(setup, wValue) } case usb.GET_MAX_LUN: if setup.BmRequestType == 0xA1 { // Device-to-Host | Class | Interface ok = m.handleGetMaxLun(setup, wValue) } case usb.MSC_RESET: if setup.BmRequestType == 0x21 { // Host-to-Device | Class | Interface ok = m.handleReset(setup, wValue) } } return ok } // Handles the CLEAR_FEATURE request for clearing ENDPOINT_HALT/stall func (m *msc) handleClearFeature(setup usb.Setup, wValue uint16) bool { ok := false // wValue is the feature selector (0x00 for ENDPOINT_HALT) // We aren't handling any other feature selectors // https://wiki.osdev.org/Universal_Serial_Bus#CLEAR_FEATURE if wValue != 0 { return ok } // Clearing the stall is not enough, continue stalling until a reset is received first // 6.6.1 CBW Not Valid // If the CBW is not valid, the device shall STALL the Bulk-In pipe. Also, the device // shall either STALL the Bulk-Out pipe, or the device shall accept and discard any // Bulk-Out data. The device shall maintain this state until a Reset Recovery // For Reset Recovery the host shall issue in the following order: : // (a) a Bulk-Only Mass Storage Reset (handleReset()) // (b) a Clear Feature HALT to the Bulk-In endpoint (clear stall IN) // (c) a Clear Feature HALT to the Bulk-Out endpoint (clear stall OUT) // https://usb.org/sites/default/files/usbmassbulk_10.pdf if m.state == mscStateNeedReset { wIndex := setup.WIndex & 0x7F // Clear the direction bit from the endpoint address for comparison if wIndex == usb.MSC_ENDPOINT_IN { m.stallEndpoint(usb.MSC_ENDPOINT_IN) } else if wIndex == usb.MSC_ENDPOINT_OUT { m.stallEndpoint(usb.MSC_ENDPOINT_OUT) } machine.SendZlp() return true } // Clear the direction bit from the endpoint address for comparison wIndex := setup.WIndex & 0x7F // Clear the IN/OUT stalls if addressed to the endpoint if wIndex == usb.MSC_ENDPOINT_IN { m.clearStallEndpoint(usb.MSC_ENDPOINT_IN) ok = true } if wIndex == usb.MSC_ENDPOINT_OUT { m.clearStallEndpoint(usb.MSC_ENDPOINT_OUT) ok = true } // Send a CSW if needed to resume after the IN endpoint stall is cleared if m.state == mscStateStatus && wIndex == usb.MSC_ENDPOINT_IN { m.sendCSW(m.respStatus) ok = true } if ok { machine.SendZlp() } return ok } // 3.2 Get Max LUN // https://usb.org/sites/default/files/usbmassbulk_10.pdf func (m *msc) handleGetMaxLun(setup usb.Setup, wValue uint16) bool { if setup.WIndex != mscInterface || setup.WLength != 1 || wValue != 0 { return false } // Send the maximum LUN ID number (zero-indexed, so n-1) supported by the device m.resetBuffer(1) // Shrink buffer to 1 byte m.buf[0] = m.maxLUN return machine.SendUSBInPacket(usb.CONTROL_ENDPOINT, m.buf) } // 3.1 Bulk-Only Mass Storage Reset // https://usb.org/sites/default/files/usbmassbulk_10.pdf func (m *msc) handleReset(setup usb.Setup, wValue uint16) bool { if setup.WIndex != mscInterface || setup.WLength != 0 || wValue != 0 { return false } // Reset to command waiting state m.state = mscStateCmd // Reset transfer state m.resetBuffer(0) m.senseKey = 0 m.addlSenseCode = 0 m.addlSenseQualifier = 0 // Send a zero-length packet (ZLP) to indicate the reset is complete machine.SendZlp() // Return true to indicate successful reset return true } func (m *msc) stallEndpoint(ep uint8) { if ep == usb.MSC_ENDPOINT_IN { m.txStalled = true machine.USBDev.SetStallEPIn(usb.MSC_ENDPOINT_IN) } else if ep == usb.MSC_ENDPOINT_OUT { m.rxStalled = true machine.USBDev.SetStallEPOut(usb.MSC_ENDPOINT_OUT) } else if ep == usb.CONTROL_ENDPOINT { machine.USBDev.SetStallEPIn(usb.CONTROL_ENDPOINT) } } func (m *msc) clearStallEndpoint(ep uint8) { if ep == usb.MSC_ENDPOINT_IN { machine.USBDev.ClearStallEPIn(usb.MSC_ENDPOINT_IN) m.txStalled = false } else if ep == usb.MSC_ENDPOINT_OUT { machine.USBDev.ClearStallEPOut(usb.MSC_ENDPOINT_OUT) m.rxStalled = false } } func (m *msc) setStringField(field []byte, value string) { copy(field, []byte(value)) for i := len(value); i < len(field); i++ { field[i] = 0x20 // Fill remaining bytes with spaces } } func (m *msc) SetVendorID(vendorID string) { m.setStringField(m.vendorID[:], vendorID) } func (m *msc) SetProductID(productID string) { m.setStringField(m.productID[:], productID) } func (m *msc) SetProductRev(productRev string) { m.setStringField(m.productRev[:], productRev) } func SetVendorID(vendorID string) { if MSC != nil { MSC.SetVendorID(vendorID) } } func SetProductID(productID string) { if MSC != nil { MSC.SetProductID(productID) } } func SetProductRev(productRev string) { if MSC != nil { MSC.SetProductRev(productRev) } } ================================================ FILE: src/machine/usb/usb.go ================================================ package usb var ( // TODO: allow setting these STRING_LANGUAGE = [2]uint16{(3 << 8) | (2 + 2), 0x0409} // English ) const ( DescriptorConfigCDC = 1 << iota DescriptorConfigHID DescriptorConfigMIDI DescriptorConfigJoystick ) const ( IMANUFACTURER = 1 IPRODUCT = 2 ISERIAL = 3 ENDPOINT_TYPE_DISABLE = 0xFF ENDPOINT_TYPE_CONTROL = 0x00 ENDPOINT_TYPE_ISOCHRONOUS = 0x01 ENDPOINT_TYPE_BULK = 0x02 ENDPOINT_TYPE_INTERRUPT = 0x03 EndpointOut = 0x00 EndpointIn = 0x80 EndpointPacketSize = 64 // 64 for Full Speed, EPT size max is 1024 // bRequest - standard requests GET_STATUS = 0 CLEAR_FEATURE = 1 SET_FEATURE = 3 SET_ADDRESS = 5 GET_DESCRIPTOR = 6 SET_DESCRIPTOR = 7 GET_CONFIGURATION = 8 SET_CONFIGURATION = 9 GET_INTERFACE = 10 SET_INTERFACE = 11 // bRequest - HID class-specific requests GET_REPORT = 1 GET_IDLE = 2 GET_PROTOCOL = 3 SET_REPORT = 9 SET_IDLE = 10 SET_PROTOCOL = 11 SET_REPORT_TYPE = 33 // bRequest - MSC class-specific requests GET_MAX_LUN = 0xFE MSC_RESET = 0xFF DEVICE_CLASS_COMMUNICATIONS = 0x02 DEVICE_CLASS_HUMAN_INTERFACE = 0x03 DEVICE_CLASS_STORAGE = 0x08 DEVICE_CLASS_VENDOR_SPECIFIC = 0xFF CONFIG_POWERED_MASK = 0x40 CONFIG_BUS_POWERED = 0x80 CONFIG_SELF_POWERED = 0xC0 CONFIG_REMOTE_WAKEUP = 0x20 // Interface NumberOfInterfaces = 3 CDC_ACM_INTERFACE = 0 // CDC ACM CDC_DATA_INTERFACE = 1 // CDC Data CDC_FIRST_ENDPOINT = 1 HID_INTERFACE = 2 // HID // Endpoint CONTROL_ENDPOINT = 0 CDC_ENDPOINT_ACM = 1 CDC_ENDPOINT_OUT = 2 CDC_ENDPOINT_IN = 3 HID_ENDPOINT_IN = 4 // for Interrupt In HID_ENDPOINT_OUT = 5 // for Interrupt Out MIDI_ENDPOINT_IN = 6 // for Bulk In MIDI_ENDPOINT_OUT = 7 // for Bulk Out MSC_ENDPOINT_IN = 6 // for Bulk In MSC_ENDPOINT_OUT = 7 // for Bulk Out // bmRequestType REQUEST_HOSTTODEVICE = 0x00 REQUEST_DEVICETOHOST = 0x80 REQUEST_DIRECTION = 0x80 REQUEST_STANDARD = 0x00 REQUEST_CLASS = 0x20 REQUEST_VENDOR = 0x40 REQUEST_TYPE = 0x60 REQUEST_DEVICE = 0x00 REQUEST_INTERFACE = 0x01 REQUEST_ENDPOINT = 0x02 REQUEST_OTHER = 0x03 REQUEST_RECIPIENT = 0x1F REQUEST_DEVICETOHOST_CLASS_INTERFACE = (REQUEST_DEVICETOHOST | REQUEST_CLASS | REQUEST_INTERFACE) REQUEST_HOSTTODEVICE_CLASS_INTERFACE = (REQUEST_HOSTTODEVICE | REQUEST_CLASS | REQUEST_INTERFACE) REQUEST_DEVICETOHOST_STANDARD_INTERFACE = (REQUEST_DEVICETOHOST | REQUEST_STANDARD | REQUEST_INTERFACE) ) type Setup struct { BmRequestType uint8 BRequest uint8 WValueL uint8 WValueH uint8 WIndex uint16 WLength uint16 } func NewSetup(data []byte) Setup { u := Setup{} u.BmRequestType = uint8(data[0]) u.BRequest = uint8(data[1]) u.WValueL = uint8(data[2]) u.WValueH = uint8(data[3]) u.WIndex = uint16(data[4]) | (uint16(data[5]) << 8) u.WLength = uint16(data[6]) | (uint16(data[7]) << 8) return u } var ( // VendorID aka VID is the officially assigned vendor number // for this USB device. Only set this if you know what you are doing, // since changing it can make it difficult to reflash some devices. VendorID uint16 // ProductID aka PID is the product number associated with the officially assigned // vendor number for this USB device. Only set this if you know what you are doing, // since changing it can make it difficult to reflash some devices. ProductID uint16 // Manufacturer is the manufacturer name displayed for this USB device. Manufacturer string // Product is the product name displayed for this USB device. Product string // Serial is the serial value displayed for this USB device. Assign a value to // transmit the serial to the host when requested. Serial string ) ================================================ FILE: src/machine/usb.go ================================================ //go:build sam || nrf52840 || rp2040 || rp2350 package machine import ( "machine/usb" "machine/usb/descriptor" "errors" ) type USBDevice struct { initcomplete bool InitEndpointComplete bool } var ( USBDev = &USBDevice{} USBCDC Serialer ) func initUSB() { enableUSBCDC() USBDev.Configure(UARTConfig{}) } // Using go:linkname here because there's a circular dependency between the // machine package and the machine/usb/cdc package. // //go:linkname enableUSBCDC machine/usb/cdc.EnableUSBCDC func enableUSBCDC() type Serialer interface { WriteByte(c byte) error Write(data []byte) (n int, err error) Configure(config UARTConfig) error Buffered() int ReadByte() (byte, error) DTR() bool RTS() bool } var usbDescriptor descriptor.Descriptor func usbVendorID() uint16 { if usb.VendorID != 0 { return usb.VendorID } return usb_VID } func usbProductID() uint16 { if usb.ProductID != 0 { return usb.ProductID } return usb_PID } func usbManufacturer() string { if usb.Manufacturer != "" { return usb.Manufacturer } return usb_STRING_MANUFACTURER } func usbProduct() string { if usb.Product != "" { return usb.Product } return usb_STRING_PRODUCT } func usbSerial() string { if usb.Serial != "" { return usb.Serial } return "" } // strToUTF16LEDescriptor converts a utf8 string into a string descriptor // note: the following code only converts ascii characters to UTF16LE. In order // to do a "proper" conversion, we would need to pull in the 'unicode/utf16' // package, which at the time this was written added 512 bytes to the compiled // binary. func strToUTF16LEDescriptor(in string, out []byte) { out[0] = byte(len(out)) out[1] = descriptor.TypeString for i, rune := range in { out[(i<<1)+2] = byte(rune) out[(i<<1)+3] = 0 } return } const cdcLineInfoSize = 7 var ( ErrUSBReadTimeout = errors.New("USB read timeout") ErrUSBBytesRead = errors.New("USB invalid number of bytes read") ErrUSBBytesWritten = errors.New("USB invalid number of bytes written") ) var ( usbEndpointDescriptors [NumberOfUSBEndpoints]descriptor.Device isEndpointHalt = false isRemoteWakeUpEnabled = false usbConfiguration uint8 usbSetInterface uint8 ) //go:align 4 var udd_ep_control_cache_buffer [256]uint8 //go:align 4 var udd_ep_in_cache_buffer [NumberOfUSBEndpoints][64]uint8 //go:align 4 var udd_ep_out_cache_buffer [NumberOfUSBEndpoints][64]uint8 // usb_trans_buffer max size is 255 since that is max size // for a descriptor (bLength is 1 byte), and the biggest use // for this buffer is to transmit string descriptors. If // this buffer is used for new purposes in future the length // must be revisited. var usb_trans_buffer [255]uint8 var ( usbTxHandler [NumberOfUSBEndpoints]func() usbRxHandler [NumberOfUSBEndpoints]func([]byte) bool usbSetupHandler [usb.NumberOfInterfaces]func(usb.Setup) bool usbStallHandler [NumberOfUSBEndpoints]func(usb.Setup) bool ) // sendDescriptor creates and sends the various USB descriptor types that // can be requested by the host. func sendDescriptor(setup usb.Setup) { switch setup.WValueH { case descriptor.TypeConfiguration: sendUSBPacket(0, usbDescriptor.Configuration, setup.WLength) return case descriptor.TypeDevice: usbDescriptor.Configure(usbVendorID(), usbProductID()) sendUSBPacket(0, usbDescriptor.Device, setup.WLength) return case descriptor.TypeString: switch setup.WValueL { case 0: usb_trans_buffer[0] = 0x04 usb_trans_buffer[1] = 0x03 usb_trans_buffer[2] = 0x09 usb_trans_buffer[3] = 0x04 sendUSBPacket(0, usb_trans_buffer[:4], setup.WLength) case usb.IPRODUCT: b := usb_trans_buffer[:(len(usbProduct())<<1)+2] strToUTF16LEDescriptor(usbProduct(), b) sendUSBPacket(0, b, setup.WLength) case usb.IMANUFACTURER: b := usb_trans_buffer[:(len(usbManufacturer())<<1)+2] strToUTF16LEDescriptor(usbManufacturer(), b) sendUSBPacket(0, b, setup.WLength) case usb.ISERIAL: sz := len(usbSerial()) if sz == 0 { SendZlp() } else { b := usb_trans_buffer[:(sz<<1)+2] strToUTF16LEDescriptor(usbSerial(), b) sendUSBPacket(0, b, setup.WLength) } } return case descriptor.TypeHIDReport: if h, ok := usbDescriptor.HID[setup.WIndex]; ok { sendUSBPacket(0, h, setup.WLength) return } case descriptor.TypeDeviceQualifier: // skip default: } // do not know how to handle this message, so return zero SendZlp() return } func handleStandardSetup(setup usb.Setup) bool { switch setup.BRequest { case usb.GET_STATUS: usb_trans_buffer[0] = 0 usb_trans_buffer[1] = 0 if setup.BmRequestType != 0 { // endpoint if isEndpointHalt { usb_trans_buffer[0] = 1 } } sendUSBPacket(0, usb_trans_buffer[:2], setup.WLength) return true case usb.CLEAR_FEATURE: if setup.WValueL == 1 { // DEVICEREMOTEWAKEUP isRemoteWakeUpEnabled = false } else if setup.WValueL == 0 { // ENDPOINTHALT if idx := setup.WIndex & 0x7F; idx < NumberOfUSBEndpoints && usbStallHandler[idx] != nil { // Host has requested to clear an endpoint stall. If the request is addressed to // an endpoint with a configured StallHandler, forward the message on. // The 0x7F mask is used to clear the direction bit from the endpoint number return usbStallHandler[idx](setup) } isEndpointHalt = false } SendZlp() return true case usb.SET_FEATURE: if setup.WValueL == 1 { // DEVICEREMOTEWAKEUP isRemoteWakeUpEnabled = true } else if setup.WValueL == 0 { // ENDPOINTHALT if idx := setup.WIndex & 0x7F; idx < NumberOfUSBEndpoints && usbStallHandler[idx] != nil { // Host has requested to stall an endpoint. If the request is addressed to // an endpoint with a configured StallHandler, forward the message on. // The 0x7F mask is used to clear the direction bit from the endpoint number return usbStallHandler[idx](setup) } isEndpointHalt = true } SendZlp() return true case usb.SET_ADDRESS: return handleUSBSetAddress(setup) case usb.GET_DESCRIPTOR: sendDescriptor(setup) return true case usb.SET_DESCRIPTOR: return false case usb.GET_CONFIGURATION: usb_trans_buffer[0] = usbConfiguration sendUSBPacket(0, usb_trans_buffer[:1], setup.WLength) return true case usb.SET_CONFIGURATION: if setup.BmRequestType&usb.REQUEST_RECIPIENT == usb.REQUEST_DEVICE { for i := 1; i < len(endPoints); i++ { initEndpoint(uint32(i), endPoints[i]) } usbConfiguration = setup.WValueL USBDev.InitEndpointComplete = true SendZlp() return true } else { return false } case usb.GET_INTERFACE: usb_trans_buffer[0] = usbSetInterface sendUSBPacket(0, usb_trans_buffer[:1], setup.WLength) return true case usb.SET_INTERFACE: usbSetInterface = setup.WValueL SendZlp() return true default: return true } } func EnableCDC(txHandler func(), rxHandler func([]byte), setupHandler func(usb.Setup) bool) { if len(usbDescriptor.Device) == 0 { usbDescriptor = descriptor.CDC } // Initialization of endpoints is required even for non-CDC ConfigureUSBEndpoint(usbDescriptor, []usb.EndpointConfig{ { Index: usb.CDC_ENDPOINT_ACM, IsIn: true, Type: usb.ENDPOINT_TYPE_INTERRUPT, }, { Index: usb.CDC_ENDPOINT_OUT, IsIn: false, Type: usb.ENDPOINT_TYPE_BULK, RxHandler: rxHandler, }, { Index: usb.CDC_ENDPOINT_IN, IsIn: true, Type: usb.ENDPOINT_TYPE_BULK, TxHandler: txHandler, }, }, []usb.SetupConfig{ { Index: usb.CDC_ACM_INTERFACE, Handler: setupHandler, }, }) } func ConfigureUSBEndpoint(desc descriptor.Descriptor, epSettings []usb.EndpointConfig, setup []usb.SetupConfig) { usbDescriptor = desc for _, ep := range epSettings { if ep.IsIn { endPoints[ep.Index] = uint32(ep.Type | usb.EndpointIn) if ep.TxHandler != nil { usbTxHandler[ep.Index] = ep.TxHandler } } else { endPoints[ep.Index] = uint32(ep.Type | usb.EndpointOut) if ep.RxHandler != nil { usbRxHandler[ep.Index] = func(b []byte) bool { ep.RxHandler(b) return true } } else if ep.DelayRxHandler != nil { usbRxHandler[ep.Index] = ep.DelayRxHandler } } if ep.StallHandler != nil { usbStallHandler[ep.Index] = ep.StallHandler } } for _, s := range setup { usbSetupHandler[s.Index] = s.Handler } } ================================================ FILE: src/machine/virt.go ================================================ //go:build tinygo.riscv32 && virt // Machine implementation for VirtIO targets. // At the moment only QEMU RISC-V is supported, but support for ARM for example // should not be difficult to add with a change to virtioFindDevice. package machine import ( "errors" "runtime/volatile" "sync" "unsafe" ) const deviceName = "riscv-qemu" func (p Pin) Set(high bool) { // no pins defined } var rngLock sync.Mutex var rngDevice *virtioDevice1 var rngBuf volatile.Register32 var errNoRNG = errors.New("machine: no entropy source found") var errNoRNGData = errors.New("machine: entropy source didn't return enough data") // GetRNG returns random numbers from a VirtIO entropy source. // When running in QEMU, it requires adding the RNG device: // // -device virtio-rng-device func GetRNG() (uint32, error) { rngLock.Lock() // Initialize the device on first use. if rngDevice == nil { // Search for an available RNG. rngDevice = virtioFindDevice(virtioDeviceEntropySource) if rngDevice == nil { rngLock.Unlock() return 0, errNoRNG } // Initialize the device. rngDevice.status.Set(0) // reset device rngDevice.status.Set(virtioDeviceStatusAcknowledge) rngDevice.status.Set(virtioDeviceStatusAcknowledge | virtioDeviceStatusDriver) rngDevice.hostFeaturesSel.Set(0) rngDevice.status.Set(virtioDeviceStatusAcknowledge | virtioDeviceStatusDriver | virtioDeviceStatusDriverOk) rngDevice.guestPageSize.Set(4096) // Configure queue, according to section 4.2.4 "Legacy interface". // Note: we're skipping checks for queuePFM and queueNumMax. rngDevice.queueSel.Set(0) // use queue 0 (the only queue) rngDevice.queueNum.Set(1) // use a single buffer in the queue rngDevice.queueAlign.Set(4096) // default alignment appears to be 4096 rngDevice.queuePFN.Set(uint32(uintptr(unsafe.Pointer(&rngQueue))) / 4096) // Configure the only buffer in the queue (but don't increment // rngQueue.available yet). rngQueue.buffers[0].address = uint64(uintptr(unsafe.Pointer(&rngBuf))) rngQueue.buffers[0].length = uint32(unsafe.Sizeof(rngBuf)) rngQueue.buffers[0].flags = 2 // 2 means write-only buffer } // Increment the available ring buffer. This doesn't actually change the // buffer index (it's a ring with a single entry), but the number needs to // be incremented otherwise the device won't recognize a new buffer. index := rngQueue.available.index rngQueue.available.index = index + 1 rngDevice.queueNotify.Set(0) // notify the device of the 'new' (reused) buffer for rngQueue.used.index.Get() != index+1 { // Busy wait until the RNG buffer is filled. // A better way would be to wait for an interrupt, but since this driver // implementation is mostly used for testing it's good enough for now. } // Check that we indeed got 4 bytes back. if rngQueue.used.ring[0].length != 4 { rngLock.Unlock() return 0, errNoRNGData } // Read the resulting random numbers. result := rngBuf.Get() rngLock.Unlock() return result, nil } // Implement a driver for the VirtIO entropy device. // https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html // http://wiki.osdev.org/Virtio // http://www.dumais.io/index.php?article=aca38a9a2b065b24dfa1dee728062a12 const ( virtioDeviceStatusAcknowledge = 1 virtioDeviceStatusDriver = 2 virtioDeviceStatusDriverOk = 4 virtioDeviceStatusFeaturesOk = 8 virtioDeviceStatusFailed = 128 ) const ( virtioDeviceReserved = iota virtioDeviceNetworkCard virtioDeviceBlockDevice virtioDeviceConsole virtioDeviceEntropySource // there are more device types ) // VirtIO device version 1 type virtioDevice1 struct { magic volatile.Register32 // always 0x74726976 version volatile.Register32 deviceID volatile.Register32 vendorID volatile.Register32 hostFeatures volatile.Register32 hostFeaturesSel volatile.Register32 _ [2]uint32 guestFeatures volatile.Register32 guestFeaturesSel volatile.Register32 guestPageSize volatile.Register32 _ uint32 queueSel volatile.Register32 queueNumMax volatile.Register32 queueNum volatile.Register32 queueAlign volatile.Register32 queuePFN volatile.Register32 _ [3]uint32 queueNotify volatile.Register32 _ [3]uint32 interruptStatus volatile.Register32 interruptAck volatile.Register32 _ [2]uint32 status volatile.Register32 } // VirtIO queue, with a single buffer. type virtioQueue struct { buffers [1]struct { address uint64 length uint32 flags uint16 next uint16 } // 16 bytes available struct { flags uint16 index uint16 ring [1]uint16 eventIndex uint16 } // 8 bytes _ [4096 - 16*1 - 8*1]byte // padding (to align on a 4096 byte boundary) used struct { flags uint16 index volatile.Register16 ring [1]struct { index uint32 length uint32 } availEvent uint16 } } func virtioFindDevice(deviceID uint32) *virtioDevice1 { // On RISC-V, QEMU defines 8 VirtIO devices starting at 0x10001000 and // repeating every 0x1000 bytes. // The memory map can be seen in the QEMU source code: // https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c for i := 0; i < 8; i++ { dev := (*virtioDevice1)(unsafe.Pointer(uintptr(0x10001000 + i*0x1000))) if dev.magic.Get() != 0x74726976 || dev.version.Get() != 1 || dev.deviceID.Get() != deviceID { continue } return dev } return nil } // A VirtIO queue needs to be page-aligned. // //go:align 4096 var rngQueue virtioQueue ================================================ FILE: src/machine/watchdog.go ================================================ //go:build nrf52840 || nrf52833 || rp2040 || rp2350 || atsamd21 || atsamd51 || atsame5x || stm32 package machine // WatchdogConfig holds configuration for the watchdog timer. type WatchdogConfig struct { // The timeout (in milliseconds) before the watchdog fires. // // If the requested timeout exceeds `MaxTimeout` it will be rounded // down. TimeoutMillis uint32 } // watchdog must be implemented by any platform supporting watchdog functionality type watchdog interface { // Configure the watchdog. // // This method should not be called after the watchdog is started and on // some platforms attempting to reconfigure after starting the watchdog // is explicitly forbidden / will not work. Configure(config WatchdogConfig) error // Starts the watchdog. Start() error // Update the watchdog, indicating that the app is healthy. Update() } // Ensure required public symbols var exists and meets interface spec var _ = watchdog(Watchdog) // Ensure required public constants exist const _ = WatchdogMaxTimeout ================================================ FILE: src/os/deadline_test.go ================================================ //go:build posix && !baremetal && !js package os_test import ( "errors" . "os" "testing" ) func TestDeadlines(t *testing.T) { // Create a handle to a known-good, existing file f, err := Open("/dev/null") if err != nil { t.Fatal(err) } if err := f.SetDeadline(0); err == nil { if err != nil { t.Errorf("wanted nil, got %v", err) } } if err := f.SetDeadline(1); err == nil { if !errors.Is(err, ErrNotImplemented) { t.Errorf("wanted ErrNotImplemented, got %v", err) } } if err := f.SetReadDeadline(1); err == nil { if !errors.Is(err, ErrNotImplemented) { t.Errorf("wanted ErrNotImplemented, got %v", err) } } if err := f.SetWriteDeadline(1); err == nil { if !errors.Is(err, ErrNotImplemented) { t.Errorf("wanted ErrNotImplemented, got %v", err) } } // Closed files must return an error f.Close() if err := f.SetDeadline(0); err == nil { if err != ErrClosed { t.Errorf("wanted ErrClosed, got %v", err) } } } ================================================ FILE: src/os/dir.go ================================================ // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os import ( "io/fs" "sort" ) type readdirMode int const ( readdirName readdirMode = iota readdirDirEntry readdirFileInfo ) // Readdir reads the contents of the directory associated with file and // returns a slice of up to n FileInfo values, as would be returned // by Lstat, in directory order. Subsequent calls on the same file will yield // further FileInfos. // // If n > 0, Readdir returns at most n FileInfo structures. In this case, if // Readdir returns an empty slice, it will return a non-nil error // explaining why. At the end of a directory, the error is io.EOF. // // If n <= 0, Readdir returns all the FileInfo from the directory in // a single slice. In this case, if Readdir succeeds (reads all // the way to the end of the directory), it returns the slice and a // nil error. If it encounters an error before the end of the // directory, Readdir returns the FileInfo read until that point // and a non-nil error. // // Most clients are better served by the more efficient ReadDir method. func (f *File) Readdir(n int) ([]FileInfo, error) { if f == nil { return nil, ErrInvalid } _, _, infos, err := f.readdir(n, readdirFileInfo) if infos == nil { // Readdir has historically always returned a non-nil empty slice, never nil, // even on error (except misuse with nil receiver above). // Keep it that way to avoid breaking overly sensitive callers. infos = []FileInfo{} } return infos, err } // Readdirnames reads the contents of the directory associated with file // and returns a slice of up to n names of files in the directory, // in directory order. Subsequent calls on the same file will yield // further names. // // If n > 0, Readdirnames returns at most n names. In this case, if // Readdirnames returns an empty slice, it will return a non-nil error // explaining why. At the end of a directory, the error is io.EOF. // // If n <= 0, Readdirnames returns all the names from the directory in // a single slice. In this case, if Readdirnames succeeds (reads all // the way to the end of the directory), it returns the slice and a // nil error. If it encounters an error before the end of the // directory, Readdirnames returns the names read until that point and // a non-nil error. func (f *File) Readdirnames(n int) (names []string, err error) { if f == nil { return nil, ErrInvalid } names, _, _, err = f.readdir(n, readdirName) if names == nil { // Readdirnames has historically always returned a non-nil empty slice, never nil, // even on error (except misuse with nil receiver above). // Keep it that way to avoid breaking overly sensitive callers. names = []string{} } return names, err } // A DirEntry is an entry read from a directory // (using the ReadDir function or a File's ReadDir method). type DirEntry = fs.DirEntry // ReadDir reads the contents of the directory associated with the file f // and returns a slice of DirEntry values in directory order. // Subsequent calls on the same file will yield later DirEntry records in the directory. // // If n > 0, ReadDir returns at most n DirEntry records. // In this case, if ReadDir returns an empty slice, it will return an error explaining why. // At the end of a directory, the error is io.EOF. // // If n <= 0, ReadDir returns all the DirEntry records remaining in the directory. // When it succeeds, it returns a nil error (not io.EOF). func (f *File) ReadDir(n int) ([]DirEntry, error) { if f == nil { return nil, ErrInvalid } _, dirents, _, err := f.readdir(n, readdirDirEntry) if dirents == nil { // Match Readdir and Readdirnames: don't return nil slices. dirents = []DirEntry{} } return dirents, err } // testingForceReadDirLstat forces ReadDir to call Lstat, for testing that code path. // This can be difficult to provoke on some Unix systems otherwise. var testingForceReadDirLstat bool // ReadDir reads the named directory, // returning all its directory entries sorted by filename. // If an error occurs reading the directory, // ReadDir returns the entries it was able to read before the error, // along with the error. func ReadDir(name string) ([]DirEntry, error) { f, err := Open(name) if err != nil { return nil, err } defer f.Close() dirs, err := f.ReadDir(-1) sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() }) return dirs, err } ================================================ FILE: src/os/dir_darwin.go ================================================ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os import ( "io" "runtime" "syscall" "unsafe" ) // Auxiliary information if the File describes a directory type dirInfo struct { dir uintptr // Pointer to DIR structure from dirent.h } func (d *dirInfo) close() { if d.dir == 0 { return } closedir(d.dir) d.dir = 0 } func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) { if f.dirinfo == nil { dir, call, errno := darwinOpenDir(syscallFd(f.handle.(unixFileHandle))) if errno != nil { return nil, nil, nil, &PathError{Op: call, Path: f.name, Err: errno} } f.dirinfo = &dirInfo{ dir: dir, } } d := f.dirinfo size := n if size <= 0 { size = 100 n = -1 } var dirent syscall.Dirent var entptr *syscall.Dirent for len(names)+len(dirents)+len(infos) < size || n == -1 { if errno := readdir_r(d.dir, &dirent, &entptr); errno != 0 { if errno == syscall.EINTR { continue } return names, dirents, infos, &PathError{Op: "readdir", Path: f.name, Err: errno} } if entptr == nil { // EOF break } if dirent.Ino == 0 { continue } name := (*[len(syscall.Dirent{}.Name)]byte)(unsafe.Pointer(&dirent.Name))[:] for i, c := range name { if c == 0 { name = name[:i] break } } // Check for useless names before allocating a string. if string(name) == "." || string(name) == ".." { continue } if mode == readdirName { names = append(names, string(name)) } else if mode == readdirDirEntry { de, err := newUnixDirent(f.name, string(name), dtToType(dirent.Type)) if IsNotExist(err) { // File disappeared between readdir and stat. // Treat as if it didn't exist. continue } if err != nil { return nil, dirents, nil, err } dirents = append(dirents, de) } else { info, err := lstat(f.name + "/" + string(name)) if IsNotExist(err) { // File disappeared between readdir + stat. // Treat as if it didn't exist. continue } if err != nil { return nil, nil, infos, err } infos = append(infos, info) } runtime.KeepAlive(f) } if n > 0 && len(names)+len(dirents)+len(infos) == 0 { return nil, nil, nil, io.EOF } return names, dirents, infos, nil } func dtToType(typ uint8) FileMode { switch typ { case syscall.DT_BLK: return ModeDevice case syscall.DT_CHR: return ModeDevice | ModeCharDevice case syscall.DT_DIR: return ModeDir case syscall.DT_FIFO: return ModeNamedPipe case syscall.DT_LNK: return ModeSymlink case syscall.DT_REG: return 0 case syscall.DT_SOCK: return ModeSocket } return ^FileMode(0) } // darwinOpenDir returns a pointer to a DIR structure suitable for // ReadDir. In case of an error, the name of the failed // syscall is returned along with a syscall.Errno. // Borrowed from upstream's internal/poll/fd_opendir_darwin.go func darwinOpenDir(fd syscallFd) (uintptr, string, error) { // fdopendir(3) takes control of the file descriptor, // so use a dup. fd2, err := syscall.Dup(fd) if err != nil { return 0, "dup", err } var dir uintptr for { dir, err = fdopendir(fd2) if err != syscall.EINTR { break } } if err != nil { syscall.Close(fd2) return 0, "fdopendir", err } return dir, "", nil } // Implemented in syscall/syscall_libc_darwin_*.go. //go:linkname fdopendir syscall.fdopendir func fdopendir(fd int) (dir uintptr, err error) //go:linkname closedir syscall.closedir func closedir(dir uintptr) (err error) //go:linkname readdir_r syscall.readdir_r func readdir_r(dir uintptr, entry *syscall.Dirent, result **syscall.Dirent) (res syscall.Errno) ================================================ FILE: src/os/dir_other.go ================================================ //go:build baremetal || js || windows || wasm_unknown || nintendoswitch // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os import ( "syscall" ) type dirInfo struct { } func (*dirInfo) close() { } func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) { return nil, nil, nil, &PathError{Op: "readdir unimplemented", Err: syscall.ENOTDIR} } ================================================ FILE: src/os/dir_test.go ================================================ //go:build darwin || (linux && !baremetal && !js && !wasip1 && !wasip2 && !386 && !arm) package os_test import ( . "os" "testing" ) func testReaddirnames(dir string, contents []string, t *testing.T) { file, err := Open(dir) if err != nil { t.Fatalf("open %q failed: %v", dir, err) } defer file.Close() s, err2 := file.Readdirnames(-1) if err2 != nil { t.Fatalf("Readdirnames %q failed: %v", dir, err2) } for _, m := range contents { found := false for _, n := range s { if n == "." || n == ".." { t.Errorf("got %q in directory", n) } if !equal(m, n) { continue } if found { t.Error("present twice:", m) } found = true } if !found { t.Error("could not find", m) } } if s == nil { t.Error("Readdirnames returned nil instead of empty slice") } } func testReaddir(dir string, contents []string, t *testing.T) { file, err := Open(dir) if err != nil { t.Fatalf("open %q failed: %v", dir, err) } defer file.Close() s, err2 := file.Readdir(-1) if err2 != nil { t.Fatalf("Readdir %q failed: %v", dir, err2) } for _, m := range contents { found := false for _, n := range s { if n.Name() == "." || n.Name() == ".." { t.Errorf("got %q in directory", n.Name()) } if !equal(m, n.Name()) { continue } if found { t.Error("present twice:", m) } found = true } if !found { t.Error("could not find", m) } } if s == nil { t.Error("Readdir returned nil instead of empty slice") } } func testReadDir(dir string, contents []string, t *testing.T) { file, err := Open(dir) if err != nil { t.Fatalf("open %q failed: %v", dir, err) } defer file.Close() s, err2 := file.ReadDir(-1) if err2 != nil { t.Fatalf("ReadDir %q failed: %v", dir, err2) } for _, m := range contents { found := false for _, n := range s { if n.Name() == "." || n.Name() == ".." { t.Errorf("got %q in directory", n) } if !equal(m, n.Name()) { continue } if found { t.Error("present twice:", m) } found = true lstat, err := Lstat(dir + "/" + m) if err != nil { t.Fatal(err) } if n.IsDir() != lstat.IsDir() { t.Errorf("%s: IsDir=%v, want %v", m, n.IsDir(), lstat.IsDir()) } if n.Type() != lstat.Mode().Type() { t.Errorf("%s: IsDir=%v, want %v", m, n.Type(), lstat.Mode().Type()) } info, err := n.Info() if err != nil { t.Errorf("%s: Info: %v", m, err) continue } if !SameFile(info, lstat) { t.Errorf("%s: Info: SameFile(info, lstat) = false", m) } } if !found { t.Error("could not find", m) } } if s == nil { t.Error("ReadDir returned nil instead of empty slice") } } func TestFileReaddirnames(t *testing.T) { testReaddirnames(".", dot, t) testReaddirnames(TempDir(), nil, t) } func TestFileReaddir(t *testing.T) { testReaddir(".", dot, t) testReaddir(TempDir(), nil, t) } func TestFileReadDir(t *testing.T) { testReadDir(".", dot, t) testReadDir(TempDir(), nil, t) } func TestReadDir(t *testing.T) { dirname := "rumpelstilzchen" _, err := ReadDir(dirname) if err == nil { t.Fatalf("ReadDir %s: error expected, none found", dirname) } dirname = "testdata" list, err := ReadDir(dirname) if err != nil { t.Fatalf("ReadDir %s: %v", dirname, err) } foundFile := false foundSubDir := false for _, dir := range list { switch { case !dir.IsDir() && dir.Name() == "hello": foundFile = true case dir.IsDir() && dir.Name() == "issue37161": foundSubDir = true } } if !foundFile { t.Fatalf("ReadDir %s: hello file not found", dirname) } if !foundSubDir { t.Fatalf("ReadDir %s: testdata directory not found", dirname) } } // TestReadNonDir just verifies that opening a non-directory returns an error. func TestReadNonDir(t *testing.T) { // Use filename of this source file; it is known to exist, and go tests run in source tree. dirname := "dir_test.go" _, err := ReadDir(dirname) if err == nil { t.Fatalf("ReadDir %s: error on non-dir expected, none found", dirname) } } ================================================ FILE: src/os/dir_unix.go ================================================ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build linux && !baremetal && !wasip1 && !wasip2 && !wasm_unknown && !nintendoswitch package os import ( "io" "syscall" "unsafe" ) // Auxiliary information if the File describes a directory type dirInfo struct { nbuf int // length of buf; return value from Getdirentries bufp int // location of next record in buf. buf [blockSize]byte // buffer for directory I/O } const ( // More than 5760 to work around https://golang.org/issue/24015. blockSize = 8192 - 2*unsafe.Sizeof(int(0)) ) func (d *dirInfo) close() { } func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) { // If this file has no dirinfo, create one. if f.dirinfo == nil { f.dirinfo = new(dirInfo) } d := f.dirinfo // Change the meaning of n for the implementation below. // // The n above was for the public interface of "if n <= 0, // Readdir returns all the FileInfo from the directory in a // single slice". // // But below, we use only negative to mean looping until the // end and positive to mean bounded, with positive // terminating at 0. if n == 0 { n = -1 } for n != 0 { // Refill the buffer if necessary if d.bufp >= d.nbuf { d.bufp = 0 var errno error d.nbuf, errno = syscall.ReadDirent(syscallFd(f.handle.(unixFileHandle)), d.buf[:]) if d.nbuf < 0 { errno = handleSyscallError(errno) } if errno != nil { return names, dirents, infos, &PathError{Op: "readdirent", Path: f.name, Err: errno} } if d.nbuf <= 0 { break // EOF } } // Drain the buffer buf := d.buf[d.bufp:d.nbuf] reclen, ok := direntReclen(buf) if !ok || reclen > uint64(len(buf)) { break } rec := buf[:reclen] d.bufp += int(reclen) ino, ok := direntIno(rec) if !ok { break } if ino == 0 { continue } const namoff = uint64(unsafe.Offsetof(syscall.Dirent{}.Name)) namlen, ok := direntNamlen(rec) if !ok || namoff+namlen > uint64(len(rec)) { break } name := rec[namoff : namoff+namlen] for i, c := range name { if c == 0 { name = name[:i] break } } // Check for useless names before allocating a string. if string(name) == "." || string(name) == ".." { continue } if n > 0 { // see 'n == 0' comment above n-- } if mode == readdirName { names = append(names, string(name)) } else if mode == readdirDirEntry { de, err := newUnixDirent(f.name, string(name), direntType(rec)) if IsNotExist(err) { // File disappeared between readdir and stat. // Treat as if it didn't exist. continue } if err != nil { return nil, dirents, nil, err } dirents = append(dirents, de) } else { info, err := lstat(f.name + "/" + string(name)) if IsNotExist(err) { // File disappeared between readdir + stat. // Treat as if it didn't exist. continue } if err != nil { return nil, nil, infos, err } infos = append(infos, info) } } if n > 0 && len(names)+len(dirents)+len(infos) == 0 { return nil, nil, nil, io.EOF } return names, dirents, infos, nil } // readInt returns the size-bytes unsigned integer in native byte order at offset off. func readInt(b []byte, off, size uintptr) (u uint64, ok bool) { if len(b) < int(off+size) { return 0, false } if isBigEndian { return readIntBE(b[off:], size), true } return readIntLE(b[off:], size), true } func readIntBE(b []byte, size uintptr) uint64 { switch size { case 1: return uint64(b[0]) case 2: _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808 return uint64(b[1]) | uint64(b[0])<<8 case 4: _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808 return uint64(b[3]) | uint64(b[2])<<8 | uint64(b[1])<<16 | uint64(b[0])<<24 case 8: _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56 default: panic("syscall: readInt with unsupported size") } } func readIntLE(b []byte, size uintptr) uint64 { switch size { case 1: return uint64(b[0]) case 2: _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808 return uint64(b[0]) | uint64(b[1])<<8 case 4: _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808 return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 case 8: _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 default: panic("syscall: readInt with unsupported size") } } ================================================ FILE: src/os/dir_wasi.go ================================================ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // This file was derived from src/os/dir_darwin.go since the logic for WASI is // fairly similar: we use fdopendir, fdclosedir, and readdir from wasi-libc in // a similar way that the darwin code uses functions from libc. //go:build wasip1 || wasip2 package os import ( "io" "runtime" "syscall" "unsafe" ) // opaque DIR* returned by fdopendir // // We add an unused field so it is not the empty struct, which is usually // a special case in Go. type dirInfo struct{ _ int32 } func (d *dirInfo) close() { syscall.Fdclosedir(uintptr(unsafe.Pointer(d))) } func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) { if f.dirinfo == nil { dir, errno := syscall.Fdopendir(syscallFd(f.handle.(unixFileHandle))) if errno != nil { return nil, nil, nil, &PathError{Op: "fdopendir", Path: f.name, Err: errno} } f.dirinfo = (*dirInfo)(unsafe.Pointer(dir)) } d := uintptr(unsafe.Pointer(f.dirinfo)) // see src/os/dir_unix.go if n == 0 { n = -1 } for n != 0 { dirent, errno := syscall.Readdir(d) if errno != nil { if errno == syscall.EINTR { continue } return names, dirents, infos, &PathError{Op: "readdir", Path: f.name, Err: errno} } if dirent == nil { // EOF break } name := dirent.Name() // Check for useless names before allocating a string. if string(name) == "." || string(name) == ".." { continue } if n > 0 { n-- } if mode == readdirName { names = append(names, string(name)) } else if mode == readdirDirEntry { de, err := newUnixDirent(f.name, string(name), dtToType(dirent.Type)) if IsNotExist(err) { // File disappeared between readdir and stat. // Treat as if it didn't exist. continue } if err != nil { return nil, dirents, nil, err } dirents = append(dirents, de) } else { info, err := lstat(f.name + "/" + string(name)) if IsNotExist(err) { // File disappeared between readdir + stat. // Treat as if it didn't exist. continue } if err != nil { return nil, nil, infos, err } infos = append(infos, info) } runtime.KeepAlive(f) } if n > 0 && len(names)+len(dirents)+len(infos) == 0 { return nil, nil, nil, io.EOF } return names, dirents, infos, nil } func dtToType(typ uint8) FileMode { switch typ { case syscall.DT_BLK: return ModeDevice case syscall.DT_CHR: return ModeDevice | ModeCharDevice case syscall.DT_DIR: return ModeDir case syscall.DT_FIFO: return ModeNamedPipe case syscall.DT_LNK: return ModeSymlink case syscall.DT_REG: return 0 } return ^FileMode(0) } ================================================ FILE: src/os/dirent_linux.go ================================================ //go:build !baremetal && !js && !wasip1 && !wasip2 && !wasm_unknown && !nintendoswitch // Copyright 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os import ( "syscall" "unsafe" ) func direntIno(buf []byte) (uint64, bool) { return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Ino), unsafe.Sizeof(syscall.Dirent{}.Ino)) } func direntReclen(buf []byte) (uint64, bool) { return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Reclen), unsafe.Sizeof(syscall.Dirent{}.Reclen)) } func direntNamlen(buf []byte) (uint64, bool) { reclen, ok := direntReclen(buf) if !ok { return 0, false } return reclen - uint64(unsafe.Offsetof(syscall.Dirent{}.Name)), true } func direntType(buf []byte) FileMode { off := unsafe.Offsetof(syscall.Dirent{}.Type) if off >= uintptr(len(buf)) { return ^FileMode(0) // unknown } typ := buf[off] switch typ { case syscall.DT_BLK: return ModeDevice case syscall.DT_CHR: return ModeDevice | ModeCharDevice case syscall.DT_DIR: return ModeDir case syscall.DT_FIFO: return ModeNamedPipe case syscall.DT_LNK: return ModeSymlink case syscall.DT_REG: return 0 case syscall.DT_SOCK: return ModeSocket } return ^FileMode(0) // unknown } ================================================ FILE: src/os/endian_little.go ================================================ // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // TODO: exclude big endian targets here and include them in endian_big.go, once tinygo supports one package os const isBigEndian = false ================================================ FILE: src/os/env.go ================================================ // Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // General environment variables. package os import ( "internal/testlog" "syscall" ) // Expand replaces ${var} or $var in the string based on the mapping function. // For example, os.ExpandEnv(s) is equivalent to os.Expand(s, os.Getenv). func Expand(s string, mapping func(string) string) string { var buf []byte // ${} is all ASCII, so bytes are fine for this operation. i := 0 for j := 0; j < len(s); j++ { if s[j] == '$' && j+1 < len(s) { if buf == nil { buf = make([]byte, 0, 2*len(s)) } buf = append(buf, s[i:j]...) name, w := getShellName(s[j+1:]) if name == "" && w > 0 { // Encountered invalid syntax; eat the // characters. } else if name == "" { // Valid syntax, but $ was not followed by a // name. Leave the dollar character untouched. buf = append(buf, s[j]) } else { buf = append(buf, mapping(name)...) } j += w i = j + 1 } } if buf == nil { return s } return string(buf) + s[i:] } // ExpandEnv replaces ${var} or $var in the string according to the values // of the current environment variables. References to undefined // variables are replaced by the empty string. func ExpandEnv(s string) string { return Expand(s, Getenv) } // isShellSpecialVar reports whether the character identifies a special // shell variable such as $*. func isShellSpecialVar(c uint8) bool { switch c { case '*', '#', '$', '@', '!', '?', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': return true } return false } // isAlphaNum reports whether the byte is an ASCII letter, number, or underscore func isAlphaNum(c uint8) bool { return c == '_' || '0' <= c && c <= '9' || 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' } // getShellName returns the name that begins the string and the number of bytes // consumed to extract it. If the name is enclosed in {}, it's part of a ${} // expansion and two more bytes are needed than the length of the name. func getShellName(s string) (string, int) { switch { case s[0] == '{': if len(s) > 2 && isShellSpecialVar(s[1]) && s[2] == '}' { return s[1:2], 3 } // Scan to closing brace for i := 1; i < len(s); i++ { if s[i] == '}' { if i == 1 { return "", 2 // Bad syntax; eat "${}" } return s[1:i], i + 1 } } return "", 1 // Bad syntax; eat "${" case isShellSpecialVar(s[0]): return s[0:1], 1 } // Scan alphanumerics. var i int for i = 0; i < len(s) && isAlphaNum(s[i]); i++ { } return s[:i], i } // Getenv retrieves the value of the environment variable named by the key. // It returns the value, which will be empty if the variable is not present. // To distinguish between an empty value and an unset value, use LookupEnv. func Getenv(key string) string { testlog.Getenv(key) v, _ := syscall.Getenv(key) return v } // LookupEnv retrieves the value of the environment variable named // by the key. If the variable is present in the environment the // value (which may be empty) is returned and the boolean is true. // Otherwise the returned value will be empty and the boolean will // be false. func LookupEnv(key string) (string, bool) { testlog.Getenv(key) return syscall.Getenv(key) } // Setenv sets the value of the environment variable named by the key. // It returns an error, if any. func Setenv(key, value string) error { err := syscall.Setenv(key, value) if err != nil { return NewSyscallError("setenv", err) } return nil } // Unsetenv unsets a single environment variable. func Unsetenv(key string) error { return syscall.Unsetenv(key) } // Clearenv deletes all environment variables. func Clearenv() { syscall.Clearenv() } // Environ returns a copy of strings representing the environment, // in the form "key=value". func Environ() []string { return syscall.Environ() } ================================================ FILE: src/os/env_test.go ================================================ // Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os_test import ( . "os" "reflect" "strings" "testing" ) // testGetenv gives us a controlled set of variables for testing Expand. func testGetenv(s string) string { switch s { case "*": return "all the args" case "#": return "NARGS" case "$": return "PID" case "1": return "ARGUMENT1" case "HOME": return "/usr/gopher" case "H": return "(Value of H)" case "home_1": return "/usr/foo" case "_": return "underscore" } return "" } var expandTests = []struct { in, out string }{ {"", ""}, {"$*", "all the args"}, {"$$", "PID"}, {"${*}", "all the args"}, {"$1", "ARGUMENT1"}, {"${1}", "ARGUMENT1"}, {"now is the time", "now is the time"}, {"$HOME", "/usr/gopher"}, {"$home_1", "/usr/foo"}, {"${HOME}", "/usr/gopher"}, {"${H}OME", "(Value of H)OME"}, {"A$$$#$1$H$home_1*B", "APIDNARGSARGUMENT1(Value of H)/usr/foo*B"}, {"start$+middle$^end$", "start$+middle$^end$"}, {"mixed$|bag$$$", "mixed$|bagPID$"}, {"$", "$"}, {"$}", "$}"}, {"${", ""}, // invalid syntax; eat up the characters {"${}", ""}, // invalid syntax; eat up the characters } func TestExpand(t *testing.T) { for _, test := range expandTests { result := Expand(test.in, testGetenv) if result != test.out { t.Errorf("Expand(%q)=%q; expected %q", test.in, result, test.out) } } } var global interface{} func BenchmarkExpand(b *testing.B) { b.Run("noop", func(b *testing.B) { var s string b.ReportAllocs() for i := 0; i < b.N; i++ { s = Expand("tick tick tick tick", func(string) string { return "" }) } global = s }) b.Run("multiple", func(b *testing.B) { var s string b.ReportAllocs() for i := 0; i < b.N; i++ { s = Expand("$a $a $a $a", func(string) string { return "boom" }) } global = s }) } func TestConsistentEnviron(t *testing.T) { e0 := Environ() for i := 0; i < 10; i++ { e1 := Environ() if !reflect.DeepEqual(e0, e1) { t.Fatalf("environment changed") } } } func TestUnsetenv(t *testing.T) { const testKey = "GO_TEST_UNSETENV" set := func() bool { prefix := testKey + "=" for _, key := range Environ() { if strings.HasPrefix(key, prefix) { return true } } return false } if err := Setenv(testKey, "1"); err != nil { t.Fatalf("Setenv: %v", err) } if !set() { t.Error("Setenv didn't set TestUnsetenv") } if err := Unsetenv(testKey); err != nil { t.Fatalf("Unsetenv: %v", err) } if set() { t.Fatal("Unsetenv didn't clear TestUnsetenv") } } func TestClearenv(t *testing.T) { const testKey = "GO_TEST_CLEARENV" const testValue = "1" // reset env defer func(origEnv []string) { for _, pair := range origEnv { // Environment variables on Windows can begin with = // https://blogs.msdn.com/b/oldnewthing/archive/2010/05/06/10008132.aspx i := strings.Index(pair[1:], "=") + 1 if err := Setenv(pair[:i], pair[i+1:]); err != nil { t.Errorf("Setenv(%q, %q) failed during reset: %v", pair[:i], pair[i+1:], err) } } }(Environ()) if err := Setenv(testKey, testValue); err != nil { t.Fatalf("Setenv(%q, %q) failed: %v", testKey, testValue, err) } if _, ok := LookupEnv(testKey); !ok { t.Errorf("Setenv(%q, %q) didn't set $%s", testKey, testValue, testKey) } Clearenv() if val, ok := LookupEnv(testKey); ok { t.Errorf("Clearenv() didn't clear $%s, remained with value %q", testKey, val) } } func TestLookupEnv(t *testing.T) { const smallpox = "SMALLPOX" // No one has smallpox. value, ok := LookupEnv(smallpox) // Should not exist. if ok || value != "" { t.Fatalf("%s=%q", smallpox, value) } defer Unsetenv(smallpox) err := Setenv(smallpox, "virus") if err != nil { t.Fatalf("failed to release smallpox virus") } _, ok = LookupEnv(smallpox) if !ok { t.Errorf("smallpox release failed; world remains safe but LookupEnv is broken") } } // On Windows, Environ was observed to report keys with a single leading "=". // Check that they are properly reported by LookupEnv and can be set by SetEnv. // See https://golang.org/issue/49886. func TestEnvironConsistency(t *testing.T) { for _, kv := range Environ() { i := strings.Index(kv, "=") if i == 0 { // We observe in practice keys with a single leading "=" on Windows. // TODO(#49886): Should we consume only the first leading "=" as part // of the key, or parse through arbitrarily many of them until a non-=, // or try each possible key/value boundary until LookupEnv succeeds? i = strings.Index(kv[1:], "=") + 1 } if i < 0 { t.Errorf("Environ entry missing '=': %q", kv) } k := kv[:i] v := kv[i+1:] v2, ok := LookupEnv(k) if ok && v == v2 { //t.Logf("LookupEnv(%q) = %q, %t", k, v2, ok) } else { t.Errorf("Environ contains %q, but LookupEnv(%q) = %q, %t", kv, k, v2, ok) } // Since k=v is already present in the environment, // setting it should be a no-op. if err := Setenv(k, v); err == nil { //t.Logf("Setenv(%q, %q)", k, v) } else { t.Errorf("Environ contains %q, but SetEnv(%q, %q) = %q", kv, k, v, err) } } } ================================================ FILE: src/os/env_unix_test.go ================================================ // Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build darwin || linux || wasip1 || wasip2 package os_test import ( "fmt" . "os" "testing" ) var setenvEinvalTests = []struct { k, v string }{ {"", ""}, // empty key {"k=v", ""}, // '=' in key {"\x00", ""}, // '\x00' in key {"k", "\x00"}, // '\x00' in value } func TestSetenvUnixEinval(t *testing.T) { for _, tt := range setenvEinvalTests { err := Setenv(tt.k, tt.v) if err == nil { t.Errorf(`Setenv(%q, %q) == nil, want error`, tt.k, tt.v) } } } var shellSpecialVarTests = []struct { k, v string }{ {"*", "asterisk"}, {"#", "pound"}, {"$", "dollar"}, {"@", "at"}, {"!", "exclamation mark"}, {"?", "question mark"}, {"-", "dash"}, } func TestExpandEnvShellSpecialVar(t *testing.T) { for _, tt := range shellSpecialVarTests { Setenv(tt.k, tt.v) defer Unsetenv(tt.k) argRaw := fmt.Sprintf("$%s", tt.k) argWithBrace := fmt.Sprintf("${%s}", tt.k) if gotRaw, gotBrace := ExpandEnv(argRaw), ExpandEnv(argWithBrace); gotRaw != gotBrace { t.Errorf("ExpandEnv(%q) = %q, ExpandEnv(%q) = %q; expect them to be equal", argRaw, gotRaw, argWithBrace, gotBrace) } } } ================================================ FILE: src/os/errors.go ================================================ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os import ( "errors" "io/fs" "syscall" ) // Portable analogs of some common system call errors. // // Errors returned from this package may be tested against these errors // with errors.Is. var ( // ErrInvalid indicates an invalid argument. // Methods on File will return this error when the receiver is nil. ErrInvalid = fs.ErrInvalid // "invalid argument" ErrPermission = fs.ErrPermission // "permission denied" ErrExist = fs.ErrExist // "file already exists" ErrNotExist = fs.ErrNotExist // "file does not exist" ErrClosed = fs.ErrClosed // "file already closed" // Note that these are exported for use in the Filesystem interface. ErrUnsupported = errors.New("operation not supported") ErrNotImplemented = errors.New("operation not implemented") ) // The following code is copied from the official implementation. // src/internal/poll/fd.go // ErrNoDeadline is returned when a request is made to set a deadline // on a file type that does not use the poller. var ErrNoDeadline = errors.New("file type does not support deadline") // ErrDeadlineExceeded is returned for an expired deadline. // This is exported by the os package as os.ErrDeadlineExceeded. var ErrDeadlineExceeded error = &DeadlineExceededError{} // DeadlineExceededError is returned for an expired deadline. type DeadlineExceededError struct{} // Implement the net.Error interface. // The string is "i/o timeout" because that is what was returned // by earlier Go versions. Changing it may break programs that // match on error strings. func (e *DeadlineExceededError) Error() string { return "i/o timeout" } func (e *DeadlineExceededError) Timeout() bool { return true } func (e *DeadlineExceededError) Temporary() bool { return true } // The following code is copied from the official implementation. // https://github.com/golang/go/blob/4ce6a8e89668b87dce67e2f55802903d6eb9110a/src/os/error.go#L65-L104 func NewSyscallError(syscall string, err error) error { if err == nil { return nil } return &SyscallError{syscall, err} } // PathError records an error and the operation and file path that caused it. type PathError = fs.PathError // SyscallError records an error from a specific system call. type SyscallError struct { Syscall string Err error } func (e *SyscallError) Error() string { return e.Syscall + ": " + e.Err.Error() } func (e *SyscallError) Unwrap() error { return e.Err } type timeout interface { Timeout() bool } // Timeout reports whether this error represents a timeout. func (e *SyscallError) Timeout() bool { t, ok := e.Err.(timeout) return ok && t.Timeout() } func IsExist(err error) bool { return underlyingErrorIs(err, ErrExist) } func IsNotExist(err error) bool { return underlyingErrorIs(err, ErrNotExist) } func IsPermission(err error) bool { return underlyingErrorIs(err, ErrPermission) } func IsTimeout(err error) bool { terr, ok := underlyingError(err).(timeout) return ok && terr.Timeout() } func underlyingErrorIs(err, target error) bool { // Note that this function is not errors.Is: // underlyingError only unwraps the specific error-wrapping types // that it historically did, not all errors implementing Unwrap(). err = underlyingError(err) if err == target { return true } // To preserve prior behavior, only examine syscall errors. e, ok := err.(syscall.Errno) return ok && e.Is(target) } // underlyingError returns the underlying error for known os error types. func underlyingError(err error) error { switch err := err.(type) { case *PathError: return err.Err case *SyscallError: return err.Err } return err } ================================================ FILE: src/os/exec/exec.go ================================================ package exec import "os" // An ExitError reports an unsuccessful exit by a command. type ExitError struct { *os.ProcessState // Stderr holds a subset of the standard error output from the // Cmd.Output method if standard error was not otherwise being // collected. // // If the error output is long, Stderr may contain only a prefix // and suffix of the output, with the middle replaced with // text about the number of omitted bytes. // // Stderr is provided for debugging, for inclusion in error messages. // Users with other needs should redirect Cmd.Stderr as needed. Stderr []byte } func (e *ExitError) Error() string { return e.ProcessState.String() } ================================================ FILE: src/os/exec.go ================================================ package os import ( "errors" "syscall" ) var ( ErrNotImplementedDir = errors.New("directory setting not implemented") ErrNotImplementedSys = errors.New("sys setting not implemented") ErrNotImplementedFiles = errors.New("files setting not implemented") ) type Signal interface { String() string Signal() // to distinguish from other Stringers } // Getpid returns the process id of the caller, or -1 if unavailable. func Getpid() int { return syscall.Getpid() } // Getppid returns the process id of the caller's parent, or -1 if unavailable. func Getppid() int { return syscall.Getppid() } type ProcAttr struct { Dir string Env []string Files []*File Sys *syscall.SysProcAttr } // ErrProcessDone indicates a Process has finished. var ErrProcessDone = errors.New("os: process already finished") type ProcessState struct { } func (p *ProcessState) String() string { return "" // TODO } func (p *ProcessState) Success() bool { return false // TODO } // Sys returns system-dependent exit information about // the process. Convert it to the appropriate underlying // type, such as syscall.WaitStatus on Unix, to access its contents. func (p *ProcessState) Sys() interface{} { return nil // TODO } func (p *ProcessState) Exited() bool { return false // TODO } // ExitCode returns the exit code of the exited process, or -1 // if the process hasn't exited or was terminated by a signal. func (p *ProcessState) ExitCode() int { return -1 // TODO } type Process struct { Pid int } // StartProcess starts a new process with the program, arguments and attributes specified by name, argv and attr. // Arguments to the process (os.Args) are passed via argv. func StartProcess(name string, argv []string, attr *ProcAttr) (*Process, error) { return startProcess(name, argv, attr) } func (p *Process) Wait() (*ProcessState, error) { if p.Pid == -1 { return nil, syscall.EINVAL } return nil, ErrNotImplemented } func (p *Process) Kill() error { return ErrNotImplemented } func (p *Process) Signal(sig Signal) error { return ErrNotImplemented } func Ignore(sig ...Signal) { // leave all the signals unaltered return } // Release releases any resources associated with the Process p, // rendering it unusable in the future. // Release only needs to be called if Wait is not. func (p *Process) Release() error { return p.release() } // FindProcess looks for a running process by its pid. // Keep compatibility with golang and always succeed and return new proc with pid on Linux. func FindProcess(pid int) (*Process, error) { return findProcess(pid) } ================================================ FILE: src/os/exec_linux.go ================================================ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build linux && !baremetal && !tinygo.wasm && !nintendoswitch package os import ( "errors" "runtime" "syscall" ) // The only signal values guaranteed to be present in the os package on all // systems are os.Interrupt (send the process an interrupt) and os.Kill (force // the process to exit). On Windows, sending os.Interrupt to a process with // os.Process.Signal is not implemented; it will return an error instead of // sending a signal. var ( Interrupt Signal = syscall.SIGINT Kill Signal = syscall.SIGKILL ) // Keep compatible with golang and always succeed and return new proc with pid on Linux. func findProcess(pid int) (*Process, error) { return &Process{Pid: pid}, nil } func (p *Process) release() error { // NOOP for unix. p.Pid = -1 // no need for a finalizer anymore runtime.SetFinalizer(p, nil) return nil } // This function is a wrapper around the forkExec function, which is a wrapper around the fork and execve system calls. // The StartProcess function creates a new process by forking the current process and then calling execve to replace the current process with the new process. // It thereby replaces the newly created process with the specified command and arguments. // Differences to upstream golang implementation (https://cs.opensource.google/go/go/+/master:src/syscall/exec_unix.go;l=143): // * No setting of Process Attributes // * Ignoring Ctty // * No ForkLocking (might be introduced by #4273) // * No parent-child communication via pipes (TODO) // * No waiting for crashes child processes to prohibit zombie process accumulation / Wait status checking (TODO) func forkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error) { if argv == nil { return 0, errors.New("exec: no argv") } if len(argv) == 0 { return 0, errors.New("exec: no argv") } if attr == nil { attr = new(ProcAttr) } p, err := fork() pid = int(p) if err != nil { return 0, err } // else code runs in child, which then should exec the new process err = execve(argv0, argv, attr.Env) if err != nil { // exec failed return 0, err } // 3. TODO: use pipes to communicate back child status return pid, nil } // In Golang, the idiomatic way to create a new process is to use the StartProcess function. // Since the Model of operating system processes in tinygo differs from the one in Golang, we need to implement the StartProcess function differently. // The startProcess function is a wrapper around the forkExec function, which is a wrapper around the fork and execve system calls. // The StartProcess function creates a new process by forking the current process and then calling execve to replace the current process with the new process. // It thereby replaces the newly created process with the specified command and arguments. func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err error) { if attr != nil { if attr.Dir != "" { return nil, ErrNotImplementedDir } if attr.Sys != nil { return nil, ErrNotImplementedSys } if len(attr.Files) != 0 { return nil, ErrNotImplementedFiles } } pid, err := forkExec(name, argv, attr) if err != nil { return nil, err } return findProcess(pid) } ================================================ FILE: src/os/exec_linux_test.go ================================================ //go:build linux && !baremetal && !tinygo.wasm package os_test import ( "errors" . "os" "runtime" "syscall" "testing" ) // Test the functionality of the forkExec function, which is used to fork and exec a new process. // This test is not run on Windows, as forkExec is not supported on Windows. // This test is not run on Plan 9, as forkExec is not supported on Plan 9. func TestForkExec(t *testing.T) { if runtime.GOOS != "linux" { t.Logf("skipping test on %s", runtime.GOOS) return } proc, err := StartProcess("/bin/echo", []string{"hello", "world"}, &ProcAttr{}) if !errors.Is(err, nil) { t.Fatalf("forkExec failed: %v", err) } if proc == nil { t.Fatalf("proc is nil") } if proc.Pid == 0 { t.Fatalf("forkExec failed: new process has pid 0") } } func TestForkExecErrNotExist(t *testing.T) { proc, err := StartProcess("invalid", []string{"invalid"}, &ProcAttr{}) if !errors.Is(err, ErrNotExist) { t.Fatalf("wanted ErrNotExist, got %s\n", err) } if proc != nil { t.Fatalf("wanted nil, got %v\n", proc) } } func TestForkExecProcDir(t *testing.T) { proc, err := StartProcess("/bin/echo", []string{"hello", "world"}, &ProcAttr{Dir: "dir"}) if !errors.Is(err, ErrNotImplementedDir) { t.Fatalf("wanted ErrNotImplementedDir, got %v\n", err) } if proc != nil { t.Fatalf("wanted nil, got %v\n", proc) } } func TestForkExecProcSys(t *testing.T) { proc, err := StartProcess("/bin/echo", []string{"hello", "world"}, &ProcAttr{Sys: &syscall.SysProcAttr{}}) if !errors.Is(err, ErrNotImplementedSys) { t.Fatalf("wanted ErrNotImplementedSys, got %v\n", err) } if proc != nil { t.Fatalf("wanted nil, got %v\n", proc) } } func TestForkExecProcFiles(t *testing.T) { proc, err := StartProcess("/bin/echo", []string{"hello", "world"}, &ProcAttr{Files: []*File{}}) if !errors.Is(err, ErrNotImplementedFiles) { t.Fatalf("wanted ErrNotImplementedFiles, got %v\n", err) } if proc != nil { t.Fatalf("wanted nil, got %v\n", proc) } } ================================================ FILE: src/os/exec_other.go ================================================ //go:build (!aix && !android && !freebsd && !linux && !netbsd && !openbsd && !plan9 && !solaris) || baremetal || tinygo.wasm || nintendoswitch package os import "syscall" var ( Interrupt Signal = syscall.SIGINT Kill Signal = syscall.SIGKILL ) func findProcess(pid int) (*Process, error) { return &Process{Pid: pid}, nil } func (p *Process) release() error { p.Pid = -1 return nil } func forkExec(_ string, _ []string, _ *ProcAttr) (pid int, err error) { return 0, ErrNotImplemented } func startProcess(_ string, _ []string, _ *ProcAttr) (proc *Process, err error) { return &Process{Pid: 0}, ErrNotImplemented } ================================================ FILE: src/os/exec_test.go ================================================ // Copyright 2024 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os_test import ( . "os" "testing" ) func TestFindProcess(t *testing.T) { // NOTE: For now, we only test the Linux case since only exec_posix.go is currently the only implementation. // Linux guarantees that there is pid 0 proc, err := FindProcess(0) if err != nil { t.Error("FindProcess(0): wanted err == nil, got %v:", err) } if proc.Pid != 0 { t.Error("Expected pid 0, got: ", proc.Pid) } pid0 := Process{Pid: 0} if *proc != pid0 { t.Error("Expected &Process{Pid: 0}, got", *proc) } } ================================================ FILE: src/os/executable_darwin.go ================================================ //go:build darwin package os // via runtime because we need argc/argv ptrs func runtime_executable_path() string func Executable() (string, error) { p := runtime_executable_path() if p != "" && p[0] == '/' { // absolute path return p, nil } cwd, err := Getwd() if err != nil { return "", err } return joinPath(cwd, p), nil } ================================================ FILE: src/os/executable_other.go ================================================ //go:build (!linux && !darwin) || baremetal package os import "errors" func Executable() (string, error) { return "", errors.New("Executable not implemented") } ================================================ FILE: src/os/executable_procfs.go ================================================ // The following is copied from Go 1.17 official implementation. // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build linux && !baremetal package os func Executable() (string, error) { path, err := Readlink("/proc/self/exe") // When the executable has been deleted then Readlink returns a // path appended with " (deleted)". return stringsTrimSuffix(path, " (deleted)"), err } // stringsTrimSuffix is the same as strings.TrimSuffix. func stringsTrimSuffix(s, suffix string) string { if len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix { return s[:len(s)-len(suffix)] } return s } ================================================ FILE: src/os/export_test.go ================================================ // Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os // Export for testing. var ErrWriteAtInAppendMode = errWriteAtInAppendMode var ErrPatternHasSeparator = errPatternHasSeparator ================================================ FILE: src/os/export_windows_test.go ================================================ // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os // Export for testing. var ( FixLongPath = fixLongPath CanUseLongPaths = canUseLongPaths ) ================================================ FILE: src/os/file.go ================================================ // Portions copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // This file was originally copied from Go, see: // https://github.com/golang/go/blob/master/src/os/file.go // // Some of the code inherited from Go is not used anymore in Tinygo, but we keep // changes to a minimum to help simplify bringing changes (e.g. the lstat global // is not used here anymore, but we might need it if we add tests from Go in // this package). // Package os implements a subset of the Go "os" package. See // https://godoc.org/os for details. // // Note that the current implementation is blocking. This limitation should be // removed in a future version. package os import ( "errors" "io" "io/fs" "runtime" "syscall" "time" ) // Seek whence values. // // Deprecated: Use io.SeekStart, io.SeekCurrent, and io.SeekEnd. const ( SEEK_SET int = io.SeekStart SEEK_CUR int = io.SeekCurrent SEEK_END int = io.SeekEnd ) // lstat is overridden in tests. var lstat = Lstat // Mkdir creates a directory. If the operation fails, it will return an error of // type *PathError. func Mkdir(path string, perm FileMode) error { fs, suffix := findMount(path) if fs == nil { return &PathError{Op: "mkdir", Path: path, Err: ErrNotExist} } err := fs.Mkdir(suffix, perm) if err != nil { return &PathError{Op: "mkdir", Path: path, Err: err} } return nil } // Many functions in package syscall return a count of -1 instead of 0. // Using fixCount(call()) instead of call() corrects the count. func fixCount(n int, err error) (int, error) { if n < 0 { n = 0 } return n, err } // Remove removes a file or (empty) directory. If the operation fails, it will // return an error of type *PathError. func Remove(path string) error { fs, suffix := findMount(path) if fs == nil { return &PathError{Op: "remove", Path: path, Err: ErrNotExist} } err := fs.Remove(suffix) if err != nil { return err } return nil } // Name returns the name of the file with which it was opened. func (f *File) Name() string { return f.name } // OpenFile opens the named file. If the operation fails, the returned error // will be of type *PathError. func OpenFile(name string, flag int, perm FileMode) (*File, error) { fs, suffix := findMount(name) if fs == nil { return nil, &PathError{Op: "open", Path: name, Err: ErrNotExist} } handle, err := fs.OpenFile(suffix, flag, perm) if err != nil { return nil, &PathError{Op: "open", Path: name, Err: err} } f := NewFile(handle, name) f.appendMode = (flag & O_APPEND) != 0 return f, nil } // Open opens the file named for reading. func Open(name string) (*File, error) { return OpenFile(name, O_RDONLY, 0) } // Create creates the named file, overwriting it if it already exists. func Create(name string) (*File, error) { return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666) } // Read reads up to len(b) bytes from the File. It returns the number of bytes // read and any error encountered. At end of file, Read returns 0, io.EOF. func (f *File) Read(b []byte) (n int, err error) { if f.handle == nil { err = ErrClosed } else { n, err = f.handle.Read(b) } // TODO: want to always wrap, like upstream, but ReadFile() compares against exactly io.EOF? if err != nil && err != io.EOF { err = &PathError{Op: "read", Path: f.name, Err: err} } return } var errNegativeOffset = errors.New("negative offset") // ReadAt reads up to len(b) bytes from the File at the given absolute offset. // It returns the number of bytes read and any error encountered, possible io.EOF. // At end of file, Read returns 0, io.EOF. func (f *File) ReadAt(b []byte, offset int64) (n int, err error) { if offset < 0 { return 0, &PathError{Op: "readat", Path: f.name, Err: errNegativeOffset} } if f.handle == nil { return 0, &PathError{Op: "readat", Path: f.name, Err: ErrClosed} } for len(b) > 0 { m, e := f.handle.ReadAt(b, offset) if e != nil { // TODO: want to always wrap, like upstream, but TestReadAtEOF compares against exactly io.EOF? if e != io.EOF { err = &PathError{Op: "readat", Path: f.name, Err: e} } else { err = e } break } n += m b = b[m:] offset += int64(m) } return } // Write writes len(b) bytes to the File. It returns the number of bytes written // and an error, if any. Write returns a non-nil error when n != len(b). func (f *File) Write(b []byte) (n int, err error) { if f.handle == nil { err = ErrClosed } else { n, err = f.handle.Write(b) } if err != nil { err = &PathError{Op: "write", Path: f.name, Err: err} } return } // WriteString is like Write, but writes the contents of string s rather than a // slice of bytes. func (f *File) WriteString(s string) (n int, err error) { return f.Write([]byte(s)) } var errWriteAtInAppendMode = errors.New("os: invalid use of WriteAt on file opened with O_APPEND") // WriteAt writes len(b) bytes to the File starting at byte offset off. // It returns the number of bytes written and an error, if any. // WriteAt returns a non-nil error when n != len(b). // // If file was opened with the O_APPEND flag, WriteAt returns an error. func (f *File) WriteAt(b []byte, offset int64) (n int, err error) { switch { case offset < 0: return 0, &PathError{Op: "writeat", Path: f.name, Err: errNegativeOffset} case f.handle == nil: return 0, &PathError{Op: "writeat", Path: f.name, Err: ErrClosed} case f.appendMode: // Go does not wrap this error but it would be more consistent // if it did. return 0, errWriteAtInAppendMode } for len(b) > 0 { m, e := f.handle.WriteAt(b, offset) if e != nil { err = &PathError{Op: "writeat", Path: f.name, Err: e} break } n += m b = b[m:] offset += int64(m) } return } // Close closes the File, rendering it unusable for I/O. func (f *File) Close() (err error) { if f.handle == nil { err = ErrClosed } else { // Some platforms manage extra state other than the system handle which // needs to be released when the file is closed. For example, darwin // files have a DIR object holding a dup of the file descriptor, and // linux files hold a buffer which needs to be released to a pool. // // These platform-specific logic is provided by the (*file).close method // which is why we do not call the handle's Close method directly. err = f.file.close() if err == nil { f.handle = nil } } if err != nil { err = &PathError{Op: "close", Path: f.name, Err: err} } return } // Seek sets the offset for the next Read or Write on file to offset, interpreted // according to whence: 0 means relative to the origin of the file, 1 means // relative to the current offset, and 2 means relative to the end. // It returns the new offset and an error, if any. // The behavior of Seek on a file opened with O_APPEND is not specified. // // If f is a directory, the behavior of Seek varies by operating // system; you can seek to the beginning of the directory on Unix-like // operating systems, but not on Windows. func (f *File) Seek(offset int64, whence int) (ret int64, err error) { if f.handle == nil { err = ErrClosed } else { ret, err = f.handle.Seek(offset, whence) } if err != nil { err = &PathError{Op: "seek", Path: f.name, Err: err} } return } func (f *File) SyscallConn() (conn syscall.RawConn, err error) { if f.handle == nil { err = ErrClosed } else { err = ErrNotImplemented } return } // SetDeadline sets the read and write deadlines for a File. // Calls to SetDeadline for files that do not support deadlines will return ErrNoDeadline // This stub always returns ErrNoDeadline. // A zero value for t means I/O operations will not time out. func (f *File) SetDeadline(t time.Time) error { if f.handle == nil { return ErrClosed } return f.setDeadline(t) } // SetReadDeadline sets the deadline for future Read calls and any // currently-blocked Read call. func (f *File) SetReadDeadline(t time.Time) error { return f.setReadDeadline(t) } // SetWriteDeadline sets the deadline for any future Write calls and any // currently-blocked Write call. func (f *File) SetWriteDeadline(t time.Time) error { return f.setWriteDeadline(t) } // fd is an internal interface that is used to try a type assertion in order to // call the Fd() method of the underlying file handle if it is implemented. type fd interface { Fd() uintptr } // Fd returns the file handle referencing the open file. func (f *File) Fd() uintptr { handle, ok := f.handle.(fd) if ok { return handle.Fd() } return ^uintptr(0) } // Sync commits the current contents of the file to stable storage. // Typically, this means flushing the file system's in-memory copy of recently // written data to disk. func (f *File) Sync() (err error) { if f.handle == nil { err = ErrClosed } else { err = f.handle.Sync() } return } // Chmod changes the mode of the file to mode. If there is an error, it will be // of type *PathError. func (f *File) Chmod(mode FileMode) (err error) { if f.handle == nil { err = ErrClosed } else { err = f.chmod(mode) } return } // Chdir changes the current working directory to the file, which must be a // directory. If there is an error, it will be of type *PathError. func (f *File) Chdir() (err error) { if f.handle == nil { err = ErrClosed } else { err = f.chdir() } return } // LinkError records an error during a link or symlink or rename system call and // the paths that caused it. type LinkError struct { Op string Old string New string Err error } func (e *LinkError) Error() string { return e.Op + " " + e.Old + " " + e.New + ": " + e.Err.Error() } func (e *LinkError) Unwrap() error { return e.Err } // OpenFile flag values. const ( O_RDONLY int = syscall.O_RDONLY O_WRONLY int = syscall.O_WRONLY O_RDWR int = syscall.O_RDWR O_APPEND int = syscall.O_APPEND O_CREATE int = syscall.O_CREAT O_EXCL int = syscall.O_EXCL O_SYNC int = syscall.O_SYNC O_TRUNC int = syscall.O_TRUNC ) func Getwd() (string, error) { return syscall.Getwd() } // TempDir returns the default directory to use for temporary files. // // On Unix systems, it returns $TMPDIR if non-empty, else /tmp. // On Windows, it uses GetTempPath, returning the first non-empty // value from %TMP%, %TEMP%, %USERPROFILE%, or the Windows directory. // // The directory is neither guaranteed to exist nor have accessible // permissions. func TempDir() string { return tempDir() } // UserHomeDir returns the current user's home directory. // // On Unix, including macOS, it returns the $HOME environment variable. // On Windows, it returns %USERPROFILE%. // On Plan 9, it returns the $home environment variable. func UserHomeDir() (string, error) { env, enverr := "HOME", "$HOME" switch runtime.GOOS { case "windows": env, enverr = "USERPROFILE", "%userprofile%" case "plan9": env, enverr = "home", "$home" } if v := Getenv(env); v != "" { return v, nil } // On some geese the home directory is not always defined. switch runtime.GOOS { case "android": return "/sdcard", nil case "ios": return "/", nil } return "", errors.New(enverr + " is not defined") } type ( FileMode = fs.FileMode FileInfo = fs.FileInfo ) // The followings are copied from Go 1.16 or 1.17 official implementation: // https://github.com/golang/go/blob/go1.16/src/os/file.go // DirFS returns a file system (an fs.FS) for the tree of files rooted at the directory dir. // // Note that DirFS("/prefix") only guarantees that the Open calls it makes to the // operating system will begin with "/prefix": DirFS("/prefix").Open("file") is the // same as os.Open("/prefix/file"). So if /prefix/file is a symbolic link pointing outside // the /prefix tree, then using DirFS does not stop the access any more than using // os.Open does. DirFS is therefore not a general substitute for a chroot-style security // mechanism when the directory tree contains arbitrary content. func DirFS(dir string) fs.FS { return dirFS(dir) } func containsAny(s, chars string) bool { for i := 0; i < len(s); i++ { for j := 0; j < len(chars); j++ { if s[i] == chars[j] { return true } } } return false } type dirFS string func (dir dirFS) Open(name string) (fs.File, error) { if !fs.ValidPath(name) || runtime.GOOS == "windows" && containsAny(name, `\:`) { return nil, &PathError{Op: "open", Path: name, Err: ErrInvalid} } f, err := Open(string(dir) + "/" + name) if err != nil { return nil, err // nil fs.File } return f, nil } func (dir dirFS) Stat(name string) (fs.FileInfo, error) { if !fs.ValidPath(name) || runtime.GOOS == "windows" && containsAny(name, `\:`) { return nil, &PathError{Op: "stat", Path: name, Err: ErrInvalid} } f, err := Stat(string(dir) + "/" + name) if err != nil { return nil, err } return f, nil } // ReadFile reads the named file and returns the contents. // A successful call returns err == nil, not err == EOF. // Because ReadFile reads the whole file, it does not treat an EOF from Read // as an error to be reported. func ReadFile(name string) ([]byte, error) { f, err := Open(name) if err != nil { return nil, err } defer f.Close() var size int if info, err := f.Stat(); err == nil { size64 := info.Size() if int64(int(size64)) == size64 { size = int(size64) } } size++ // one byte for final read at EOF // If a file claims a small size, read at least 512 bytes. // In particular, files in Linux's /proc claim size 0 but // then do not work right if read in small pieces, // so an initial read of 1 byte would not work correctly. if size < 512 { size = 512 } data := make([]byte, 0, size) for { if len(data) >= cap(data) { d := append(data[:cap(data)], 0) data = d[:len(data)] } n, err := f.Read(data[len(data):cap(data)]) data = data[:len(data)+n] if err != nil { if err == io.EOF { err = nil } return data, err } } } // WriteFile writes data to the named file, creating it if necessary. // If the file does not exist, WriteFile creates it with permissions perm (before umask); // otherwise WriteFile truncates it before writing, without changing permissions. func WriteFile(name string, data []byte, perm FileMode) error { f, err := OpenFile(name, O_WRONLY|O_CREATE|O_TRUNC, perm) if err != nil { return err } _, err = f.Write(data) if err1 := f.Close(); err1 != nil && err == nil { err = err1 } return err } // The defined file mode bits are the most significant bits of the FileMode. // The nine least-significant bits are the standard Unix rwxrwxrwx permissions. // The values of these bits should be considered part of the public API and // may be used in wire protocols or disk representations: they must not be // changed, although new bits might be added. const ( // The single letters are the abbreviations // used by the String method's formatting. ModeDir = fs.ModeDir // d: is a directory ModeAppend = fs.ModeAppend // a: append-only ModeExclusive = fs.ModeExclusive // l: exclusive use ModeTemporary = fs.ModeTemporary // T: temporary file; Plan 9 only ModeSymlink = fs.ModeSymlink // L: symbolic link ModeDevice = fs.ModeDevice // D: device file ModeNamedPipe = fs.ModeNamedPipe // p: named pipe (FIFO) ModeSocket = fs.ModeSocket // S: Unix domain socket ModeSetuid = fs.ModeSetuid // u: setuid ModeSetgid = fs.ModeSetgid // g: setgid ModeCharDevice = fs.ModeCharDevice // c: Unix character device, when ModeDevice is set ModeSticky = fs.ModeSticky // t: sticky ModeIrregular = fs.ModeIrregular // ?: non-regular file; nothing else is known about this file // Mask for the type bits. For regular files, none will be set. ModeType = fs.ModeType ModePerm = fs.ModePerm // Unix permission bits, 0o777 ) ================================================ FILE: src/os/file_anyos.go ================================================ //go:build !baremetal && !js && !wasm_unknown && !nintendoswitch // Portions copyright 2009-2024 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os import ( "io" "syscall" ) func init() { // Mount the host filesystem at the root directory. This is what most // programs will be expecting. Mount("/", unixFilesystem{}) } // Stdin, Stdout, and Stderr are open Files pointing to the standard input, // standard output, and standard error file descriptors. var ( Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin") Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout") Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr") ) // isOS indicates whether we're running on a real operating system with // filesystem support. const isOS = true // Chdir changes the current working directory to the named directory. // If there is an error, it will be of type *PathError. func Chdir(dir string) error { if e := syscall.Chdir(dir); e != nil { return &PathError{Op: "chdir", Path: dir, Err: e} } return nil } // Rename renames (moves) oldpath to newpath. // If newpath already exists and is not a directory, Rename replaces it. // OS-specific restrictions may apply when oldpath and newpath are in different directories. // If there is an error, it will be of type *LinkError. func Rename(oldpath, newpath string) error { return rename(oldpath, newpath) } // unixFilesystem is an empty handle for a Unix/Linux filesystem. All operations // are relative to the current working directory. type unixFilesystem struct { } func (fs unixFilesystem) Mkdir(path string, perm FileMode) error { return handleSyscallError(syscall.Mkdir(path, uint32(perm))) } func (fs unixFilesystem) Remove(path string) error { // System call interface forces us to know // whether name is a file or directory. // Try both: it is cheaper on average than // doing a Stat plus the right one. e := handleSyscallError(syscall.Unlink(path)) if e == nil { return nil } e1 := handleSyscallError(syscall.Rmdir(path)) if e1 == nil { return nil } // Both failed: figure out which error to return. // OS X and Linux differ on whether unlink(dir) // returns EISDIR, so can't use that. However, // both agree that rmdir(file) returns ENOTDIR, // so we can use that to decide which error is real. // Rmdir might also return ENOTDIR if given a bad // file path, like /etc/passwd/foo, but in that case, // both errors will be ENOTDIR, so it's okay to // use the error from unlink. if e1 != syscall.ENOTDIR { e = e1 } return &PathError{Op: "remove", Path: path, Err: e} } func (fs unixFilesystem) OpenFile(path string, flag int, perm FileMode) (uintptr, error) { fp, err := syscall.Open(path, flag, uint32(perm)) return uintptr(fp), handleSyscallError(err) } // unixFileHandle is a Unix file pointer with associated methods that implement // the FileHandle interface. type unixFileHandle uintptr // Read reads up to len(b) bytes from the File. It returns the number of bytes // read and any error encountered. At end of file, Read returns 0, io.EOF. func (f unixFileHandle) Read(b []byte) (n int, err error) { n, err = syscall.Read(syscallFd(f), b) // In case of EISDIR, n == -1. // This breaks the assumption that n always represent the number of read bytes. if err == syscall.EISDIR { n = 0 } err = handleSyscallError(err) if n == 0 && len(b) > 0 && err == nil { err = io.EOF } return } // Write writes len(b) bytes to the File. It returns the number of bytes written // and an error, if any. Write returns a non-nil error when n != len(b). func (f unixFileHandle) Write(b []byte) (n int, err error) { n, err = syscall.Write(syscallFd(f), b) err = handleSyscallError(err) return } // Close closes the File, rendering it unusable for I/O. func (f unixFileHandle) Close() error { return handleSyscallError(syscall.Close(syscallFd(f))) } func (f unixFileHandle) Fd() uintptr { return uintptr(f) } // Chmod changes the mode of the named file to mode. // If the file is a symbolic link, it changes the mode of the link's target. // If there is an error, it will be of type *PathError. // // A different subset of the mode bits are used, depending on the // operating system. // // On Unix, the mode's permission bits, ModeSetuid, ModeSetgid, and // ModeSticky are used. // // On Windows, only the 0200 bit (owner writable) of mode is used; it // controls whether the file's read-only attribute is set or cleared. // The other bits are currently unused. For compatibility with Go 1.12 // and earlier, use a non-zero mode. Use mode 0400 for a read-only // file and 0600 for a readable+writable file. func Chmod(name string, mode FileMode) error { longName := fixLongPath(name) e := ignoringEINTR(func() error { return syscall.Chmod(longName, syscallMode(mode)) }) if e != nil { return &PathError{Op: "chmod", Path: name, Err: e} } return nil } // Chown changes the numeric uid and gid of the named file. // If the file is a symbolic link, it changes the uid and gid of the link's target. // A uid or gid of -1 means to not change that value. // If there is an error, it will be of type *PathError. func Chown(name string, uid, gid int) error { e := ignoringEINTR(func() error { return syscall.Chown(name, uid, gid) }) if e != nil { return &PathError{Op: "chown", Path: name, Err: e} } return nil } // ignoringEINTR makes a function call and repeats it if it returns an // EINTR error. This appears to be required even though we install all // signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846. // Also #20400 and #36644 are issues in which a signal handler is // installed without setting SA_RESTART. None of these are the common case, // but there are enough of them that it seems that we can't avoid // an EINTR loop. func ignoringEINTR(fn func() error) error { for { err := fn() if err != syscall.EINTR { return err } } } // handleSyscallError converts syscall errors into regular os package errors. // The err parameter must be either nil or of type syscall.Errno. func handleSyscallError(err error) error { if err == nil { return nil } switch err.(syscall.Errno) { case syscall.EEXIST: return ErrExist case syscall.ENOENT: return ErrNotExist default: return err } } // syscallMode returns the syscall-specific mode bits from Go's portable mode bits. func syscallMode(i FileMode) (o uint32) { o |= uint32(i.Perm()) if i&ModeSetuid != 0 { o |= syscall.S_ISUID } if i&ModeSetgid != 0 { o |= syscall.S_ISGID } if i&ModeSticky != 0 { o |= syscall.S_ISVTX } // No mapping for Go's ModeTemporary (plan9 only). return } ================================================ FILE: src/os/file_anyos_test.go ================================================ //go:build !baremetal && !js package os_test import ( "errors" "io" . "os" "runtime" "testing" ) // TestTempDir assumes that the test environment has a filesystem with a working temp dir. func TestTempDir(t *testing.T) { name := TempDir() + "/_os_test_TestTempDir" Remove(name) f, err := OpenFile(name, O_RDWR|O_CREATE, 0644) if err != nil { t.Errorf("OpenFile %s: %s", name, err) return } err = f.Close() if err != nil { t.Errorf("Close %s: %s", name, err) } err = Remove(name) if err != nil { t.Errorf("Remove %s: %s", name, err) } } func TestChdir(t *testing.T) { // Save and restore the current working directory after the test, otherwise // we might break other tests that depend on it. // // Note that it doesn't work if Chdir is broken, but then this test should // fail and highlight the issue if that is the case. oldDir, err := Getwd() if err != nil { t.Errorf("Getwd() returned %v", err) return } defer Chdir(oldDir) // create and cd into a new directory dir := "_os_test_TestChDir" Remove(dir) err = Mkdir(dir, 0755) defer Remove(dir) // even though not quite sure which directory it will execute in if err != nil { t.Errorf("Mkdir(%s, 0755) returned %v", dir, err) } err = Chdir(dir) if err != nil { t.Fatalf("Chdir %s: %s", dir, err) return } // create a file there file := "_os_test_TestTempDir.dat" f, err := OpenFile(file, O_RDWR|O_CREATE, 0644) if err != nil { t.Errorf("OpenFile %s: %s", file, err) } defer Remove(file) // even though not quite sure which directory it will execute in err = f.Close() if err != nil { t.Errorf("Close %s: %s", file, err) } // cd back to original directory err = Chdir("..") if err != nil { t.Errorf("Chdir ..: %s", err) } // clean up file and directory explicitly so we can check for errors fullname := dir + "/" + file err = Remove(fullname) if err != nil { t.Errorf("Remove %s: %s", fullname, err) } err = Remove(dir) if err != nil { t.Errorf("Remove %s: %s", dir, err) } } func TestStandardFd(t *testing.T) { if runtime.GOOS == "windows" { t.Log("TODO: TestFd fails on Windows, skipping") return } if fd := Stdin.Fd(); fd != 0 { t.Errorf("Stdin.Fd() = %d, want 0", fd) } if fd := Stdout.Fd(); fd != 1 { t.Errorf("Stdout.Fd() = %d, want 1", fd) } if fd := Stderr.Fd(); fd != 2 { t.Errorf("Stderr.Fd() = %d, want 2", fd) } } func TestFd(t *testing.T) { if runtime.GOOS == "windows" { t.Log("TODO: TestFd fails on Windows, skipping") return } f := newFile("TestFd.txt", t) defer Remove(f.Name()) defer f.Close() const data = "hello, world\n" io.WriteString(f, data) fd := NewFile(f.Fd(), "as-fd") defer fd.Close() b := make([]byte, 5) n, err := fd.ReadAt(b, 0) if n != 5 && err != nil { t.Errorf("Failed to read 5 bytes from file descriptor: %v", err) } if string(b) != data[:5] { t.Errorf("File descriptor contents not equal to file contents.") } } // closeTests is the list of tests used to validate that after calling Close, // calling any method of File returns ErrClosed. var closeTests = map[string]func(*File) error{ "Close": func(f *File) error { return f.Close() }, "Read": func(f *File) error { _, err := f.Read(nil) return err }, "ReadAt": func(f *File) error { _, err := f.ReadAt(nil, 0) return err }, "Seek": func(f *File) error { _, err := f.Seek(0, 0) return err }, "Sync": func(f *File) error { return f.Sync() }, "SyscallConn": func(f *File) error { _, err := f.SyscallConn() return err }, "Truncate": func(f *File) error { return f.Truncate(0) }, "Write": func(f *File) error { _, err := f.Write(nil) return err }, "WriteAt": func(f *File) error { _, err := f.WriteAt(nil, 0) return err }, "WriteString": func(f *File) error { _, err := f.WriteString("") return err }, } func TestClose(t *testing.T) { f := newFile("TestClose.txt", t) if err := f.Close(); err != nil { t.Error("unexpected error closing the file:", err) } if fd := f.Fd(); fd != ^uintptr(0) { t.Error("unexpected file handle after closing the file:", fd) } for name, test := range closeTests { if err := test(f); !errors.Is(err, ErrClosed) { t.Errorf("unexpected error returned by calling %s on a closed file: %v", name, err) } } } func TestReadOnDir(t *testing.T) { name := TempDir() + "/_os_test_TestReadOnDir" defer Remove(name) f, err := OpenFile(name, O_RDWR|O_CREATE, 0644) if err != nil { t.Errorf("OpenFile %s: %s", name, err) return } var buf [32]byte n, err := f.Read(buf[:]) if err == nil { t.Errorf("Error expected") return } if n != 0 { t.Errorf("Wrong read bytes: %s", err) } } ================================================ FILE: src/os/file_darwin.go ================================================ package os import "syscall" func pipe(p []int) error { return syscall.Pipe(p) } ================================================ FILE: src/os/file_notdarwin.go ================================================ //go:build (linux && !baremetal && !wasm_unknown) || wasip1 || wasip2 package os import "syscall" func pipe(p []int) error { return syscall.Pipe2(p, syscall.O_CLOEXEC) } ================================================ FILE: src/os/file_other.go ================================================ //go:build baremetal || (tinygo.wasm && !wasip1 && !wasip2) || nintendoswitch package os import ( _ "unsafe" ) // Stdin, Stdout, and Stderr are open Files pointing to the standard input, // standard output, and standard error file descriptors. var ( Stdin = NewFile(0, "/dev/stdin") Stdout = NewFile(1, "/dev/stdout") Stderr = NewFile(2, "/dev/stderr") ) const DevNull = "/dev/null" // isOS indicates whether we're running on a real operating system with // filesystem support. const isOS = false // stdioFileHandle represents one of stdin, stdout, or stderr depending on the // number. It implements the FileHandle interface. type stdioFileHandle uint8 // file is the real representation of *File. // The extra level of indirection ensures that no clients of os // can overwrite this data, which could cause the finalizer // to close the wrong file descriptor. type file struct { handle FileHandle name string appendMode bool } func (f *file) close() error { return f.handle.Close() } func NewFile(fd uintptr, name string) *File { return &File{&file{handle: stdioFileHandle(fd), name: name}} } // Chdir changes the current working directory to the named directory. // If there is an error, it will be of type *PathError. func Chdir(dir string) error { return ErrNotImplemented } // Rename renames (moves) oldpath to newpath. // If newpath already exists and is not a directory, Rename replaces it. // OS-specific restrictions may apply when oldpath and newpath are in different directories. // If there is an error, it will be of type *LinkError. func Rename(oldpath, newpath string) error { return ErrNotImplemented } // Read reads up to len(b) bytes from machine.Serial. // It returns the number of bytes read and any error encountered. func (f stdioFileHandle) Read(b []byte) (n int, err error) { if len(b) == 0 { return 0, nil } size := buffered() for size == 0 { gosched() size = buffered() } if size > len(b) { size = len(b) } for i := 0; i < size; i++ { b[i] = getchar() } return size, nil } func (f stdioFileHandle) ReadAt(b []byte, off int64) (n int, err error) { return 0, ErrNotImplemented } func (f stdioFileHandle) WriteAt(b []byte, off int64) (n int, err error) { return 0, ErrNotImplemented } // Write writes len(b) bytes to the output. It returns the number of bytes // written or an error if this file is not stdout or stderr. func (f stdioFileHandle) Write(b []byte) (n int, err error) { switch f { case 1, 2: // stdout, stderr for _, c := range b { putchar(c) } return len(b), nil default: return 0, ErrUnsupported } } // Close is unsupported on this system. func (f stdioFileHandle) Close() error { return ErrUnsupported } // Seek wraps syscall.Seek. func (f stdioFileHandle) Seek(offset int64, whence int) (int64, error) { return -1, ErrUnsupported } func (f stdioFileHandle) Sync() error { return ErrUnsupported } func (f stdioFileHandle) Fd() uintptr { return uintptr(f) } //go:linkname putchar runtime.putchar func putchar(c byte) //go:linkname getchar runtime.getchar func getchar() byte //go:linkname buffered runtime.buffered func buffered() int //go:linkname gosched runtime.Gosched func gosched() int func Pipe() (r *File, w *File, err error) { return nil, nil, ErrNotImplemented } func Symlink(oldname, newname string) error { return ErrNotImplemented } func Readlink(name string) (string, error) { return "", ErrNotImplemented } func tempDir() string { return "/tmp" } // Truncate is unsupported on this system. func Truncate(filename string, size int64) (err error) { return ErrUnsupported } // Truncate is unsupported on this system. func (f *File) Truncate(size int64) (err error) { if f.handle == nil { return ErrClosed } return Truncate(f.name, size) } func (f *File) chmod(mode FileMode) error { return ErrUnsupported } func (f *File) chdir() error { return ErrNotImplemented } ================================================ FILE: src/os/file_posix.go ================================================ package os import ( "time" ) //TODO: re-implement the ErrNoDeadline error in the correct code path // Chtimes is a stub, not yet implemented func Chtimes(name string, atime time.Time, mtime time.Time) error { return ErrNotImplemented } // setDeadline sets the read and write deadline. func (f *File) setDeadline(t time.Time) error { if t.IsZero() { return nil } return ErrNotImplemented } // setReadDeadline sets the read deadline, not yet implemented // A zero value for t means Read will not time out. func (f *File) setReadDeadline(t time.Time) error { if t.IsZero() { return nil } return ErrNotImplemented } // setWriteDeadline sets the write deadline, not yet implemented // A zero value for t means Read will not time out. func (f *File) setWriteDeadline(t time.Time) error { if t.IsZero() { return nil } return ErrNotImplemented } ================================================ FILE: src/os/file_unix.go ================================================ //go:build darwin || (linux && !baremetal && !wasm_unknown && !nintendoswitch) || wasip1 || wasip2 // target wasi sets GOOS=linux and thus the +linux build tag, // even though it doesn't show up in "tinygo info target -wasi" // Portions copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os import ( "io" "syscall" ) const DevNull = "/dev/null" type syscallFd = int // fixLongPath is a noop on non-Windows platforms. func fixLongPath(path string) string { return path } func rename(oldname, newname string) error { // TODO: import rest of upstream tests, handle fancy cases err := syscall.Rename(oldname, newname) if err != nil { return &LinkError{"rename", oldname, newname, err} } return nil } // file is the real representation of *File. // The extra level of indirection ensures that no clients of os // can overwrite this data, which could cause the finalizer // to close the wrong file descriptor. type file struct { handle FileHandle name string dirinfo *dirInfo // nil unless directory being read appendMode bool } func (f *file) close() (err error) { if f.dirinfo != nil { f.dirinfo.close() f.dirinfo = nil } return f.handle.Close() } func NewFile(fd uintptr, name string) *File { return &File{&file{handle: unixFileHandle(fd), name: name}} } // Truncate changes the size of the named file. // If the file is a symbolic link, it changes the size of the link's target. // If there is an error, it will be of type *PathError. func Truncate(name string, size int64) error { e := ignoringEINTR(func() error { return syscall.Truncate(name, size) }) if e != nil { return &PathError{Op: "truncate", Path: name, Err: e} } return nil } func Pipe() (r *File, w *File, err error) { var p [2]int err = handleSyscallError(pipe(p[:])) if err != nil { return } r = NewFile(uintptr(p[0]), "|0") w = NewFile(uintptr(p[1]), "|1") return } func tempDir() string { dir := Getenv("TMPDIR") if dir == "" { dir = "/tmp" } return dir } // Link creates newname as a hard link to the oldname file. // If there is an error, it will be of type *LinkError. func Link(oldname, newname string) error { e := ignoringEINTR(func() error { return syscall.Link(oldname, newname) }) if e != nil { return &LinkError{"link", oldname, newname, e} } return nil } // Symlink creates newname as a symbolic link to oldname. // On Windows, a symlink to a non-existent oldname creates a file symlink; // if oldname is later created as a directory the symlink will not work. // If there is an error, it will be of type *LinkError. func Symlink(oldname, newname string) error { e := ignoringEINTR(func() error { return syscall.Symlink(oldname, newname) }) if e != nil { return &LinkError{"symlink", oldname, newname, e} } return nil } // Readlink returns the destination of the named symbolic link. // If there is an error, it will be of type *PathError. func Readlink(name string) (string, error) { for len := 128; ; len *= 2 { b := make([]byte, len) var ( n int e error ) for { n, e = fixCount(syscall.Readlink(name, b)) if e != syscall.EINTR { break } } if e != nil { return "", &PathError{Op: "readlink", Path: name, Err: e} } if n < len { return string(b[0:n]), nil } } } // Truncate changes the size of the file. // It does not change the I/O offset. // If there is an error, it will be of type *PathError. // Alternatively just use 'raw' syscall by file name func (f *File) Truncate(size int64) (err error) { if f.handle == nil { return ErrClosed } return Truncate(f.name, size) } func (f *File) chmod(mode FileMode) error { if f.handle == nil { return ErrClosed } longName := fixLongPath(f.name) e := ignoringEINTR(func() error { return syscall.Chmod(longName, syscallMode(mode)) }) if e != nil { return &PathError{Op: "chmod", Path: f.name, Err: e} } return nil } func (f *File) chdir() error { if f.handle == nil { return ErrClosed } // TODO: use syscall.Fchdir instead longName := fixLongPath(f.name) e := ignoringEINTR(func() error { return syscall.Chdir(longName) }) if e != nil { return &PathError{Op: "chdir", Path: f.name, Err: e} } return nil } // ReadAt reads up to len(b) bytes from the File starting at the given absolute offset. // It returns the number of bytes read and any error encountered, possibly io.EOF. // At end of file, Pread returns 0, io.EOF. // TODO: move to file_anyos once ReadAt is implemented for windows func (f unixFileHandle) ReadAt(b []byte, offset int64) (n int, err error) { n, err = syscall.Pread(syscallFd(f), b, offset) err = handleSyscallError(err) if n == 0 && len(b) > 0 && err == nil { err = io.EOF } return } // WriteAt writes len(b) bytes to the File starting at byte offset off. // It returns the number of bytes written and an error, if any. // WriteAt returns a non-nil error when n != len(b). // // If file was opened with the O_APPEND flag, WriteAt returns an error. // // TODO: move to file_anyos once WriteAt is implemented for windows. func (f unixFileHandle) WriteAt(b []byte, offset int64) (int, error) { n, err := syscall.Pwrite(syscallFd(f), b, offset) return n, handleSyscallError(err) } // Seek wraps syscall.Seek. func (f unixFileHandle) Seek(offset int64, whence int) (int64, error) { newoffset, err := syscall.Seek(syscallFd(f), offset, whence) return newoffset, handleSyscallError(err) } func (f unixFileHandle) Sync() error { err := syscall.Fsync(syscallFd(f)) return handleSyscallError(err) } type unixDirent struct { parent string name string typ FileMode info FileInfo } func (d *unixDirent) Name() string { return d.name } func (d *unixDirent) IsDir() bool { return d.typ.IsDir() } func (d *unixDirent) Type() FileMode { return d.typ } func (d *unixDirent) Info() (FileInfo, error) { if d.info != nil { return d.info, nil } return lstat(d.parent + "/" + d.name) } func newUnixDirent(parent, name string, typ FileMode) (DirEntry, error) { ude := &unixDirent{ parent: parent, name: name, typ: typ, } if typ != ^FileMode(0) && !testingForceReadDirLstat { return ude, nil } info, err := lstat(parent + "/" + name) if err != nil { return nil, err } ude.typ = info.Mode().Type() ude.info = info return ude, nil } // Since internal/poll is not available, we need to stub this out. // Big go requires the option to add the fd to the polling system. // //go:linkname net_newUnixFile net.newUnixFile func net_newUnixFile(fd int, name string) *File { if fd < 0 { panic("invalid FD") } // see src/os/file_unix.go:162 newFile for the original implementation. // return newFile(fd, name, kindSock, true) return NewFile(uintptr(fd), name) } ================================================ FILE: src/os/file_windows.go ================================================ //go:build windows // Portions copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os import ( "internal/syscall/windows" "syscall" "unicode/utf16" ) const DevNull = "NUL" type syscallFd = syscall.Handle // Symlink is a stub, it is not implemented. func Symlink(oldname, newname string) error { return ErrNotImplemented } // Readlink is a stub (for now), always returning the string it was given func Readlink(name string) (string, error) { return name, nil } func rename(oldname, newname string) error { e := windows.Rename(fixLongPath(oldname), fixLongPath(newname)) if e != nil { return &LinkError{"rename", oldname, newname, e} } return nil } type file struct { handle FileHandle name string appendMode bool } func (f *file) close() error { return f.handle.Close() } func NewFile(fd uintptr, name string) *File { return &File{&file{handle: unixFileHandle(fd), name: name}} } func Pipe() (r *File, w *File, err error) { var p [2]syscall.Handle e := handleSyscallError(syscall.Pipe(p[:])) if e != nil { return nil, nil, err } r = NewFile(uintptr(p[0]), "|0") w = NewFile(uintptr(p[1]), "|1") return } func (f *unixFileHandle) Truncate(size int64) error { return ErrNotImplemented } // Truncate changes the size of the named file. // If the file is a symbolic link, it changes the size of the link's target. func Truncate(name string, size int64) error { return &PathError{Op: "truncate", Path: name, Err: ErrNotImplemented} } func tempDir() string { n := uint32(syscall.MAX_PATH) for { b := make([]uint16, n) n, _ = syscall.GetTempPath(uint32(len(b)), &b[0]) if n > uint32(len(b)) { continue } if n == 3 && b[1] == ':' && b[2] == '\\' { // Do nothing for path, like C:\. } else if n > 0 && b[n-1] == '\\' { // Otherwise remove terminating \. n-- } return string(utf16.Decode(b[:n])) } } // ReadAt reads up to len(b) bytes from the File starting at the given absolute offset. // It returns the number of bytes read and any error encountered, possibly io.EOF. // At end of file, Pread returns 0, io.EOF. // TODO: move to file_anyos once ReadAt is implemented for windows func (f unixFileHandle) ReadAt(b []byte, offset int64) (n int, err error) { return -1, ErrNotImplemented } // WriteAt writes len(b) bytes to the File starting at byte offset off. // It returns the number of bytes written and an error, if any. // WriteAt returns a non-nil error when n != len(b). // // If file was opened with the O_APPEND flag, WriteAt returns an error. // // TODO: move to file_anyos once WriteAt is implemented for windows. func (f unixFileHandle) WriteAt(b []byte, offset int64) (n int, err error) { return -1, ErrNotImplemented } // Seek wraps syscall.Seek. func (f unixFileHandle) Seek(offset int64, whence int) (int64, error) { newoffset, err := syscall.Seek(syscallFd(f), offset, whence) return newoffset, handleSyscallError(err) } func (f unixFileHandle) Sync() error { return ErrNotImplemented } // Truncate changes the size of the named file. // If the file is a symbolic link, it changes the size of the link's target. func (f *File) Truncate(size int64) error { if f.handle == nil { return &PathError{Op: "truncate", Path: f.name, Err: ErrClosed} } return Truncate(f.name, size) } // isWindowsNulName reports whether name is os.DevNull ('NUL') on Windows. // True is returned if name is 'NUL' whatever the case. func isWindowsNulName(name string) bool { if len(name) != 3 { return false } if name[0] != 'n' && name[0] != 'N' { return false } if name[1] != 'u' && name[1] != 'U' { return false } if name[2] != 'l' && name[2] != 'L' { return false } return true } func (f *File) chmod(mode FileMode) error { return ErrNotImplemented } func (f *File) chdir() error { return ErrNotImplemented } ================================================ FILE: src/os/filesystem.go ================================================ package os import ( "strings" ) // mounts lists the mount points currently mounted in the filesystem provided by // the os package. To resolve a path to a mount point, it is scanned from top to // bottom looking for the first prefix match. var mounts []mountPoint type mountPoint struct { // prefix is a filesystem prefix, that always starts and ends with a forward // slash. To denote the root filesystem, use a single slash: "/". // This allows fast checking whether a path lies within a mount point. prefix string // filesystem is the Filesystem implementation that is mounted at this mount // point. filesystem Filesystem } // Filesystem provides an interface for generic filesystem drivers mounted in // the os package. The errors returned must be one of the os.Err* errors, or a // custom error if one doesn't exist. It should not be a *PathError because // errors will be wrapped with a *PathError by the filesystem abstraction. // // WARNING: this interface is not finalized and may change in a future version. type Filesystem interface { // OpenFile opens the named file. OpenFile(name string, flag int, perm FileMode) (uintptr, error) // Mkdir creates a new directory with the specified permission (before // umask). Some filesystems may not support directories or permissions. Mkdir(name string, perm FileMode) error // Remove removes the named file or (empty) directory. Remove(name string) error } // FileHandle is an interface that should be implemented by filesystems // implementing the Filesystem interface. // // WARNING: this interface is not finalized and may change in a future version. type FileHandle interface { // Read reads up to len(b) bytes from the file. Read(b []byte) (n int, err error) // ReadAt reads up to len(b) bytes from the file starting at the given absolute offset ReadAt(b []byte, offset int64) (n int, err error) // Seek resets the file pointer relative to start, current position, or end Seek(offset int64, whence int) (newoffset int64, err error) // Sync blocks until buffered writes have been written to persistent storage Sync() (err error) // Write writes up to len(b) bytes to the file. Write(b []byte) (n int, err error) // WriteAt writes b to the file at the given absolute offset WriteAt(b []byte, offset int64) (n int, err error) // Close closes the file, making it unusable for further writes. Close() (err error) } // findMount returns the appropriate (mounted) filesystem to use for a given // filename plus the path relative to that filesystem. func findMount(path string) (Filesystem, string) { for i := len(mounts) - 1; i >= 0; i-- { mount := mounts[i] if strings.HasPrefix(path, mount.prefix) { return mount.filesystem, path[len(mount.prefix)-1:] } } if isOS { // Assume that the first entry in the mounts slice is the OS filesystem // at the root of the directory tree. Use it as-is, to support relative // paths. return mounts[0].filesystem, path } return nil, path } // Mount mounts the given filesystem in the filesystem abstraction layer of the // os package. It is not possible to unmount filesystems. Filesystems added // later will override earlier filesystems. // // The provided prefix must start and end with a forward slash. This is true for // the root directory ("/") for example. func Mount(prefix string, filesystem Filesystem) { if prefix[0] != '/' || prefix[len(prefix)-1] != '/' { panic("os.Mount: invalid prefix") } mounts = append(mounts, mountPoint{prefix, filesystem}) } ================================================ FILE: src/os/getpagesize_test.go ================================================ //go:build windows || darwin || (linux && !baremetal) || wasip1 || wasip2 package os_test import ( "os" "testing" ) func TestGetpagesize(t *testing.T) { pagesize := os.Getpagesize() if pagesize == 0x1000 || pagesize == 0x4000 || pagesize == 0x10000 { return } t.Errorf("os.Getpagesize() returns strange value %d", pagesize) } ================================================ FILE: src/os/os_anyos_test.go ================================================ //go:build windows || darwin || (linux && !baremetal) || wasip1 || wasip2 package os_test import ( "io/fs" "os" . "os" "path/filepath" "runtime" "strconv" "strings" "testing" "testing/fstest" "time" ) var dot = []string{ "dir.go", "env.go", "errors.go", "file.go", "os_test.go", "types.go", "stat_darwin.go", } func randomName() string { // fastrand() does not seem available here, so fake it ns := time.Now().Nanosecond() pid := Getpid() return strconv.FormatUint(uint64(ns^pid), 10) } func TestMkdir(t *testing.T) { dir := TempDir() + "/TestMkdir" + randomName() Remove(dir) err := Mkdir(dir, 0755) defer Remove(dir) if err != nil { t.Errorf("Mkdir(%s, 0755) returned %v", dir, err) } // tests the "directory" branch of Remove err = Remove(dir) if err != nil { t.Errorf("Remove(%s) returned %v", dir, err) } } func TestStatBadDir(t *testing.T) { if runtime.GOOS == "windows" { t.Log("TODO: TestStatBadDir: IsNotExist fails on Windows, skipping") return } dir := TempDir() badDir := filepath.Join(dir, "not-exist/really-not-exist") _, err := Stat(badDir) if pe, ok := err.(*fs.PathError); !ok || !IsNotExist(err) || pe.Path != badDir { t.Errorf("Mkdir error = %#v; want PathError for path %q satisfying IsNotExist", err, badDir) } } func equal(name1, name2 string) (r bool) { switch runtime.GOOS { case "windows": r = strings.ToLower(name1) == strings.ToLower(name2) default: r = name1 == name2 } return } func TestFstat(t *testing.T) { if runtime.GOARCH == "386" || runtime.GOARCH == "arm" { t.Log("TODO: implement fstat for 386 and arm") return } sfname := "TestFstat" path := TempDir() + "/" + sfname payload := writeFile(t, path, O_CREATE|O_TRUNC|O_RDWR, "Hello") defer Remove(path) file, err1 := Open(path) if err1 != nil { t.Fatal("open failed:", err1) } defer file.Close() dir, err2 := file.Stat() if err2 != nil { t.Fatal("fstat failed:", err2) } if !equal(sfname, dir.Name()) { t.Error("name should be ", sfname, "; is", dir.Name()) } filesize := len(payload) if dir.Size() != int64(filesize) { t.Error("size should be", filesize, "; is", dir.Size()) } } func writeFile(t *testing.T, fname string, flag int, text string) string { f, err := OpenFile(fname, flag, 0666) if err != nil { t.Fatalf("Open: %v", err) } n, err := f.WriteString(text) if err != nil { t.Fatalf("WriteString: %d, %v", n, err) } f.Close() data, err := ReadFile(f.Name()) if err != nil { t.Fatalf("ReadFile: %v", err) } return string(data) } func TestRemove(t *testing.T) { f := TempDir() + "/TestRemove" + randomName() err := Remove(f) if err == nil { t.Errorf("TestRemove: remove of nonexistent file did not fail") } else { if pe, ok := err.(*fs.PathError); !ok { t.Errorf("TestRemove: expected PathError, got err %q", err.Error()) } else { if pe.Path != f { t.Errorf("TestRemove: PathError returned path %q, expected %q", pe.Path, f) } } if !IsNotExist(err) { t.Errorf("TestRemove: expected IsNotExist(err) true, got false; err %q", err.Error()) } } s := writeFile(t, f, O_CREATE|O_TRUNC|O_RDWR, "new") if s != "new" { t.Fatalf("writeFile: have %q want %q", s, "new") } // tests the "file" branch of Remove err = Remove(f) if err != nil { t.Fatalf("Remove: %v", err) } } // chtmpdir changes the working directory to a new temporary directory and // provides a cleanup function. func chtmpdir(t *testing.T) func() { oldwd, err := Getwd() if err != nil { t.Fatalf("chtmpdir: %v", err) } d, err := MkdirTemp("", "test") if err != nil { t.Fatalf("chtmpdir: %v", err) } if err := Chdir(d); err != nil { t.Fatalf("chtmpdir: %v", err) } return func() { if err := Chdir(oldwd); err != nil { t.Fatalf("chtmpdir: %v", err) } RemoveAll(d) } } func TestRename(t *testing.T) { // TODO: use t.TempDir() from, to := TempDir()+"/"+"TestRename-from", TempDir()+"/"+"TestRename-to" file, err := Create(from) defer Remove(from) // TODO: switch to t.Tempdir, remove this line if err != nil { t.Fatalf("open %q failed: %v", from, err) } defer Remove(to) // TODO: switch to t.Tempdir, remove this line if err = file.Close(); err != nil { t.Errorf("close %q failed: %v", from, err) } err = Rename(from, to) if err != nil { t.Fatalf("rename %q, %q failed: %v", to, from, err) } _, err = Stat(to) if err != nil { t.Errorf("stat %q failed: %v", to, err) } } func TestRenameOverwriteDest(t *testing.T) { from, to := TempDir()+"/"+"TestRenameOverwrite-from", TempDir()+"/"+"TestRenameOverwrite-to" toData := []byte("to") fromData := []byte("from") err := os.WriteFile(to, toData, 0777) defer Remove(to) // TODO: switch to t.Tempdir, remove this line if err != nil { t.Fatalf("write file %q failed: %v", to, err) } err = os.WriteFile(from, fromData, 0777) defer Remove(from) // TODO: switch to t.Tempdir, remove this line if err != nil { t.Fatalf("write file %q failed: %v", from, err) } err = Rename(from, to) if err != nil { t.Fatalf("rename %q, %q failed: %v", to, from, err) } _, err = Stat(from) if err == nil { t.Errorf("from file %q still exists", from) } if runtime.GOOS == "windows" { t.Log("TODO: TestRenameOverwriteDest: IsNotExist fails on Windows, skipping") } else if err != nil && !IsNotExist(err) { t.Fatalf("stat from: %v", err) } toFi, err := Stat(to) if err != nil { t.Fatalf("stat %q failed: %v", to, err) } if toFi.Size() != int64(len(fromData)) { t.Errorf(`"to" size = %d; want %d (old "from" size)`, toFi.Size(), len(fromData)) } } func TestRenameFailed(t *testing.T) { from, to := TempDir()+"/"+"RenameFailed-from", TempDir()+"/"+"RenameFailed-to" err := Rename(from, to) switch err := err.(type) { case *LinkError: if err.Op != "rename" { t.Errorf("rename %q, %q: err.Op: want %q, got %q", from, to, "rename", err.Op) } if err.Old != from { t.Errorf("rename %q, %q: err.Old: want %q, got %q", from, to, from, err.Old) } if err.New != to { t.Errorf("rename %q, %q: err.New: want %q, got %q", from, to, to, err.New) } case nil: t.Errorf("rename %q, %q: expected error, got nil", from, to) default: t.Errorf("rename %q, %q: expected %T, got %T %v", from, to, new(LinkError), err, err) } } func TestUserHomeDir(t *testing.T) { dir, err := UserHomeDir() if dir == "" && err == nil { t.Fatal("UserHomeDir returned an empty string but no error") } if err != nil { t.Logf("UserHomeDir failed: %v", err) return } fi, err := Stat(dir) if err != nil { t.Fatal(err) } if !fi.IsDir() { t.Fatalf("dir %s is not directory; type = %v", dir, fi.Mode()) } } func TestDirFS(t *testing.T) { if runtime.GOOS == "windows" { t.Log("TODO: implement Readdir for Windows") return } if runtime.GOOS == "wasip1" || runtime.GOOS == "wasip2" { t.Log("TODO: allow foo/bar/. as synonym for path foo/bar on wasi?") return } if err := fstest.TestFS(DirFS("./testdata/dirfs"), "a", "b", "dir/x"); err != nil { t.Fatal(err) } // Test that Open does not accept backslash as separator. d := DirFS(".") _, err := d.Open(`testdata\dirfs`) if err == nil { t.Fatalf(`Open testdata\dirfs succeeded`) } } func TestDirFSPathsValid(t *testing.T) { if runtime.GOOS == "windows" { t.Log("skipping on Windows") return } if runtime.GOOS == "wasip1" || runtime.GOOS == "wasip2" { t.Log("skipping on wasi because it fails on wasi on windows") return } // TODO: switch back to t.TempDir once it's implemented d, err := MkdirTemp("", "TestDirFSPathsValid") if err != nil { t.Fatal(err) } defer Remove(d) if err := WriteFile(filepath.Join(d, "control.txt"), []byte(string("Hello, world!")), 0644); err != nil { t.Fatal(err) } defer Remove(filepath.Join(d, "control.txt")) if err := WriteFile(filepath.Join(d, `e:xperi\ment.txt`), []byte(string("Hello, colon and backslash!")), 0644); err != nil { t.Fatal(err) } defer Remove(filepath.Join(d, `e:xperi\ment.txt`)) fsys := DirFS(d) err = fs.WalkDir(fsys, ".", func(path string, e fs.DirEntry, err error) error { if fs.ValidPath(e.Name()) { t.Logf("%q ok", e.Name()) } else { t.Errorf("%q INVALID", e.Name()) } return nil }) if err != nil { t.Fatal(err) } } ================================================ FILE: src/os/os_chmod_test.go ================================================ //go:build !baremetal && !js && !wasip1 && !wasip2 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // TODO: Move this back into os_test.go (as upstream has it) when wasi supports chmod package os_test import ( "errors" "io/fs" . "os" "runtime" "testing" ) func TestChmod(t *testing.T) { // Chmod f := newFile("TestChmod", t) defer Remove(f.Name()) defer f.Close() // Creation mode is read write fm := FileMode(0456) if runtime.GOOS == "windows" { fm = FileMode(0444) // read-only file } if err := Chmod(f.Name(), fm); err != nil { t.Fatalf("chmod %s %#o: %s", f.Name(), fm, err) } checkMode(t, f.Name(), fm) } // Since testing syscalls requires a static, predictable environment that has to be controlled // by the CI, we don't test for success but for failures and verify that the error messages are as expected. // EACCES is returned when the user does not have the required permissions to change the ownership of the file // ENOENT is returned when the file does not exist // ENOTDIR is returned when the file is not a directory func TestChownErr(t *testing.T) { if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { t.Log("skipping on " + runtime.GOOS) return } var ( TEST_UID_ROOT = 0 TEST_GID_ROOT = 0 ) f := newFile("TestChown", t) defer Remove(f.Name()) defer f.Close() // EACCES if err := Chown(f.Name(), TEST_UID_ROOT, TEST_GID_ROOT); err != nil { errCmp := fs.PathError{Op: "chown", Path: f.Name(), Err: errors.New("operation not permitted")} if errors.Is(err, &errCmp) { t.Fatalf("chown(%s, uid=%v, gid=%v): got '%v', want 'operation not permitted'", f.Name(), TEST_UID_ROOT, TEST_GID_ROOT, err) } } // ENOENT if err := Chown("invalid", Geteuid(), Getgid()); err != nil { errCmp := fs.PathError{Op: "chown", Path: "invalid", Err: errors.New("no such file or directory")} if errors.Is(err, &errCmp) { t.Fatalf("chown(%s, uid=%v, gid=%v): got '%v', want 'no such file or directory'", f.Name(), Geteuid(), Getegid(), err) } } } ================================================ FILE: src/os/os_hardlink_test.go ================================================ //go:build !windows && !baremetal && !js && !wasip1 && !wasm_unknown // Copyright 2024 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os_test import ( . "os" "syscall" "testing" ) func TestHardlink(t *testing.T) { defer chtmpdir(t)() from, to := "hardlinktestfrom", "hardlinktestto" file, err := Create(to) if err != nil { t.Fatalf("Create(%q) failed: %v", to, err) } if err = file.Close(); err != nil { t.Errorf("Close(%q) failed: %v", to, err) } err = Link(to, from) if err != nil { t.Fatalf("Link(%q, %q) failed: %v", to, from, err) } tostat, err := Lstat(to) if err != nil { t.Fatalf("Lstat(%q) failed: %v", to, err) } fromstat, err := Stat(from) if err != nil { t.Fatalf("Stat(%q) failed: %v", from, err) } if !SameFile(tostat, fromstat) { t.Errorf("Symlink(%q, %q) did not create symlink", to, from) } fromstat, err = Lstat(from) if err != nil { t.Fatalf("Lstat(%q) failed: %v", from, err) } // if they have the same inode, they are hard links if fromstat.Sys().(*syscall.Stat_t).Ino != tostat.Sys().(*syscall.Stat_t).Ino { t.Fatalf("Lstat(%q).Sys().Ino = %v, Lstat(%q).Sys().Ino = %v, want the same", to, tostat.Sys().(*syscall.Stat_t).Ino, from, fromstat.Sys().(*syscall.Stat_t).Ino) } file.Close() } ================================================ FILE: src/os/os_symlink_test.go ================================================ //go:build !windows && !baremetal && !js && !wasip1 && !wasip2 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os_test import ( . "os" "testing" ) // TODO: Move this back into os_anyos_test.go when wasi supports symlink func TestSymlink(t *testing.T) { //testenv.MustHaveSymlink(t) defer chtmpdir(t)() from, to := "symlinktestfrom", "symlinktestto" file, err := Create(to) if err != nil { t.Fatalf("Create(%q) failed: %v", to, err) } if err = file.Close(); err != nil { t.Errorf("Close(%q) failed: %v", to, err) } err = Symlink(to, from) if err != nil { t.Fatalf("Symlink(%q, %q) failed: %v", to, from, err) } tostat, err := Lstat(to) if err != nil { t.Fatalf("Lstat(%q) failed: %v", to, err) } if tostat.Mode()&ModeSymlink != 0 { t.Fatalf("Lstat(%q).Mode()&ModeSymlink = %v, want 0", to, tostat.Mode()&ModeSymlink) } fromstat, err := Stat(from) if err != nil { t.Fatalf("Stat(%q) failed: %v", from, err) } if !SameFile(tostat, fromstat) { t.Errorf("Symlink(%q, %q) did not create symlink", to, from) } fromstat, err = Lstat(from) if err != nil { t.Fatalf("Lstat(%q) failed: %v", from, err) } if fromstat.Mode()&ModeSymlink == 0 { t.Fatalf("Lstat(%q).Mode()&ModeSymlink = 0, want %v", from, ModeSymlink) } fromstat, err = Stat(from) if err != nil { t.Fatalf("Stat(%q) failed: %v", from, err) } if fromstat.Name() != from { t.Errorf("Stat(%q).Name() = %q, want %q", from, fromstat.Name(), from) } if fromstat.Mode()&ModeSymlink != 0 { t.Fatalf("Stat(%q).Mode()&ModeSymlink = %v, want 0", from, fromstat.Mode()&ModeSymlink) } s, err := Readlink(from) if err != nil { t.Fatalf("Readlink(%q) failed: %v", from, err) } if s != to { t.Fatalf("Readlink(%q) = %q, want %q", from, s, to) } file, err = Open(from) if err != nil { t.Fatalf("Open(%q) failed: %v", from, err) } file.Close() } ================================================ FILE: src/os/os_test.go ================================================ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os_test import ( "fmt" "io" "os" . "os" "runtime" "strings" "syscall" "testing" ) // localTmp returns a local temporary directory not on NFS. func localTmp() string { return TempDir() } func newFile(testName string, t *testing.T) (f *File) { f, err := CreateTemp("", testName) if err != nil { t.Fatalf("newFile %s: CreateTemp fails with %s", testName, err) } return } // Read with length 0 should not return EOF. func TestRead0(t *testing.T) { f := newFile("TestRead0", t) defer Remove(f.Name()) defer f.Close() const data = "hello, world\n" io.WriteString(f, data) f.Close() f, err := Open(f.Name()) if err != nil { t.Errorf("failed to reopen") } b := make([]byte, 0) n, err := f.Read(b) if n != 0 || err != nil { t.Errorf("Read(0) = %d, %v, want 0, nil", n, err) } b = make([]byte, 5) n, err = f.Read(b) if n <= 0 || err != nil { t.Errorf("Read(5) = %d, %v, want >0, nil", n, err) } } // ReadAt with length 0 should not return EOF. func TestReadAt0(t *testing.T) { if runtime.GOOS == "windows" { t.Log("TODO: implement Pread for Windows") return } f := newFile("TestReadAt0", t) defer Remove(f.Name()) defer f.Close() const data = "hello, world\n" io.WriteString(f, data) b := make([]byte, 0) n, err := f.ReadAt(b, 0) if n != 0 || err != nil { t.Errorf("ReadAt(0,0) = %d, %v, want 0, nil", n, err) } b = make([]byte, 5) n, err = f.ReadAt(b, 0) if n <= 0 || err != nil { t.Errorf("ReadAt(5,0) = %d, %v, want >0, nil", n, err) } } func checkMode(t *testing.T, path string, mode FileMode) { dir, err := Stat(path) if err != nil { t.Fatalf("Stat %q (looking for mode %#o): %s", path, mode, err) } if dir.Mode()&ModePerm != mode { t.Errorf("Stat %q: mode %#o want %#o", path, dir.Mode(), mode) } } func TestSeek(t *testing.T) { if runtime.GOARCH == "386" || runtime.GOARCH == "arm" { t.Log("TODO: implement seek for 386 and arm") return } f := newFile("TestSeek", t) if f == nil { t.Fatalf("f is nil") return // TODO: remove } defer Remove(f.Name()) defer f.Close() const data = "hello, world\n" io.WriteString(f, data) type test struct { in int64 whence int out int64 } var tests = []test{ {0, io.SeekCurrent, int64(len(data))}, {0, io.SeekStart, 0}, {5, io.SeekStart, 5}, {0, io.SeekEnd, int64(len(data))}, {0, io.SeekStart, 0}, {-1, io.SeekEnd, int64(len(data)) - 1}, {1 << 33, io.SeekStart, 1 << 33}, {1 << 33, io.SeekEnd, 1<<33 + int64(len(data))}, // Issue 21681, Windows 4G-1, etc: {1<<32 - 1, io.SeekStart, 1<<32 - 1}, {0, io.SeekCurrent, 1<<32 - 1}, {2<<32 - 1, io.SeekStart, 2<<32 - 1}, {0, io.SeekCurrent, 2<<32 - 1}, } for i, tt := range tests { off, err := f.Seek(tt.in, tt.whence) if off != tt.out || err != nil { if e, ok := err.(*PathError); ok && e.Err == syscall.EINVAL && tt.out > 1<<32 && runtime.GOOS == "linux" { mounts, _ := os.ReadFile("/proc/mounts") if strings.Contains(string(mounts), "reiserfs") { // Reiserfs rejects the big seeks. t.Skipf("skipping test known to fail on reiserfs; https://golang.org/issue/91") } } t.Errorf("#%d: Seek(%v, %v) = %v, %v want %v, nil", i, tt.in, tt.whence, off, err, tt.out) } } } func TestReadAt(t *testing.T) { if runtime.GOOS == "windows" { t.Log("TODO: implement Pread for Windows") return } f := newFile("TestReadAt", t) defer Remove(f.Name()) defer f.Close() const data = "hello, world\n" io.WriteString(f, data) b := make([]byte, 5) n, err := f.ReadAt(b, 7) if err != nil || n != len(b) { t.Fatalf("ReadAt 7: %d, %v", n, err) } if string(b) != "world" { t.Fatalf("ReadAt 7: have %q want %q", string(b), "world") } } // Verify that ReadAt doesn't affect seek offset. // In the Plan 9 kernel, there used to be a bug in the implementation of // the pread syscall, where the channel offset was erroneously updated after // calling pread on a file. func TestReadAtOffset(t *testing.T) { if runtime.GOOS == "windows" { t.Log("TODO: implement Pread for Windows") return } f := newFile("TestReadAtOffset", t) defer Remove(f.Name()) defer f.Close() const data = "hello, world\n" io.WriteString(f, data) f.Close() f, err := Open(f.Name()) if err != nil { t.Errorf("failed to reopen") } b := make([]byte, 5) n, err := f.ReadAt(b, 7) if err != nil || n != len(b) { t.Fatalf("ReadAt 7: %d, %v", n, err) } if string(b) != "world" { t.Fatalf("ReadAt 7: have %q want %q", string(b), "world") } n, err = f.Read(b) if err != nil || n != len(b) { t.Fatalf("Read: %d, %v", n, err) } if string(b) != "hello" { t.Fatalf("Read: have %q want %q", string(b), "hello") } } // Verify that ReadAt doesn't allow negative offset. func TestReadAtNegativeOffset(t *testing.T) { if runtime.GOOS == "windows" { t.Log("TODO: implement Pread for Windows") return } f := newFile("TestReadAtNegativeOffset", t) defer Remove(f.Name()) defer f.Close() const data = "hello, world\n" io.WriteString(f, data) f.Close() f, err := Open(f.Name()) if err != nil { t.Errorf("failed to reopen") } b := make([]byte, 5) n, err := f.ReadAt(b, -10) const wantsub = "negative offset" if !strings.Contains(err.Error(), wantsub) || n != 0 { t.Errorf("ReadAt(-10) = %v, %v; want 0, ...%q...", n, err, wantsub) } } func TestReadAtEOF(t *testing.T) { if runtime.GOOS == "windows" { t.Log("TODO: implement Pread for Windows") return } f := newFile("TestReadAtEOF", t) defer Remove(f.Name()) defer f.Close() _, err := f.ReadAt(make([]byte, 10), 0) switch err { case io.EOF: // all good case nil: t.Fatalf("ReadAt succeeded") default: t.Fatalf("ReadAt failed: %s", err) } } func TestWriteAt(t *testing.T) { if runtime.GOOS == "windows" { t.Log("TODO: implement Pwrite for Windows") return } f := newFile("TestWriteAt", t) defer Remove(f.Name()) defer f.Close() const data = "hello, world\n" io.WriteString(f, data) n, err := f.WriteAt([]byte("WORLD"), 7) if err != nil || n != 5 { t.Fatalf("WriteAt 7: %d, %v", n, err) } b, err := os.ReadFile(f.Name()) if err != nil { t.Fatalf("ReadFile %s: %v", f.Name(), err) } if string(b) != "hello, WORLD\n" { t.Fatalf("after write: have %q want %q", string(b), "hello, WORLD\n") } } // Verify that WriteAt doesn't allow negative offset. func TestWriteAtNegativeOffset(t *testing.T) { if runtime.GOOS == "windows" { t.Log("TODO: implement Pwrite for Windows") return } f := newFile("TestWriteAtNegativeOffset", t) defer Remove(f.Name()) defer f.Close() n, err := f.WriteAt([]byte("WORLD"), -10) const wantsub = "negative offset" if !strings.Contains(fmt.Sprint(err), wantsub) || n != 0 { t.Errorf("WriteAt(-10) = %v, %v; want 0, ...%q...", n, err, wantsub) } } // Verify that WriteAt doesn't work in append mode. func TestWriteAtInAppendMode(t *testing.T) { if runtime.GOOS == "windows" { t.Log("TODO: implement Pwrite for Windows") return } defer chtmpdir(t)() f, err := OpenFile("write_at_in_append_mode.txt", O_APPEND|O_CREATE|O_WRONLY, 0666) if err != nil { t.Fatalf("OpenFile: %v", err) } defer f.Close() _, err = f.WriteAt([]byte(""), 1) if err != ErrWriteAtInAppendMode { t.Fatalf("f.WriteAt returned %v, expected %v", err, ErrWriteAtInAppendMode) } } ================================================ FILE: src/os/osexec.go ================================================ //go:build linux && !baremetal && !tinygo.wasm && !nintendoswitch package os import ( "syscall" "unsafe" ) func fork() (pid int32, err error) { pid = libc_fork() if pid != 0 { if errno := *libc_errno(); errno != 0 { err = syscall.Errno(*libc_errno()) } } return } // the golang standard library does not expose interfaces for execve and fork, so we define them here the same way via the libc wrapper func execve(pathname string, argv []string, envv []string) error { argv0 := cstring(pathname) // transform argv and envv into the format expected by execve argv1 := make([]*byte, len(argv)+1) for i, arg := range argv { argv1[i] = &cstring(arg)[0] } argv1[len(argv)] = nil env1 := make([]*byte, len(envv)+1) for i, env := range envv { env1[i] = &cstring(env)[0] } env1[len(envv)] = nil ret, _, err := syscall.Syscall(syscall.SYS_EXECVE, uintptr(unsafe.Pointer(&argv0[0])), uintptr(unsafe.Pointer(&argv1[0])), uintptr(unsafe.Pointer(&env1[0]))) if int(ret) != 0 { return err } return nil } func cstring(s string) []byte { data := make([]byte, len(s)+1) copy(data, s) // final byte should be zero from the initial allocation return data } //export fork func libc_fork() int32 // Internal musl function to get the C errno pointer. // //export __errno_location func libc_errno() *int32 ================================================ FILE: src/os/path.go ================================================ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os import ( "syscall" ) // MkdirAll creates a directory named path, // along with any necessary parents, and returns nil, // or else returns an error. // The permission bits perm (before umask) are used for all // directories that MkdirAll creates. // If path is already a directory, MkdirAll does nothing // and returns nil. func MkdirAll(path string, perm FileMode) error { // Fast path: if we can tell whether path is a directory or file, stop with success or error. dir, err := Stat(path) if err == nil { if dir.IsDir() { return nil } return &PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR} } // Slow path: make sure parent exists and then call Mkdir for path. i := len(path) for i > 0 && IsPathSeparator(path[i-1]) { // Skip trailing path separator. i-- } j := i for j > 0 && !IsPathSeparator(path[j-1]) { // Scan backward over element. j-- } if j > 1 { // Create parent. err = MkdirAll(fixRootDirectory(path[:j-1]), perm) if err != nil { return err } } // Parent now exists; invoke Mkdir and use its result. err = Mkdir(path, perm) if err != nil { // Handle arguments like "foo/." by // double-checking that directory doesn't exist. dir, err1 := Lstat(path) if err1 == nil && dir.IsDir() { return nil } return err } return nil } // RemoveAll removes path and any children it contains. // It removes everything it can but returns the first error // it encounters. If the path does not exist, RemoveAll // returns nil (no error). // If there is an error, it will be of type *PathError. func RemoveAll(path string) error { return removeAll(path) } // endsWithDot reports whether the final component of path is ".". func endsWithDot(path string) bool { if path == "." { return true } if len(path) >= 2 && path[len(path)-1] == '.' && IsPathSeparator(path[len(path)-2]) { return true } return false } ================================================ FILE: src/os/path_test.go ================================================ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os_test import ( . "os" "path/filepath" "runtime" "testing" ) func TestMkdirAll(t *testing.T) { tmpDir := TempDir() path := tmpDir + "/_TestMkdirAll_/dir/./dir2" err := MkdirAll(path, 0777) if err != nil { t.Fatalf("MkdirAll %q: %s", path, err) } // TODO: revert to upstream code which uses RemoveAll defer Remove(tmpDir + "/_TestMkdirAll_/dir/dir2") defer Remove(tmpDir + "/_TestMkdirAll_/dir") defer Remove(tmpDir + "/_TestMkdirAll_") // Already exists, should succeed. err = MkdirAll(path, 0777) if err != nil { t.Fatalf("MkdirAll %q (second time): %s", path, err) } // Make file. fpath := path + "/file" f, err := Create(fpath) if err != nil { t.Fatalf("create %q: %s", fpath, err) } defer Remove(fpath) defer f.Close() // Can't make directory named after file. err = MkdirAll(fpath, 0777) if err == nil { t.Fatalf("MkdirAll %q: no error", fpath) } perr, ok := err.(*PathError) if !ok { t.Fatalf("MkdirAll %q returned %T, not *PathError", fpath, err) } if filepath.Clean(perr.Path) != filepath.Clean(fpath) { t.Fatalf("MkdirAll %q returned wrong error path: %q not %q", fpath, filepath.Clean(perr.Path), filepath.Clean(fpath)) } // Can't make subdirectory of file. ffpath := fpath + "/subdir" err = MkdirAll(ffpath, 0777) if err == nil { t.Fatalf("MkdirAll %q: no error", ffpath) } perr, ok = err.(*PathError) if !ok { t.Fatalf("MkdirAll %q returned %T, not *PathError", ffpath, err) } if filepath.Clean(perr.Path) != filepath.Clean(fpath) { t.Fatalf("MkdirAll %q returned wrong error path: %q not %q", ffpath, filepath.Clean(perr.Path), filepath.Clean(fpath)) } if runtime.GOOS == "windows" { path := tmpDir + `\_TestMkdirAll_\dir\.\dir2\` err := MkdirAll(path, 0777) if err != nil { t.Fatalf("MkdirAll %q: %s", path, err) } } } ================================================ FILE: src/os/path_unix.go ================================================ //go:build !windows // Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os const ( PathSeparator = '/' // PathSeparator is the OS-specific path separator PathListSeparator = ':' // PathListSeparator is the OS-specific path list separator ) // IsPathSeparator reports whether c is a directory separator character. func IsPathSeparator(c uint8) bool { return PathSeparator == c } // basename removes trailing slashes and the leading directory name from path name. func basename(name string) string { i := len(name) - 1 // Remove trailing slashes for ; i > 0 && name[i] == '/'; i-- { name = name[:i] } // Remove leading directory name for i--; i >= 0; i-- { if name[i] == '/' { name = name[i+1:] break } } return name } func fixRootDirectory(p string) string { return p } ================================================ FILE: src/os/path_windows.go ================================================ // Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os const ( PathSeparator = '\\' // PathSeparator is the OS-specific path separator PathListSeparator = ';' // PathListSeparator is the OS-specific path list separator ) // IsPathSeparator reports whether c is a directory separator character. func IsPathSeparator(c uint8) bool { // NOTE: Windows accept / as path separator. return c == '\\' || c == '/' } // basename removes trailing slashes and the leading // directory name and drive letter from path name. func basename(name string) string { // Remove drive letter if len(name) == 2 && name[1] == ':' { name = "." } else if len(name) > 2 && name[1] == ':' { name = name[2:] } i := len(name) - 1 // Remove trailing slashes for ; i > 0 && (name[i] == '/' || name[i] == '\\'); i-- { name = name[:i] } // Remove leading directory name for i--; i >= 0; i-- { if name[i] == '/' || name[i] == '\\' { name = name[i+1:] break } } return name } func isAbs(path string) (b bool) { v := volumeName(path) if v == "" { return false } path = path[len(v):] if path == "" { return false } return IsPathSeparator(path[0]) } func volumeName(path string) (v string) { if len(path) < 2 { return "" } // with drive letter c := path[0] if path[1] == ':' && ('0' <= c && c <= '9' || 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') { return path[:2] } // is it UNC if l := len(path); l >= 5 && IsPathSeparator(path[0]) && IsPathSeparator(path[1]) && !IsPathSeparator(path[2]) && path[2] != '.' { // first, leading `\\` and next shouldn't be `\`. its server name. for n := 3; n < l-1; n++ { // second, next '\' shouldn't be repeated. if IsPathSeparator(path[n]) { n++ // third, following something characters. its share name. if !IsPathSeparator(path[n]) { if path[n] == '.' { break } for ; n < l; n++ { if IsPathSeparator(path[n]) { break } } return path[:n] } break } } } return "" } func fromSlash(path string) string { // Replace each '/' with '\\' if present var pathbuf []byte var lastSlash int for i, b := range path { if b == '/' { if pathbuf == nil { pathbuf = make([]byte, len(path)) } copy(pathbuf[lastSlash:], path[lastSlash:i]) pathbuf[i] = '\\' lastSlash = i + 1 } } if pathbuf == nil { return path } copy(pathbuf[lastSlash:], path[lastSlash:]) return string(pathbuf) } func dirname(path string) string { vol := volumeName(path) i := len(path) - 1 for i >= len(vol) && !IsPathSeparator(path[i]) { i-- } dir := path[len(vol) : i+1] last := len(dir) - 1 if last > 0 && IsPathSeparator(dir[last]) { dir = dir[:last] } if dir == "" { dir = "." } return vol + dir } // This is set via go:linkname on runtime.canUseLongPaths, and is true when the OS // supports opting into proper long path handling without the need for fixups. var canUseLongPaths bool // fixLongPath returns the extended-length (\\?\-prefixed) form of // path when needed, in order to avoid the default 260 character file // path limit imposed by Windows. If path is not easily converted to // the extended-length form (for example, if path is a relative path // or contains .. elements), or is short enough, fixLongPath returns // path unmodified. // // See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath func fixLongPath(path string) string { if canUseLongPaths { return path } // Do nothing (and don't allocate) if the path is "short". // Empirically (at least on the Windows Server 2013 builder), // the kernel is arbitrarily okay with < 248 bytes. That // matches what the docs above say: // "When using an API to create a directory, the specified // path cannot be so long that you cannot append an 8.3 file // name (that is, the directory name cannot exceed MAX_PATH // minus 12)." Since MAX_PATH is 260, 260 - 12 = 248. // // The MSDN docs appear to say that a normal path that is 248 bytes long // will work; empirically the path must be less then 248 bytes long. if len(path) < 248 { // Don't fix. (This is how Go 1.7 and earlier worked, // not automatically generating the \\?\ form) return path } // The extended form begins with \\?\, as in // \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt. // The extended form disables evaluation of . and .. path // elements and disables the interpretation of / as equivalent // to \. The conversion here rewrites / to \ and elides // . elements as well as trailing or duplicate separators. For // simplicity it avoids the conversion entirely for relative // paths or paths containing .. elements. For now, // \\server\share paths are not converted to // \\?\UNC\server\share paths because the rules for doing so // are less well-specified. if len(path) >= 2 && path[:2] == `\\` { // Don't canonicalize UNC paths. return path } if !isAbs(path) { // Relative path return path } const prefix = `\\?` pathbuf := make([]byte, len(prefix)+len(path)+len(`\`)) copy(pathbuf, prefix) n := len(path) r, w := 0, len(prefix) for r < n { switch { case IsPathSeparator(path[r]): // empty block r++ case path[r] == '.' && (r+1 == n || IsPathSeparator(path[r+1])): // /./ r++ case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || IsPathSeparator(path[r+2])): // /../ is currently unhandled return path default: pathbuf[w] = '\\' w++ for ; r < n && !IsPathSeparator(path[r]); r++ { pathbuf[w] = path[r] w++ } } } // A drive's root directory needs a trailing \ if w == len(`\\?\c:`) { pathbuf[w] = '\\' w++ } return string(pathbuf[:w]) } // fixRootDirectory fixes a reference to a drive's root directory to // have the required trailing slash. func fixRootDirectory(p string) string { if len(p) == len(`\\?\c:`) { if IsPathSeparator(p[0]) && IsPathSeparator(p[1]) && p[2] == '?' && IsPathSeparator(p[3]) && p[5] == ':' { return p + `\` } } return p } ================================================ FILE: src/os/path_windows_test.go ================================================ // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os_test import ( "os" "strings" "syscall" "testing" ) func TestFixLongPath(t *testing.T) { if os.CanUseLongPaths { return } // 248 is long enough to trigger the longer-than-248 checks in // fixLongPath, but short enough not to make a path component // longer than 255, which is illegal on Windows. (which // doesn't really matter anyway, since this is purely a string // function we're testing, and it's not actually being used to // do a system call) veryLong := "l" + strings.Repeat("o", 248) + "ng" for _, test := range []struct{ in, want string }{ // Short; unchanged: {`C:\short.txt`, `C:\short.txt`}, {`C:\`, `C:\`}, {`C:`, `C:`}, // The "long" substring is replaced by a looooooong // string which triggers the rewriting. Except in the // cases below where it doesn't. {`C:\long\foo.txt`, `\\?\C:\long\foo.txt`}, {`C:/long/foo.txt`, `\\?\C:\long\foo.txt`}, {`C:\long\foo\\bar\.\baz\\`, `\\?\C:\long\foo\bar\baz`}, {`\\unc\path`, `\\unc\path`}, {`long.txt`, `long.txt`}, {`C:long.txt`, `C:long.txt`}, {`c:\long\..\bar\baz`, `c:\long\..\bar\baz`}, {`\\?\c:\long\foo.txt`, `\\?\c:\long\foo.txt`}, {`\\?\c:\long/foo.txt`, `\\?\c:\long/foo.txt`}, } { in := strings.ReplaceAll(test.in, "long", veryLong) want := strings.ReplaceAll(test.want, "long", veryLong) if got := os.FixLongPath(in); got != want { got = strings.ReplaceAll(got, veryLong, "long") t.Errorf("fixLongPath(%q) = %q; want %q", test.in, got, test.want) } } } // TODO: bring back upstream version's TestMkdirAllLongPath once os.RemoveAll and t.TempDir implemented // isWine returns true if executing on wine (Wine Is Not an Emulator), which // is compatible with windows but does not reproduce all its quirks. func isWine() bool { return os.Getenv("WINECONFIGDIR") != "" } func TestMkdirAllExtendedLength(t *testing.T) { // TODO: revert to upstream version once os.RemoveAll and t.TempDir implemented tmpDir := os.TempDir() const prefix = `\\?\` if len(tmpDir) < 4 || tmpDir[:4] != prefix { fullPath, err := syscall.FullPath(tmpDir) if err != nil { t.Fatalf("FullPath(%q) fails: %v", tmpDir, err) } tmpDir = prefix + fullPath } path := tmpDir + `\dir\` if err := os.MkdirAll(path, 0777); err != nil { t.Fatalf("MkdirAll(%q) failed: %v", path, err) } if isWine() { // TODO: use t.Skip once implemented t.Log("wine: Skipping check for no-dots-for-you quirk in windows extended paths") return } path = path + `.\dir2` if err := os.MkdirAll(path, 0777); err == nil { t.Fatalf("MkdirAll(%q) should have failed, but did not", path) } } ================================================ FILE: src/os/pipe_test.go ================================================ //go:build windows || darwin || (linux && !baremetal && !wasip1 && !wasip2) // Copyright 2021 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Test pipes on Unix and Windows systems. package os_test import ( "bytes" "os" "testing" ) // TestSmokePipe is a simple smoke test for Pipe(). func TestSmokePipe(t *testing.T) { // Procedure: // 1. Get the bytes // 2. Light the bytes on fire // 3. Smoke the bytes r, w, err := os.Pipe() if err != nil { t.Fatal(err) return // TODO: remove once Fatal is fatal } defer r.Close() defer w.Close() msg := []byte("Sed nvlla nisi ardva virtvs") n, err := w.Write(msg) if err != nil { t.Errorf("Writing to fresh pipe failed, error %v", err) } want := len(msg) if n != want { t.Errorf("Writing to fresh pipe wrote %d bytes, expected %d", n, want) } buf := make([]byte, 2*len(msg)) n, err = r.Read(buf) if err != nil { t.Errorf("Reading from pipe failed, error %v", err) } if n != want { t.Errorf("Reading from pipe got %d bytes, expected %d", n, want) } // Read() does not set len(buf), so do it here. buf = buf[:n] if !bytes.Equal(buf, msg) { t.Errorf("Reading from fresh pipe got wrong bytes") } } ================================================ FILE: src/os/proc.go ================================================ // Package os implements a subset of the Go "os" package. See // https://godoc.org/os for details. // // Note that the current implementation is blocking. This limitation should be // removed in a future version. package os import ( "syscall" ) // Args hold the command-line arguments, starting with the program name. var Args []string func init() { Args = runtime_args() } func runtime_args() []string // in package runtime // Exit causes the current program to exit with the given status code. // Conventionally, code zero indicates success, non-zero an error. // The program terminates immediately; deferred functions are not run. func Exit(code int) { syscall.Exit(code) } // Getuid returns the numeric user id of the caller. // // On non-POSIX systems, it returns -1. func Getuid() int { return syscall.Getuid() } // Geteuid returns the numeric effective user id of the caller. // // On non-POSIX systems, it returns -1. func Geteuid() int { return syscall.Geteuid() } // Getgid returns the numeric group id of the caller. // // On non-POSIX systems, it returns -1. func Getgid() int { return syscall.Getgid() } // Getegid returns the numeric effective group id of the caller. // // On non-POSIX systems, it returns -1. func Getegid() int { return syscall.Getegid() } ================================================ FILE: src/os/read_test.go ================================================ //go:build !baremetal && !js && !wasip1 && !wasip2 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os_test import ( "bytes" . "os" "path/filepath" "testing" ) func checkNamedSize(t *testing.T, path string, size int64) { // TODO: this statement fails on wasi, possibly it objects to reading Stat on a symlink dir, err := Stat(path) if err != nil { t.Fatalf("Stat %q (looking for size %d): %s", path, size, err) return } if dir.Size() != size { t.Errorf("Stat %q: size %d want %d", path, dir.Size(), size) } } func TestReadFile(t *testing.T) { filename := "rumpelstilzchen" contents, err := ReadFile(filename) if err == nil { t.Fatalf("ReadFile %s: error expected, none found", filename) } filename = "read_test.go" contents, err = ReadFile(filename) if err != nil { t.Fatalf("ReadFile %s: %v", filename, err) } checkNamedSize(t, filename, int64(len(contents))) } func TestWriteFile(t *testing.T) { f, err := CreateTemp("", "ioutil-test") if err != nil { t.Fatal(err) } defer f.Close() defer Remove(f.Name()) msg := "Programming today is a race between software engineers striving to " + "build bigger and better idiot-proof programs, and the Universe trying " + "to produce bigger and better idiots. So far, the Universe is winning." if err := WriteFile(f.Name(), []byte(msg), 0644); err != nil { t.Fatalf("WriteFile %s: %v", f.Name(), err) } data, err := ReadFile(f.Name()) if err != nil { t.Fatalf("ReadFile %s: %v", f.Name(), err) } if string(data) != msg { t.Fatalf("ReadFile: wrong data:\nhave %q\nwant %q", string(data), msg) } } func TestReadOnlyWriteFile(t *testing.T) { // TODO: also skip on wasi, where file permissions are ignored if Getuid() == 0 { t.Skipf("Root can write to read-only files anyway, so skip the read-only test.") } // We don't want to use CreateTemp directly, since that opens a file for us as 0600. tempDir, err := MkdirTemp("", t.Name()) if err != nil { t.Fatal(err) } defer RemoveAll(tempDir) filename := filepath.Join(tempDir, "blurp.txt") shmorp := []byte("shmorp") florp := []byte("florp") err = WriteFile(filename, shmorp, 0444) if err != nil { t.Fatalf("WriteFile %s: %v", filename, err) } err = WriteFile(filename, florp, 0444) if err == nil { t.Fatalf("Expected an error when writing to read-only file %s", filename) } got, err := ReadFile(filename) if err != nil { t.Fatalf("ReadFile %s: %v", filename, err) } if !bytes.Equal(got, shmorp) { t.Fatalf("want %s, got %s", shmorp, got) } } ================================================ FILE: src/os/removeall_noat.go ================================================ // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build !baremetal && !js && !wasip1 && !wasip2 && !wasm_unknown && !nintendoswitch package os import ( "io" "runtime" "syscall" ) func removeAll(path string) error { if path == "" { // fail silently to retain compatibility with previous behavior // of RemoveAll. See issue 28830. return nil } // The rmdir system call permits removing "." on Plan 9, // so we don't permit it to remain consistent with the // "at" implementation of RemoveAll. if endsWithDot(path) { return &PathError{Op: "RemoveAll", Path: path, Err: syscall.EINVAL} } // Simple case: if Remove works, we're done. err := Remove(path) if err == nil || IsNotExist(err) { return nil } // Otherwise, is this a directory we need to recurse into? dir, serr := Lstat(path) if serr != nil { if serr, ok := serr.(*PathError); ok && (IsNotExist(serr.Err) || serr.Err == syscall.ENOTDIR) { return nil } return serr } if !dir.IsDir() { // Not a directory; return the error from Remove. return err } // Remove contents & return first error. err = nil for { fd, err := Open(path) if err != nil { if IsNotExist(err) { // Already deleted by someone else. return nil } return err } const reqSize = 1024 var names []string var readErr error for { numErr := 0 names, readErr = fd.Readdirnames(reqSize) for _, name := range names { err1 := RemoveAll(path + string(PathSeparator) + name) if err == nil { err = err1 } if err1 != nil { numErr++ } } // If we can delete any entry, break to start new iteration. // Otherwise, we discard current names, get next entries and try deleting them. if numErr != reqSize { break } } // Removing files from the directory may have caused // the OS to reshuffle it. Simply calling Readdirnames // again may skip some entries. The only reliable way // to avoid this is to close and re-open the // directory. See issue 20841. fd.Close() if readErr == io.EOF { break } // If Readdirnames returned an error, use it. if err == nil { err = readErr } if len(names) == 0 { break } // We don't want to re-open unnecessarily, so if we // got fewer than request names from Readdirnames, try // simply removing the directory now. If that // succeeds, we are done. if len(names) < reqSize { err1 := Remove(path) if err1 == nil || IsNotExist(err1) { return nil } if err != nil { // We got some error removing the // directory contents, and since we // read fewer names than we requested // there probably aren't more files to // remove. Don't loop around to read // the directory again. We'll probably // just get the same error. return err } } } // Remove directory. err1 := Remove(path) if err1 == nil || IsNotExist(err1) { return nil } if runtime.GOOS == "windows" && IsPermission(err1) { if fs, err := Stat(path); err == nil { if err = Chmod(path, FileMode(0200|int(fs.Mode()))); err == nil { err1 = Remove(path) } } } if err == nil { err = err1 } return err } ================================================ FILE: src/os/removeall_other.go ================================================ //go:build baremetal || js || wasip1 || wasip2 || wasm_unknown || nintendoswitch // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os import ( "syscall" ) func removeAll(path string) error { return &PathError{Op: "RemoveAll", Path: path, Err: syscall.ENOSYS} } ================================================ FILE: src/os/removeall_test.go ================================================ //go:build darwin || (linux && !baremetal && !js && !wasip1 && !wasip2) // TODO: implement ReadDir on windows // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os_test import ( "fmt" "os" . "os" "path/filepath" "runtime" "testing" ) func TestRemoveAll(t *testing.T) { tmpDir, _ := os.MkdirTemp("", "TestRemoveAll") if err := RemoveAll(""); err != nil { t.Errorf("RemoveAll(\"\"): %v; want nil", err) } file := filepath.Join(tmpDir, "file") path := filepath.Join(tmpDir, "_TestRemoveAll_") fpath := filepath.Join(path, "file") dpath := filepath.Join(path, "dir") // Make a regular file and remove fd, err := Create(file) if err != nil { t.Fatalf("create %q: %s", file, err) } fd.Close() if err = RemoveAll(file); err != nil { t.Fatalf("RemoveAll %q (first): %s", file, err) } if _, err = Lstat(file); err == nil { t.Fatalf("Lstat %q succeeded after RemoveAll (first)", file) } // Make directory with 1 file and remove. if err := MkdirAll(path, 0777); err != nil { t.Fatalf("MkdirAll %q: %s", path, err) } fd, err = Create(fpath) if err != nil { t.Fatalf("create %q: %s", fpath, err) } fd.Close() if err = RemoveAll(path); err != nil { t.Fatalf("RemoveAll %q (second): %s", path, err) } if _, err = Lstat(path); err == nil { t.Fatalf("Lstat %q succeeded after RemoveAll (second)", path) } // Make directory with file and subdirectory and remove. if err = MkdirAll(dpath, 0777); err != nil { t.Fatalf("MkdirAll %q: %s", dpath, err) } fd, err = Create(fpath) if err != nil { t.Fatalf("create %q: %s", fpath, err) } fd.Close() fd, err = Create(dpath + "/file") if err != nil { t.Fatalf("create %q: %s", fpath, err) } fd.Close() if err = RemoveAll(path); err != nil { t.Fatalf("RemoveAll %q (third): %s", path, err) } if _, err := Lstat(path); err == nil { t.Fatalf("Lstat %q succeeded after RemoveAll (third)", path) } // Chmod is not supported under Windows and test fails as root. if runtime.GOOS != "windows" && Getuid() != 0 { // Make directory with file and subdirectory and trigger error. if err = MkdirAll(dpath, 0777); err != nil { t.Fatalf("MkdirAll %q: %s", dpath, err) } for _, s := range []string{fpath, dpath + "/file1", path + "/zzz"} { fd, err = Create(s) if err != nil { t.Fatalf("create %q: %s", s, err) } fd.Close() } if err = Chmod(dpath, 0); err != nil { t.Fatalf("Chmod %q 0: %s", dpath, err) } // No error checking here: either RemoveAll // will or won't be able to remove dpath; // either way we want to see if it removes fpath // and path/zzz. Reasons why RemoveAll might // succeed in removing dpath as well include: // * running as root // * running on a file system without permissions (FAT) RemoveAll(path) Chmod(dpath, 0777) for _, s := range []string{fpath, path + "/zzz"} { if _, err = Lstat(s); err == nil { t.Fatalf("Lstat %q succeeded after partial RemoveAll", s) } } } if err = RemoveAll(path); err != nil { t.Fatalf("RemoveAll %q after partial RemoveAll: %s", path, err) } if _, err = Lstat(path); err == nil { t.Fatalf("Lstat %q succeeded after RemoveAll (final)", path) } } // Test RemoveAll on a large directory. func TestRemoveAllLarge(t *testing.T) { if testing.Short() { t.Skip("skipping in short mode") } tmpDir, _ := os.MkdirTemp("", "TestRemoveAllLarge") path := filepath.Join(tmpDir, "_TestRemoveAllLarge_") // Make directory with 1000 files and remove. if err := MkdirAll(path, 0777); err != nil { t.Fatalf("MkdirAll %q: %s", path, err) } for i := 0; i < 1000; i++ { fpath := fmt.Sprintf("%s/file%d", path, i) fd, err := Create(fpath) if err != nil { t.Fatalf("create %q: %s", fpath, err) } fd.Close() } if err := RemoveAll(path); err != nil { t.Fatalf("RemoveAll %q: %s", path, err) } if _, err := Lstat(path); err == nil { t.Fatalf("Lstat %q succeeded after RemoveAll", path) } } func TestRemoveAllDot(t *testing.T) { prevDir, err := Getwd() if err != nil { t.Fatalf("Could not get wd: %s", err) } tempDir, err := os.MkdirTemp("", "TestRemoveAllDot-") if err != nil { t.Fatalf("Could not create TempDir: %s", err) } defer RemoveAll(tempDir) err = Chdir(tempDir) if err != nil { t.Fatalf("Could not chdir to tempdir: %s", err) } err = RemoveAll(".") if err == nil { t.Errorf("RemoveAll succeed to remove .") } err = Chdir(prevDir) if err != nil { t.Fatalf("Could not chdir %s: %s", prevDir, err) } } func TestRemoveAllDotDot(t *testing.T) { t.Parallel() tempDir, _ := os.MkdirTemp("", "TestRemoveAllDotDot") subdir := filepath.Join(tempDir, "x") subsubdir := filepath.Join(subdir, "y") if err := MkdirAll(subsubdir, 0777); err != nil { t.Fatal(err) } if err := RemoveAll(filepath.Join(subsubdir, "..")); err != nil { t.Error(err) } for _, dir := range []string{subsubdir, subdir} { if _, err := Stat(dir); err == nil { t.Errorf("%s: exists after RemoveAll", dir) } } } // Issue #29178. func TestRemoveReadOnlyDir(t *testing.T) { t.Parallel() tempDir, _ := os.MkdirTemp("", "TestRemoveReadOnlyDir") subdir := filepath.Join(tempDir, "x") if err := Mkdir(subdir, 0); err != nil { t.Fatal(err) } // If an error occurs make it more likely that removing the // temporary directory will succeed. defer Chmod(subdir, 0777) if err := RemoveAll(subdir); err != nil { t.Fatal(err) } if _, err := Stat(subdir); err == nil { t.Error("subdirectory was not removed") } } // Issue #29983. func TestRemoveAllButReadOnlyAndPathError(t *testing.T) { switch runtime.GOOS { case "js", "windows": t.Skipf("skipping test on %s", runtime.GOOS) } if Getuid() == 0 { t.Skip("skipping test when running as root") } t.Parallel() tempDir, _ := os.MkdirTemp("", "TestRemoveAllButReadOnlyAndPathError") dirs := []string{ "a", "a/x", "a/x/1", "b", "b/y", "b/y/2", "c", "c/z", "c/z/3", } readonly := []string{ "b", } inReadonly := func(d string) bool { for _, ro := range readonly { if d == ro { return true } dd, _ := filepath.Split(d) if filepath.Clean(dd) == ro { return true } } return false } for _, dir := range dirs { if err := Mkdir(filepath.Join(tempDir, dir), 0777); err != nil { t.Fatal(err) } } for _, dir := range readonly { d := filepath.Join(tempDir, dir) if err := Chmod(d, 0555); err != nil { t.Fatal(err) } // Defer changing the mode back so that the deferred // RemoveAll(tempDir) can succeed. defer Chmod(d, 0777) } err := RemoveAll(tempDir) if err == nil { t.Fatal("RemoveAll succeeded unexpectedly") } // The error should be of type *PathError. // see issue 30491 for details. if pathErr, ok := err.(*PathError); ok { want := filepath.Join(tempDir, "b", "y") if pathErr.Path != want { t.Errorf("RemoveAll(%q): err.Path=%q, want %q", tempDir, pathErr.Path, want) } } else { t.Errorf("RemoveAll(%q): error has type %T, want *fs.PathError", tempDir, err) } for _, dir := range dirs { _, err := Stat(filepath.Join(tempDir, dir)) if inReadonly(dir) { if err != nil { t.Errorf("file %q was deleted but should still exist", dir) } } else { if err == nil { t.Errorf("file %q still exists but should have been deleted", dir) } } } } func TestRemoveUnreadableDir(t *testing.T) { switch runtime.GOOS { case "js": t.Skipf("skipping test on %s", runtime.GOOS) } if Getuid() == 0 { t.Skip("skipping test when running as root") } t.Parallel() tempDir, _ := os.MkdirTemp("", "TestRemoveUnreadableDir") target := filepath.Join(tempDir, "d0", "d1", "d2") if err := MkdirAll(target, 0755); err != nil { t.Fatal(err) } if err := Chmod(target, 0300); err != nil { t.Fatal(err) } if err := RemoveAll(filepath.Join(tempDir, "d0")); err != nil { t.Fatal(err) } } // Issue 29921 func TestRemoveAllWithMoreErrorThanReqSize(t *testing.T) { if testing.Short() { t.Skip("skipping in short mode") } tmpDir, _ := os.MkdirTemp("", "TestRemoveAllWithMoreErrorThanReqSize") path := filepath.Join(tmpDir, "_TestRemoveAllWithMoreErrorThanReqSize_") // Make directory with 1025 read-only files. if err := MkdirAll(path, 0777); err != nil { t.Fatalf("MkdirAll %q: %s", path, err) } for i := 0; i < 1025; i++ { fpath := filepath.Join(path, fmt.Sprintf("file%d", i)) fd, err := Create(fpath) if err != nil { t.Fatalf("create %q: %s", fpath, err) } fd.Close() } // Make the parent directory read-only. On some platforms, this is what // prevents os.Remove from removing the files within that directory. if err := Chmod(path, 0555); err != nil { t.Fatal(err) } defer Chmod(path, 0755) // This call should not hang, even on a platform that disallows file deletion // from read-only directories. err := RemoveAll(path) if Getuid() == 0 { // On many platforms, root can remove files from read-only directories. return } if err == nil { if runtime.GOOS == "windows" { // Marking a directory as read-only in Windows does not prevent the RemoveAll // from creating or removing files within it. return } t.Fatal("RemoveAll() = nil; want error") } dir, err := Open(path) if err != nil { t.Fatal(err) } defer dir.Close() names, _ := dir.Readdirnames(1025) if len(names) < 1025 { t.Fatalf("RemoveAll() unexpectedly removed %d read-only files from that directory", 1025-len(names)) } } ================================================ FILE: src/os/seek_unix_bad.go ================================================ //go:build (linux && !baremetal && 386) || (linux && !baremetal && arm && !wasip1 && !wasip2) package os import ( "syscall" ) // On linux, we use upstream's syscall package. // But we do not yet implement Go Assembly, so we don't see a few functions written in assembly there. // In particular, on i386 and arm, the function syscall.seek is missing, breaking syscall.Seek. // This in turn causes os.(*File).Seek, time, io/fs, and path/filepath to fail to link. // // To temporarily let all the above at least link, provide a stub for syscall.seek. // This belongs in syscall, but on linux, we use upstream's syscall. // Remove once we support Go Assembly. // TODO: make this a non-stub, and thus fix the whole problem? //export syscall.seek func seek(fd int, offset int64, whence int) (newoffset int64, err syscall.Errno) { return 0, syscall.ENOTSUP } ================================================ FILE: src/os/stat.go ================================================ // Copyright 2017 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os // Stat returns a FileInfo describing the named file. // If there is an error, it will be of type *PathError. func Stat(name string) (FileInfo, error) { return statNolog(name) } // Lstat returns a FileInfo describing the named file. // If the file is a symbolic link, the returned FileInfo // describes the symbolic link. Lstat makes no attempt to follow the link. // If there is an error, it will be of type *PathError. func Lstat(name string) (FileInfo, error) { return lstatNolog(name) } ================================================ FILE: src/os/stat_darwin.go ================================================ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os import ( "syscall" "time" ) func fillFileStatFromSys(fs *fileStat, name string) { fs.name = basename(name) fs.size = fs.sys.Size fs.modTime = timespecToTime(fs.sys.Mtimespec) fs.mode = FileMode(fs.sys.Mode & 0777) switch fs.sys.Mode & syscall.S_IFMT { case syscall.S_IFBLK, syscall.S_IFWHT: fs.mode |= ModeDevice case syscall.S_IFCHR: fs.mode |= ModeDevice | ModeCharDevice case syscall.S_IFDIR: fs.mode |= ModeDir case syscall.S_IFIFO: fs.mode |= ModeNamedPipe case syscall.S_IFLNK: fs.mode |= ModeSymlink case syscall.S_IFREG: // nothing to do case syscall.S_IFSOCK: fs.mode |= ModeSocket } if fs.sys.Mode&syscall.S_ISGID != 0 { fs.mode |= ModeSetgid } if fs.sys.Mode&syscall.S_ISUID != 0 { fs.mode |= ModeSetuid } if fs.sys.Mode&syscall.S_ISVTX != 0 { fs.mode |= ModeSticky } } func timespecToTime(ts syscall.Timespec) time.Time { return time.Unix(int64(ts.Sec), int64(ts.Nsec)) } // For testing. func atime(fi FileInfo) time.Time { return timespecToTime(fi.Sys().(*syscall.Stat_t).Atimespec) } ================================================ FILE: src/os/stat_linuxlike.go ================================================ //go:build (linux && !baremetal && !wasm_unknown && !nintendoswitch) || wasip1 || wasip2 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Note: this file is used for both Linux and WASI. // Eventually it might be better to spit it up, and make the syscall constants // match the typical WASI constants instead of the Linux-equivalents used here. package os import ( "syscall" "time" ) func fillFileStatFromSys(fs *fileStat, name string) { fs.name = basename(name) fs.size = fs.sys.Size fs.modTime = timespecToTime(fs.sys.Mtim) fs.mode = FileMode(fs.sys.Mode & 0777) switch fs.sys.Mode & syscall.S_IFMT { case syscall.S_IFBLK: fs.mode |= ModeDevice case syscall.S_IFCHR: fs.mode |= ModeDevice | ModeCharDevice case syscall.S_IFDIR: fs.mode |= ModeDir case syscall.S_IFIFO: fs.mode |= ModeNamedPipe case syscall.S_IFLNK: fs.mode |= ModeSymlink case syscall.S_IFREG: // nothing to do case syscall.S_IFSOCK: fs.mode |= ModeSocket } if fs.sys.Mode&syscall.S_ISGID != 0 { fs.mode |= ModeSetgid } if fs.sys.Mode&syscall.S_ISUID != 0 { fs.mode |= ModeSetuid } if fs.sys.Mode&syscall.S_ISVTX != 0 { fs.mode |= ModeSticky } } func timespecToTime(ts syscall.Timespec) time.Time { return time.Unix(int64(ts.Sec), int64(ts.Nsec)) } // For testing. func atime(fi FileInfo) time.Time { return timespecToTime(fi.Sys().(*syscall.Stat_t).Atim) } ================================================ FILE: src/os/stat_other.go ================================================ //go:build baremetal || (tinygo.wasm && !wasip1 && !wasip2) || nintendoswitch // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os // Stat is a stub, not yet implemented func (f *File) Stat() (FileInfo, error) { return nil, ErrNotImplemented } // statNolog stats a file with no test logging. func statNolog(name string) (FileInfo, error) { return nil, &PathError{Op: "stat", Path: name, Err: ErrNotImplemented} } // lstatNolog lstats a file with no test logging. func lstatNolog(name string) (FileInfo, error) { return nil, &PathError{Op: "lstat", Path: name, Err: ErrNotImplemented} } ================================================ FILE: src/os/stat_test.go ================================================ // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os_test import ( "io/fs" "os" "path/filepath" "testing" ) // testStatAndLstat verifies that all os.Stat, os.Lstat os.File.Stat and os.Readdir work. func testStatAndLstat(t *testing.T, path string, isLink bool, statCheck, lstatCheck func(*testing.T, string, fs.FileInfo)) { // TODO: revert to upstream test once fstat and readdir are implemented // test os.Stat sfi, err := os.Stat(path) if err != nil { t.Error(err) return } statCheck(t, path, sfi) // test os.Lstat lsfi, err := os.Lstat(path) if err != nil { t.Error(err) return } lstatCheck(t, path, lsfi) if isLink { if os.SameFile(sfi, lsfi) { t.Errorf("stat and lstat of %q should not be the same", path) } } else { if !os.SameFile(sfi, lsfi) { t.Errorf("stat and lstat of %q should be the same", path) } } } // testIsDir verifies that fi refers to directory. func testIsDir(t *testing.T, path string, fi fs.FileInfo) { t.Helper() if !fi.IsDir() { t.Errorf("%q should be a directory", path) } if fi.Mode()&fs.ModeSymlink != 0 { t.Errorf("%q should not be a symlink", path) } } // testIsFile verifies that fi refers to file. func testIsFile(t *testing.T, path string, fi fs.FileInfo) { t.Helper() if fi.IsDir() { t.Errorf("%q should not be a directory", path) } if fi.Mode()&fs.ModeSymlink != 0 { t.Errorf("%q should not be a symlink", path) } } func testDirStats(t *testing.T, path string) { testStatAndLstat(t, path, false, testIsDir, testIsDir) } func testFileStats(t *testing.T, path string) { testStatAndLstat(t, path, false, testIsFile, testIsFile) } func TestDirAndSymlinkStats(t *testing.T) { // TODO: revert to upstream test once symlinks and t.TempDir are implemented tmpdir := os.TempDir() dir := filepath.Join(tmpdir, "dir") os.Remove(dir) if err := os.Mkdir(dir, 0777); err != nil { t.Fatal(err) return } testDirStats(t, dir) } func TestFileAndSymlinkStats(t *testing.T) { // TODO: revert to upstream test once symlinks and t.TempDir are implemented tmpdir := os.TempDir() file := filepath.Join(tmpdir, "file") if err := os.WriteFile(file, []byte("abcdefg"), 0644); err != nil { t.Fatal(err) return } testFileStats(t, file) } ================================================ FILE: src/os/stat_unix.go ================================================ //go:build darwin || (linux && !baremetal && !wasm_unknown && !nintendoswitch) || wasip1 || wasip2 // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os import ( "syscall" ) // Stat returns the FileInfo structure describing file. // If there is an error, it will be of type *PathError. func (f *File) Stat() (FileInfo, error) { var fs fileStat err := ignoringEINTR(func() error { return syscall.Fstat(int(f.handle.(unixFileHandle)), &fs.sys) }) if err != nil { return nil, &PathError{Op: "fstat", Path: f.name, Err: err} } fillFileStatFromSys(&fs, f.name) return &fs, nil } // statNolog stats a file with no test logging. func statNolog(name string) (FileInfo, error) { var fs fileStat err := ignoringEINTR(func() error { return handleSyscallError(syscall.Stat(name, &fs.sys)) }) if err != nil { return nil, &PathError{Op: "stat", Path: name, Err: err} } fillFileStatFromSys(&fs, name) return &fs, nil } // lstatNolog lstats a file with no test logging. func lstatNolog(name string) (FileInfo, error) { var fs fileStat err := ignoringEINTR(func() error { return handleSyscallError(syscall.Lstat(name, &fs.sys)) }) if err != nil { return nil, &PathError{Op: "lstat", Path: name, Err: err} } fillFileStatFromSys(&fs, name) return &fs, nil } ================================================ FILE: src/os/stat_windows.go ================================================ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os import ( "internal/syscall/windows" "syscall" "unsafe" ) // Stat returns the FileInfo structure describing file. // If there is an error, it will be of type *PathError. func (file *File) Stat() (FileInfo, error) { if file == nil { return nil, ErrInvalid } if isWindowsNulName(file.name) { return &devNullStat, nil } ft, err := syscall.GetFileType(syscallFd(file.handle.(unixFileHandle))) if err != nil { return nil, &PathError{Op: "GetFileType", Path: file.name, Err: err} } switch ft { case syscall.FILE_TYPE_PIPE, syscall.FILE_TYPE_CHAR: return &fileStat{name: basename(file.name), filetype: ft}, nil } fs, err := newFileStatFromGetFileInformationByHandle(file.name, syscallFd(file.handle.(unixFileHandle))) if err != nil { return nil, err } fs.filetype = ft return fs, err } // stat implements both Stat and Lstat of a file. func stat(funcname, name string, createFileAttrs uint32) (FileInfo, error) { if len(name) == 0 { return nil, &PathError{Op: funcname, Path: name, Err: syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)} } if isWindowsNulName(name) { return &devNullStat, nil } namep, err := syscall.UTF16PtrFromString(fixLongPath(name)) if err != nil { return nil, &PathError{Op: funcname, Path: name, Err: err} } // Try GetFileAttributesEx first, because it is faster than CreateFile. // See https://golang.org/issues/19922#issuecomment-300031421 for details. var fa syscall.Win32FileAttributeData err = syscall.GetFileAttributesEx(namep, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa))) if err == nil && fa.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 { // Not a symlink. fs := &fileStat{ FileAttributes: fa.FileAttributes, CreationTime: fa.CreationTime, LastAccessTime: fa.LastAccessTime, LastWriteTime: fa.LastWriteTime, FileSizeHigh: fa.FileSizeHigh, FileSizeLow: fa.FileSizeLow, } if err := fs.saveInfoFromPath(name); err != nil { return nil, err } return fs, nil } // GetFileAttributesEx fails with ERROR_SHARING_VIOLATION error for // files, like c:\pagefile.sys. Use FindFirstFile for such files. if err == windows.ERROR_SHARING_VIOLATION { var fd syscall.Win32finddata sh, err := syscall.FindFirstFile(namep, &fd) if err != nil { return nil, &PathError{Op: "FindFirstFile", Path: name, Err: err} } syscall.FindClose(sh) fs := newFileStatFromWin32finddata(&fd) if err := fs.saveInfoFromPath(name); err != nil { return nil, err } return fs, nil } // Finally use CreateFile. h, err := syscall.CreateFile(namep, 0, 0, nil, syscall.OPEN_EXISTING, createFileAttrs, 0) if err != nil { return nil, &PathError{Op: "CreateFile", Path: name, Err: err} } defer syscall.CloseHandle(h) return newFileStatFromGetFileInformationByHandle(name, h) } // statNolog implements Stat for Windows. func statNolog(name string) (FileInfo, error) { return stat("Stat", name, syscall.FILE_FLAG_BACKUP_SEMANTICS) } // lstatNolog implements Lstat for Windows. func lstatNolog(name string) (FileInfo, error) { attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS) // Use FILE_FLAG_OPEN_REPARSE_POINT, otherwise CreateFile will follow symlink. // See https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT return stat("Lstat", name, attrs) } ================================================ FILE: src/os/sys.go ================================================ package os func Hostname() (name string, err error) { return "", ErrNotImplemented } ================================================ FILE: src/os/tempfile.go ================================================ // Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os import ( "errors" "internal/itoa" "sync" "time" ) var minrandPreviousValue uint32 var minrandMutex sync.Mutex func init() { // Avoid getting same results on every run now := time.Now() seed := uint32(Getpid()) ^ uint32(now.Nanosecond()) ^ uint32(now.Unix()) // initial state must be odd minrandPreviousValue = seed | 1 } // minrand() is a simple and rather poor placeholder for fastrand() // TODO: provide fastrand in runtime, as go does. It is hard to implement properly elsewhere. func minrand() uint32 { // c++11's minstd_rand // https://en.wikipedia.org/wiki/Linear_congruential_generator // m=2^32, c=0, a=48271 minrandMutex.Lock() minrandPreviousValue *= 48271 val := minrandPreviousValue minrandMutex.Unlock() return val } // We generate random temporary file names so that there's a good // chance the file doesn't exist yet - keeps the number of tries in // TempFile to a minimum. func nextRandom() string { // Discard lower four bits of minrand. // They're not very random, and we don't need the full range here. return itoa.Uitoa(uint(minrand() >> 4)) } // CreateTemp creates a new temporary file in the directory dir, // opens the file for reading and writing, and returns the resulting file. // The filename is generated by taking pattern and adding a random string to the end. // If pattern includes a "*", the random string replaces the last "*". // If dir is the empty string, CreateTemp uses the default directory for temporary files, as returned by TempDir. // Multiple programs or goroutines calling CreateTemp simultaneously will not choose the same file. // The caller can use the file's Name method to find the pathname of the file. // It is the caller's responsibility to remove the file when it is no longer needed. func CreateTemp(dir, pattern string) (*File, error) { if dir == "" { dir = TempDir() } prefix, suffix, err := prefixAndSuffix(pattern) if err != nil { return nil, &PathError{Op: "createtemp", Path: pattern, Err: err} } prefix = joinPath(dir, prefix) try := 0 for { name := prefix + nextRandom() + suffix f, err := OpenFile(name, O_RDWR|O_CREATE|O_EXCL, 0600) if IsExist(err) { if try++; try < 10000 { continue } return nil, &PathError{Op: "createtemp", Path: dir + string(PathSeparator) + prefix + "*" + suffix, Err: ErrExist} } return f, err } } var errPatternHasSeparator = errors.New("pattern contains path separator") // prefixAndSuffix splits pattern by the last wildcard "*", if applicable, // returning prefix as the part before "*" and suffix as the part after "*". func prefixAndSuffix(pattern string) (prefix, suffix string, err error) { for i := 0; i < len(pattern); i++ { if IsPathSeparator(pattern[i]) { return "", "", errPatternHasSeparator } } if pos := lastIndex(pattern, '*'); pos != -1 { prefix, suffix = pattern[:pos], pattern[pos+1:] } else { prefix = pattern } return prefix, suffix, nil } // MkdirTemp creates a new temporary directory in the directory dir // and returns the pathname of the new directory. // The new directory's name is generated by adding a random string to the end of pattern. // If pattern includes a "*", the random string replaces the last "*" instead. // If dir is the empty string, MkdirTemp uses the default directory for temporary files, as returned by TempDir. // Multiple programs or goroutines calling MkdirTemp simultaneously will not choose the same directory. // It is the caller's responsibility to remove the directory when it is no longer needed. func MkdirTemp(dir, pattern string) (string, error) { if dir == "" { dir = TempDir() } prefix, suffix, err := prefixAndSuffix(pattern) if err != nil { return "", &PathError{Op: "mkdirtemp", Path: pattern, Err: err} } prefix = joinPath(dir, prefix) try := 0 for { name := prefix + nextRandom() + suffix err := Mkdir(name, 0700) if err == nil { return name, nil } if IsExist(err) { if try++; try < 10000 { continue } return "", &PathError{Op: "mkdirtemp", Path: dir + string(PathSeparator) + prefix + "*" + suffix, Err: ErrExist} } if IsNotExist(err) { if _, err := Stat(dir); IsNotExist(err) { return "", err } } return "", err } } func joinPath(dir, name string) string { if len(dir) > 0 && IsPathSeparator(dir[len(dir)-1]) { return dir + name } return dir + string(PathSeparator) + name } // lastIndex from the strings package. func lastIndex(s string, sep byte) int { for i := len(s) - 1; i >= 0; i-- { if s[i] == sep { return i } } return -1 } ================================================ FILE: src/os/tempfile_test.go ================================================ // Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build !baremetal && !js && !wasip1 && !wasip2 package os_test import ( "errors" "io/fs" . "os" "path/filepath" "regexp" "runtime" "strings" "testing" ) func TestCreateTemp(t *testing.T) { dir, err := MkdirTemp("", "TestCreateTempBadDir") if err != nil { t.Fatal(err) } defer RemoveAll(dir) nonexistentDir := filepath.Join(dir, "_not_exists_") f, err := CreateTemp(nonexistentDir, "foo") if f != nil || err == nil { t.Errorf("CreateTemp(%q, `foo`) = %v, %v", nonexistentDir, f, err) } } func TestCreateTempPattern(t *testing.T) { tests := []struct{ pattern, prefix, suffix string }{ {"tempfile_test", "tempfile_test", ""}, {"tempfile_test*", "tempfile_test", ""}, {"tempfile_test*xyz", "tempfile_test", "xyz"}, } for _, test := range tests { f, err := CreateTemp("", test.pattern) if err != nil { t.Errorf("CreateTemp(..., %q) error: %v", test.pattern, err) continue } defer Remove(f.Name()) base := filepath.Base(f.Name()) f.Close() if !(strings.HasPrefix(base, test.prefix) && strings.HasSuffix(base, test.suffix)) { t.Errorf("CreateTemp pattern %q created bad name %q; want prefix %q & suffix %q", test.pattern, base, test.prefix, test.suffix) } } } func TestCreateTempBadPattern(t *testing.T) { tmpDir, err := MkdirTemp("", t.Name()) if err != nil { t.Fatal(err) } defer RemoveAll(tmpDir) const sep = string(PathSeparator) tests := []struct { pattern string wantErr bool }{ {"ioutil*test", false}, {"tempfile_test*foo", false}, {"tempfile_test" + sep + "foo", true}, {"tempfile_test*" + sep + "foo", true}, {"tempfile_test" + sep + "*foo", true}, {sep + "tempfile_test" + sep + "*foo", true}, {"tempfile_test*foo" + sep, true}, } for _, tt := range tests { t.Run(tt.pattern, func(t *testing.T) { tmpfile, err := CreateTemp(tmpDir, tt.pattern) if tmpfile != nil { defer tmpfile.Close() } if tt.wantErr { if err == nil { t.Errorf("CreateTemp(..., %#q) succeeded, expected error", tt.pattern) } if !errors.Is(err, ErrPatternHasSeparator) { t.Errorf("CreateTemp(..., %#q): %v, expected ErrPatternHasSeparator", tt.pattern, err) } } else if err != nil { t.Errorf("CreateTemp(..., %#q): %v", tt.pattern, err) } }) } } func TestMkdirTemp(t *testing.T) { name, err := MkdirTemp("/_not_exists_", "foo") if name != "" || err == nil { t.Errorf("MkdirTemp(`/_not_exists_`, `foo`) = %v, %v", name, err) } tests := []struct { pattern string wantPrefix, wantSuffix string }{ {"tempfile_test", "tempfile_test", ""}, {"tempfile_test*", "tempfile_test", ""}, {"tempfile_test*xyz", "tempfile_test", "xyz"}, } dir := filepath.Clean(TempDir()) runTestMkdirTemp := func(t *testing.T, pattern, wantRePat string) { name, err := MkdirTemp(dir, pattern) if name == "" || err != nil { t.Fatalf("MkdirTemp(dir, `tempfile_test`) = %v, %v", name, err) } defer Remove(name) re := regexp.MustCompile(wantRePat) if !re.MatchString(name) { t.Errorf("MkdirTemp(%q, %q) created bad name\n\t%q\ndid not match pattern\n\t%q", dir, pattern, name, wantRePat) } } for _, tt := range tests { t.Run(tt.pattern, func(t *testing.T) { wantRePat := "^" + regexp.QuoteMeta(filepath.Join(dir, tt.wantPrefix)) + "[0-9]+" + regexp.QuoteMeta(tt.wantSuffix) + "$" runTestMkdirTemp(t, tt.pattern, wantRePat) }) } // Separately testing "*xyz" (which has no prefix). That is when constructing the // pattern to assert on, as in the previous loop, using filepath.Join for an empty // prefix filepath.Join(dir, ""), produces the pattern: // ^[0-9]+xyz$ // yet we just want to match // "^/[0-9]+xyz" t.Run("*xyz", func(t *testing.T) { wantRePat := "^" + regexp.QuoteMeta(filepath.Join(dir)) + regexp.QuoteMeta(string(filepath.Separator)) + "[0-9]+xyz$" runTestMkdirTemp(t, "*xyz", wantRePat) }) } // test that we return a nice error message if the dir argument to TempDir doesn't // exist (or that it's empty and TempDir doesn't exist) func TestMkdirTempBadDir(t *testing.T) { if runtime.GOOS == "windows" { t.Log("TODO: TestMkdirTempBadDir fails on Windows, skipping") return } dir, err := MkdirTemp("", "MkdirTempBadDir") if err != nil { t.Fatal(err) } defer RemoveAll(dir) badDir := filepath.Join(dir, "not-exist") _, err = MkdirTemp(badDir, "foo") if pe, ok := err.(*fs.PathError); !ok || !IsNotExist(err) || pe.Path != badDir { t.Errorf("TempDir error = %#v; want PathError for path %q satisfying IsNotExist", err, badDir) } } func TestMkdirTempBadPattern(t *testing.T) { tmpDir, err := MkdirTemp("", t.Name()) if err != nil { t.Fatal(err) } defer RemoveAll(tmpDir) const sep = string(PathSeparator) tests := []struct { pattern string wantErr bool }{ {"ioutil*test", false}, {"tempfile_test*foo", false}, {"tempfile_test" + sep + "foo", true}, {"tempfile_test*" + sep + "foo", true}, {"tempfile_test" + sep + "*foo", true}, {sep + "tempfile_test" + sep + "*foo", true}, {"tempfile_test*foo" + sep, true}, } for _, tt := range tests { t.Run(tt.pattern, func(t *testing.T) { _, err := MkdirTemp(tmpDir, tt.pattern) if tt.wantErr { if err == nil { t.Errorf("MkdirTemp(..., %#q) succeeded, expected error", tt.pattern) } if !errors.Is(err, ErrPatternHasSeparator) { t.Errorf("MkdirTemp(..., %#q): %v, expected ErrPatternHasSeparator", tt.pattern, err) } } else if err != nil { t.Errorf("MkdirTemp(..., %#q): %v", tt.pattern, err) } }) } } ================================================ FILE: src/os/truncate_test.go ================================================ //go:build darwin || (linux && !baremetal && !js && !wasi) // Copyright 2024 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os_test import ( . "os" "path/filepath" "runtime" "testing" ) func TestTruncate(t *testing.T) { // Truncate is not supported on Windows or wasi at the moment if runtime.GOOS == "windows" || runtime.GOOS == "wasip1" || runtime.GOOS == "wasip2" { t.Logf("skipping test on %s", runtime.GOOS) return } tmpDir := t.TempDir() file := filepath.Join(tmpDir, "truncate_test") fd, err := Create(file) if err != nil { t.Fatalf("create %q: got %v, want nil", file, err) } defer fd.Close() // truncate up to 0x100 if err := fd.Truncate(0x100); err != nil { t.Fatalf("truncate %q: got %v, want nil", file, err) } // check if size is 0x100 fi, err := Stat(file) if err != nil { t.Fatalf("stat %q: got %v, want nil", file, err) } if fi.Size() != 0x100 { t.Fatalf("size of %q is %d; want 0x100", file, fi.Size()) } // truncate down to 0x80 if err := fd.Truncate(0x80); err != nil { t.Fatalf("truncate %q: got %v, want nil", file, err) } // check if size is 0x80 fi, err = Stat(file) if err != nil { t.Fatalf("stat %q: got %v, want nil", file, err) } if fi.Size() != 0x80 { t.Fatalf("size of %q is %d; want 0x80", file, fi.Size()) } } ================================================ FILE: src/os/types.go ================================================ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os // File represents an open file descriptor. type File struct { *file // os specific } ================================================ FILE: src/os/types_anyos.go ================================================ //go:build !baremetal && !js && !wasm_unknown && !nintendoswitch // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os import "syscall" // Getpagesize returns the underlying system's memory page size. func Getpagesize() int { return syscall.Getpagesize() } func (fs *fileStat) Name() string { return fs.name } func (fs *fileStat) IsDir() bool { return fs.Mode().IsDir() } // SameFile reports whether fi1 and fi2 describe the same file. // For example, on Unix this means that the device and inode fields // of the two underlying structures are identical; on other systems // the decision may be based on the path names. // SameFile only applies to results returned by this package's Stat. // It returns false in other cases. func SameFile(fi1, fi2 FileInfo) bool { fs1, ok1 := fi1.(*fileStat) fs2, ok2 := fi2.(*fileStat) if !ok1 || !ok2 { return false } return sameFile(fs1, fs2) } ================================================ FILE: src/os/types_unix.go ================================================ //go:build darwin || (linux && !baremetal && !wasm_unknown && !nintendoswitch) || wasip1 || wasip2 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os import ( "syscall" "time" ) // A fileStat is the implementation of FileInfo returned by Stat and Lstat. type fileStat struct { name string size int64 mode FileMode modTime time.Time sys syscall.Stat_t } func (fs *fileStat) Size() int64 { return fs.size } func (fs *fileStat) Mode() FileMode { return fs.mode } func (fs *fileStat) ModTime() time.Time { return fs.modTime } func (fs *fileStat) Sys() interface{} { return &fs.sys } func sameFile(fs1, fs2 *fileStat) bool { return fs1.sys.Dev == fs2.sys.Dev && fs1.sys.Ino == fs2.sys.Ino } ================================================ FILE: src/os/types_windows.go ================================================ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os import ( "internal/syscall/windows" "sync" "syscall" "time" "unsafe" ) // A fileStat is the implementation of FileInfo returned by Stat and Lstat. type fileStat struct { name string // from ByHandleFileInformation, Win32FileAttributeData and Win32finddata FileAttributes uint32 CreationTime syscall.Filetime LastAccessTime syscall.Filetime LastWriteTime syscall.Filetime FileSizeHigh uint32 FileSizeLow uint32 // from Win32finddata Reserved0 uint32 // what syscall.GetFileType returns filetype uint32 // used to implement SameFile sync.Mutex path string vol uint32 idxhi uint32 idxlo uint32 appendNameToPath bool } // newFileStatFromGetFileInformationByHandle calls GetFileInformationByHandle // to gather all required information about the file handle h. func newFileStatFromGetFileInformationByHandle(path string, h syscall.Handle) (fs *fileStat, err error) { var d syscall.ByHandleFileInformation err = syscall.GetFileInformationByHandle(h, &d) if err != nil { return nil, &PathError{Op: "GetFileInformationByHandle", Path: path, Err: err} } var ti windows.FILE_ATTRIBUTE_TAG_INFO err = windows.GetFileInformationByHandleEx(h, windows.FileAttributeTagInfo, (*byte)(unsafe.Pointer(&ti)), uint32(unsafe.Sizeof(ti))) if err != nil { if errno, ok := err.(syscall.Errno); ok && errno == windows.ERROR_INVALID_PARAMETER { // It appears calling GetFileInformationByHandleEx with // FILE_ATTRIBUTE_TAG_INFO fails on FAT file system with // ERROR_INVALID_PARAMETER. Clear ti.ReparseTag in that // instance to indicate no symlinks are possible. ti.ReparseTag = 0 } else { return nil, &PathError{Op: "GetFileInformationByHandleEx", Path: path, Err: err} } } return &fileStat{ name: basename(path), FileAttributes: d.FileAttributes, CreationTime: d.CreationTime, LastAccessTime: d.LastAccessTime, LastWriteTime: d.LastWriteTime, FileSizeHigh: d.FileSizeHigh, FileSizeLow: d.FileSizeLow, vol: d.VolumeSerialNumber, idxhi: d.FileIndexHigh, idxlo: d.FileIndexLow, Reserved0: ti.ReparseTag, // fileStat.path is used by os.SameFile to decide if it needs // to fetch vol, idxhi and idxlo. But these are already set, // so set fileStat.path to "" to prevent os.SameFile doing it again. }, nil } // newFileStatFromWin32finddata copies all required information // from syscall.Win32finddata d into the newly created fileStat. func newFileStatFromWin32finddata(d *syscall.Win32finddata) *fileStat { return &fileStat{ FileAttributes: d.FileAttributes, CreationTime: d.CreationTime, LastAccessTime: d.LastAccessTime, LastWriteTime: d.LastWriteTime, FileSizeHigh: d.FileSizeHigh, FileSizeLow: d.FileSizeLow, Reserved0: d.Reserved0, } } func (fs *fileStat) isSymlink() bool { // Use instructions described at // https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/ // to recognize whether it's a symlink. if fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 { return false } return fs.Reserved0 == syscall.IO_REPARSE_TAG_SYMLINK || fs.Reserved0 == windows.IO_REPARSE_TAG_MOUNT_POINT } func (fs *fileStat) Size() int64 { return int64(fs.FileSizeHigh)<<32 + int64(fs.FileSizeLow) } func (fs *fileStat) Mode() (m FileMode) { if fs == &devNullStat { return ModeDevice | ModeCharDevice | 0666 } if fs.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 { m |= 0444 } else { m |= 0666 } if fs.isSymlink() { return m | ModeSymlink } if fs.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { m |= ModeDir | 0111 } switch fs.filetype { case syscall.FILE_TYPE_PIPE: m |= ModeNamedPipe case syscall.FILE_TYPE_CHAR: m |= ModeDevice | ModeCharDevice } return m } func (fs *fileStat) ModTime() time.Time { return time.Unix(0, fs.LastWriteTime.Nanoseconds()) } // Sys returns syscall.Win32FileAttributeData for file fs. func (fs *fileStat) Sys() interface{} { return &syscall.Win32FileAttributeData{ FileAttributes: fs.FileAttributes, CreationTime: fs.CreationTime, LastAccessTime: fs.LastAccessTime, LastWriteTime: fs.LastWriteTime, FileSizeHigh: fs.FileSizeHigh, FileSizeLow: fs.FileSizeLow, } } func (fs *fileStat) loadFileId() error { fs.Lock() defer fs.Unlock() if fs.path == "" { // already done return nil } var path string if fs.appendNameToPath { path = fs.path + `\` + fs.name } else { path = fs.path } pathp, err := syscall.UTF16PtrFromString(path) if err != nil { return err } attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS) if fs.isSymlink() { // Use FILE_FLAG_OPEN_REPARSE_POINT, otherwise CreateFile will follow symlink. // See https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT } h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0) if err != nil { return err } defer syscall.CloseHandle(h) var i syscall.ByHandleFileInformation err = syscall.GetFileInformationByHandle(h, &i) if err != nil { return err } fs.path = "" fs.vol = i.VolumeSerialNumber fs.idxhi = i.FileIndexHigh fs.idxlo = i.FileIndexLow return nil } // saveInfoFromPath saves full path of the file to be used by os.SameFile later, // and set name from path. func (fs *fileStat) saveInfoFromPath(path string) error { fs.path = path if !isAbs(fs.path) { var err error fs.path, err = syscall.FullPath(fs.path) if err != nil { return &PathError{Op: "FullPath", Path: path, Err: err} } } fs.name = basename(path) return nil } // devNullStat is fileStat structure describing DevNull file ("NUL"). var devNullStat = fileStat{ name: DevNull, // hopefully this will work for SameFile vol: 0, idxhi: 0, idxlo: 0, } func sameFile(fs1, fs2 *fileStat) bool { e := fs1.loadFileId() if e != nil { return false } e = fs2.loadFileId() if e != nil { return false } return fs1.vol == fs2.vol && fs1.idxhi == fs2.idxhi && fs1.idxlo == fs2.idxlo } // For testing. func atime(fi FileInfo) time.Time { return time.Unix(0, fi.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds()) } ================================================ FILE: src/reflect/all_test.go ================================================ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package reflect_test import ( "bytes" "encoding/base64" "flag" "fmt" "go/token" "io" "math" "math/rand" "net" "os" . "reflect" "reflect/internal/example1" "reflect/internal/example2" "runtime" "sort" "strconv" "strings" "sync" "sync/atomic" "testing" "time" "unsafe" ) // keep imports var ( _ = bytes.MinRead _ = base64.StdPadding _ = flag.ErrHelp _ = fmt.Append _ = token.LowestPrec _ = io.EOF _ = math.E _ = rand.Int _ = net.IPv4len _ = os.PathSeparator _ = example1.MyStruct{} _ = example2.MyStruct{} _ = Invalid // reflect _ = runtime.Compiler _ = sort.Find _ = strconv.IntSize _ = strings.Clone _ = sync.NewCond _ = atomic.AddInt32 _ = testing.T{} _ = time.Now _ = unsafe.Add(nil, 0) ) var goarch = struct { PtrSize uintptr }{ PtrSize: unsafe.Sizeof(uintptr(0)), } var testenv = struct { OptimizationOff func() bool }{ OptimizationOff: func() bool { return false }, } var sink any func TestBool(t *testing.T) { v := ValueOf(true) if v.Bool() != true { t.Fatal("ValueOf(true).Bool() = false") } } type integer int type T struct { a int b float64 c string d *int } var _ = T{} == T{} // tests depend on T being comparable type pair struct { i any s string } func assert(t *testing.T, s, want string) { if s != want { t.Errorf("have %#q want %#q", s, want) } } var typeTests = []pair{ {struct{ x int }{}, "int"}, {struct{ x int8 }{}, "int8"}, {struct{ x int16 }{}, "int16"}, {struct{ x int32 }{}, "int32"}, {struct{ x int64 }{}, "int64"}, {struct{ x uint }{}, "uint"}, {struct{ x uint8 }{}, "uint8"}, {struct{ x uint16 }{}, "uint16"}, {struct{ x uint32 }{}, "uint32"}, {struct{ x uint64 }{}, "uint64"}, {struct{ x float32 }{}, "float32"}, {struct{ x float64 }{}, "float64"}, {struct{ x int8 }{}, "int8"}, {struct{ x (**int8) }{}, "**int8"}, {struct{ x (**integer) }{}, "**reflect_test.integer"}, {struct{ x ([32]int32) }{}, "[32]int32"}, {struct{ x ([]int8) }{}, "[]int8"}, {struct{ x (map[string]int32) }{}, "map[string]int32"}, {struct{ x (chan<- string) }{}, "chan<- string"}, {struct{ x (chan<- chan string) }{}, "chan<- chan string"}, {struct{ x (chan<- <-chan string) }{}, "chan<- <-chan string"}, {struct{ x (<-chan <-chan string) }{}, "<-chan <-chan string"}, {struct{ x (chan (<-chan string)) }{}, "chan (<-chan string)"}, {struct { x struct { c chan *int32 d float32 } }{}, "struct { c chan *int32; d float32 }", }, /* // TODO(tinygo): No function support {struct{ x (func(a int8, b int32)) }{}, "func(int8, int32)"}, {struct { x struct { c func(chan *integer, *int8) } }{}, "struct { c func(chan *reflect_test.integer, *int8) }", }, */ {struct { x struct { a int8 b int32 } }{}, "struct { a int8; b int32 }", }, {struct { x struct { a int8 b int8 c int32 } }{}, "struct { a int8; b int8; c int32 }", }, {struct { x struct { a int8 b int8 c int8 d int32 } }{}, "struct { a int8; b int8; c int8; d int32 }", }, {struct { x struct { a int8 b int8 c int8 d int8 e int32 } }{}, "struct { a int8; b int8; c int8; d int8; e int32 }", }, {struct { x struct { a int8 b int8 c int8 d int8 e int8 f int32 } }{}, "struct { a int8; b int8; c int8; d int8; e int8; f int32 }", }, {struct { x struct { a int8 `reflect:"hi there"` } }{}, `struct { a int8 "reflect:\"hi there\"" }`, }, {struct { x struct { a int8 `reflect:"hi \x00there\t\n\"\\"` } }{}, `struct { a int8 "reflect:\"hi \\x00there\\t\\n\\\"\\\\\"" }`, }, /* // TODO(tinygo): Functions not supported {struct { x struct { f func(args ...int) } }{}, "struct { f func(...int) }", }, {struct { x (interface { a(func(func(int) int) func(func(int)) int) b() }) }{}, "interface { reflect_test.a(func(func(int) int) func(func(int)) int); reflect_test.b() }", }, {struct { x struct { int32 int64 } }{}, "struct { int32; int64 }", }, */ } var valueTests = []pair{ {new(int), "132"}, {new(int8), "8"}, {new(int16), "16"}, {new(int32), "32"}, {new(int64), "64"}, {new(uint), "132"}, {new(uint8), "8"}, {new(uint16), "16"}, {new(uint32), "32"}, {new(uint64), "64"}, {new(float32), "256.25"}, {new(float64), "512.125"}, {new(complex64), "532.125+10i"}, {new(complex128), "564.25+1i"}, {new(string), "stringy cheese"}, {new(bool), "true"}, {new(*int8), "*int8(0)"}, {new(**int8), "**int8(0)"}, {new([5]int32), "[5]int32{0, 0, 0, 0, 0}"}, {new(**integer), "**reflect_test.integer(0)"}, {new(map[string]int32), "map[string]int32{}"}, {new(chan<- string), "chan<- string"}, //{new(func(a int8, b int32)), "func(int8, int32)(0)"}, // TODO(tinygo): No function support {new(struct { c chan *int32 d float32 }), "struct { c chan *int32; d float32 }{chan *int32, 0}", }, /* // TODO(tinygo): No function support {new(struct{ c func(chan *integer, *int8) }), "struct { c func(chan *reflect_test.integer, *int8) }{func(chan *reflect_test.integer, *int8)(0)}", }, */ {new(struct { a int8 b int32 }), "struct { a int8; b int32 }{0, 0}", }, {new(struct { a int8 b int8 c int32 }), "struct { a int8; b int8; c int32 }{0, 0, 0}", }, } func testType(t *testing.T, i int, typ Type, want string) { s := typ.String() if s != want { t.Errorf("#%d: have %#q, want %#q", i, s, want) } } func TestTypes(t *testing.T) { for i, tt := range typeTests { testType(t, i, ValueOf(tt.i).Field(0).Type(), tt.s) } } func TestSet(t *testing.T) { for i, tt := range valueTests { v := ValueOf(tt.i) v = v.Elem() switch v.Kind() { case Int: v.SetInt(132) case Int8: v.SetInt(8) case Int16: v.SetInt(16) case Int32: v.SetInt(32) case Int64: v.SetInt(64) case Uint: v.SetUint(132) case Uint8: v.SetUint(8) case Uint16: v.SetUint(16) case Uint32: v.SetUint(32) case Uint64: v.SetUint(64) case Float32: v.SetFloat(256.25) case Float64: v.SetFloat(512.125) case Complex64: v.SetComplex(532.125 + 10i) case Complex128: v.SetComplex(564.25 + 1i) case String: v.SetString("stringy cheese") case Bool: v.SetBool(true) } s := valueToString(v) if s != tt.s { t.Errorf("#%d: have %#q, want %#q", i, s, tt.s) } } } func TestSetValue(t *testing.T) { for i, tt := range valueTests { v := ValueOf(tt.i).Elem() switch v.Kind() { case Int: v.Set(ValueOf(int(132))) case Int8: v.Set(ValueOf(int8(8))) case Int16: v.Set(ValueOf(int16(16))) case Int32: v.Set(ValueOf(int32(32))) case Int64: v.Set(ValueOf(int64(64))) case Uint: v.Set(ValueOf(uint(132))) case Uint8: v.Set(ValueOf(uint8(8))) case Uint16: v.Set(ValueOf(uint16(16))) case Uint32: v.Set(ValueOf(uint32(32))) case Uint64: v.Set(ValueOf(uint64(64))) case Float32: v.Set(ValueOf(float32(256.25))) case Float64: v.Set(ValueOf(512.125)) case Complex64: v.Set(ValueOf(complex64(532.125 + 10i))) case Complex128: v.Set(ValueOf(complex128(564.25 + 1i))) case String: v.Set(ValueOf("stringy cheese")) case Bool: v.Set(ValueOf(true)) } s := valueToString(v) if s != tt.s { t.Errorf("#%d: have %#q, want %#q", i, s, tt.s) } } } func TestMapIterSet(t *testing.T) { m := make(map[string]any, len(valueTests)) for _, tt := range valueTests { m[tt.s] = tt.i } v := ValueOf(m) k := New(v.Type().Key()).Elem() e := New(v.Type().Elem()).Elem() iter := v.MapRange() for iter.Next() { k.SetIterKey(iter) e.SetIterValue(iter) want := m[k.String()] got := e.Interface() if got != want { t.Errorf("%q: want (%T) %v, got (%T) %v", k.String(), want, want, got, got) } if setkey, key := valueToString(k), valueToString(iter.Key()); setkey != key { t.Errorf("MapIter.Key() = %q, MapIter.SetKey() = %q", key, setkey) } if setval, val := valueToString(e), valueToString(iter.Value()); setval != val { t.Errorf("MapIter.Value() = %q, MapIter.SetValue() = %q", val, setval) } } if testenv.OptimizationOff() { return // no inlining with the noopt builder } got := int(testing.AllocsPerRun(10, func() { iter := v.MapRange() for iter.Next() { k.SetIterKey(iter) e.SetIterValue(iter) } })) // Calling MapRange should not allocate even though it returns a *MapIter. // The function is inlineable, so if the local usage does not escape // the *MapIter, it can remain stack allocated. want := 0 if got != want { t.Errorf("wanted %d alloc, got %d", want, got) } } func TestCanIntUintFloatComplex(t *testing.T) { type integer int type uinteger uint type float float64 type complex complex128 var ops = [...]string{"CanInt", "CanUint", "CanFloat", "CanComplex"} var testCases = []struct { i any want [4]bool }{ // signed integer {132, [...]bool{true, false, false, false}}, {int8(8), [...]bool{true, false, false, false}}, {int16(16), [...]bool{true, false, false, false}}, {int32(32), [...]bool{true, false, false, false}}, {int64(64), [...]bool{true, false, false, false}}, // unsigned integer {uint(132), [...]bool{false, true, false, false}}, {uint8(8), [...]bool{false, true, false, false}}, {uint16(16), [...]bool{false, true, false, false}}, {uint32(32), [...]bool{false, true, false, false}}, {uint64(64), [...]bool{false, true, false, false}}, {uintptr(0xABCD), [...]bool{false, true, false, false}}, // floating-point {float32(256.25), [...]bool{false, false, true, false}}, {float64(512.125), [...]bool{false, false, true, false}}, // complex {complex64(532.125 + 10i), [...]bool{false, false, false, true}}, {complex128(564.25 + 1i), [...]bool{false, false, false, true}}, // underlying {integer(-132), [...]bool{true, false, false, false}}, {uinteger(132), [...]bool{false, true, false, false}}, {float(256.25), [...]bool{false, false, true, false}}, {complex(532.125 + 10i), [...]bool{false, false, false, true}}, // not-acceptable {"hello world", [...]bool{false, false, false, false}}, {new(int), [...]bool{false, false, false, false}}, {new(uint), [...]bool{false, false, false, false}}, {new(float64), [...]bool{false, false, false, false}}, {new(complex64), [...]bool{false, false, false, false}}, {new([5]int), [...]bool{false, false, false, false}}, {new(integer), [...]bool{false, false, false, false}}, {new(map[int]int), [...]bool{false, false, false, false}}, {new(chan<- int), [...]bool{false, false, false, false}}, {new(func(a int8)), [...]bool{false, false, false, false}}, {new(struct{ i int }), [...]bool{false, false, false, false}}, } for i, tc := range testCases { v := ValueOf(tc.i) got := [...]bool{v.CanInt(), v.CanUint(), v.CanFloat(), v.CanComplex()} for j := range tc.want { if got[j] != tc.want[j] { t.Errorf( "#%d: v.%s() returned %t for type %T, want %t", i, ops[j], got[j], tc.i, tc.want[j], ) } } } } func TestCanSetField(t *testing.T) { type embed struct{ x, X int } type Embed struct{ x, X int } type S1 struct { embed x, X int } type S2 struct { *embed x, X int } type S3 struct { Embed x, X int } type S4 struct { *Embed x, X int } type testCase struct { // -1 means Addr().Elem() of current value index []int canSet bool } tests := []struct { val Value cases []testCase }{{ val: ValueOf(&S1{}), cases: []testCase{ {[]int{0}, false}, {[]int{0, -1}, false}, {[]int{0, 0}, false}, {[]int{0, 0, -1}, false}, {[]int{0, -1, 0}, false}, {[]int{0, -1, 0, -1}, false}, {[]int{0, 1}, true}, {[]int{0, 1, -1}, true}, {[]int{0, -1, 1}, true}, {[]int{0, -1, 1, -1}, true}, {[]int{1}, false}, {[]int{1, -1}, false}, {[]int{2}, true}, {[]int{2, -1}, true}, }, }, { val: ValueOf(&S2{embed: &embed{}}), cases: []testCase{ {[]int{0}, false}, {[]int{0, -1}, false}, {[]int{0, 0}, false}, {[]int{0, 0, -1}, false}, {[]int{0, -1, 0}, false}, {[]int{0, -1, 0, -1}, false}, {[]int{0, 1}, true}, {[]int{0, 1, -1}, true}, {[]int{0, -1, 1}, true}, {[]int{0, -1, 1, -1}, true}, {[]int{1}, false}, {[]int{2}, true}, }, }, { val: ValueOf(&S3{}), cases: []testCase{ {[]int{0}, true}, {[]int{0, -1}, true}, {[]int{0, 0}, false}, {[]int{0, 0, -1}, false}, {[]int{0, -1, 0}, false}, {[]int{0, -1, 0, -1}, false}, {[]int{0, 1}, true}, {[]int{0, 1, -1}, true}, {[]int{0, -1, 1}, true}, {[]int{0, -1, 1, -1}, true}, {[]int{1}, false}, {[]int{2}, true}, }, }, { val: ValueOf(&S4{Embed: &Embed{}}), cases: []testCase{ {[]int{0}, true}, {[]int{0, -1}, true}, {[]int{0, 0}, false}, {[]int{0, 0, -1}, false}, {[]int{0, -1, 0}, false}, {[]int{0, -1, 0, -1}, false}, {[]int{0, 1}, true}, {[]int{0, 1, -1}, true}, {[]int{0, -1, 1}, true}, {[]int{0, -1, 1, -1}, true}, {[]int{1}, false}, {[]int{2}, true}, }, }} for _, tt := range tests { t.Run(tt.val.Type().Name(), func(t *testing.T) { for _, tc := range tt.cases { f := tt.val for _, i := range tc.index { if f.Kind() == Pointer { f = f.Elem() } if i == -1 { f = f.Addr().Elem() } else { f = f.Field(i) } } if got := f.CanSet(); got != tc.canSet { t.Errorf("CanSet() = %v, want %v", got, tc.canSet) } } }) } } var _i = 7 var valueToStringTests = []pair{ {123, "123"}, {123.5, "123.5"}, {byte(123), "123"}, {"abc", "abc"}, {T{123, 456.75, "hello", &_i}, "reflect_test.T{123, 456.75, hello, *int(&7)}"}, {new(chan *T), "*chan *reflect_test.T(&chan *reflect_test.T)"}, {[10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, "[10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}"}, {&[10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, "*[10]int(&[10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})"}, {[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, "[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}"}, {&[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, "*[]int(&[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})"}, } func TestValueToString(t *testing.T) { for i, test := range valueToStringTests { s := valueToString(ValueOf(test.i)) if s != test.s { t.Errorf("#%d: have %#q, want %#q", i, s, test.s) } } } func TestArrayElemSet(t *testing.T) { v := ValueOf(&[10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}).Elem() v.Index(4).SetInt(123) s := valueToString(v) const want = "[10]int{1, 2, 3, 4, 123, 6, 7, 8, 9, 10}" if s != want { t.Errorf("[10]int: have %#q want %#q", s, want) } v = ValueOf([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) v.Index(4).SetInt(123) s = valueToString(v) const want1 = "[]int{1, 2, 3, 4, 123, 6, 7, 8, 9, 10}" if s != want1 { t.Errorf("[]int: have %#q want %#q", s, want1) } } func TestPtrPointTo(t *testing.T) { var ip *int32 var i int32 = 1234 vip := ValueOf(&ip) vi := ValueOf(&i).Elem() vip.Elem().Set(vi.Addr()) if *ip != 1234 { t.Errorf("got %d, want 1234", *ip) } ip = nil vp := ValueOf(&ip).Elem() vp.Set(Zero(vp.Type())) if ip != nil { t.Errorf("got non-nil (%p), want nil", ip) } } func TestPtrSetNil(t *testing.T) { var i int32 = 1234 ip := &i vip := ValueOf(&ip) vip.Elem().Set(Zero(vip.Elem().Type())) if ip != nil { t.Errorf("got non-nil (%d), want nil", *ip) } } func TestMapSetNil(t *testing.T) { m := make(map[string]int) vm := ValueOf(&m) vm.Elem().Set(Zero(vm.Elem().Type())) if m != nil { t.Errorf("got non-nil (%p), want nil", m) } } func TestAll(t *testing.T) { testType(t, 1, TypeOf((int8)(0)), "int8") testType(t, 2, TypeOf((*int8)(nil)).Elem(), "int8") typ := TypeOf((*struct { c chan *int32 d float32 })(nil)) testType(t, 3, typ, "*struct { c chan *int32; d float32 }") etyp := typ.Elem() testType(t, 4, etyp, "struct { c chan *int32; d float32 }") styp := etyp f := styp.Field(0) testType(t, 5, f.Type, "chan *int32") f, present := styp.FieldByName("d") if !present { t.Errorf("FieldByName says present field is absent") } testType(t, 6, f.Type, "float32") f, present = styp.FieldByName("absent") if present { t.Errorf("FieldByName says absent field is present") } typ = TypeOf([32]int32{}) testType(t, 7, typ, "[32]int32") testType(t, 8, typ.Elem(), "int32") typ = TypeOf((map[string]*int32)(nil)) testType(t, 9, typ, "map[string]*int32") mtyp := typ testType(t, 10, mtyp.Key(), "string") testType(t, 11, mtyp.Elem(), "*int32") typ = TypeOf((chan<- string)(nil)) testType(t, 12, typ, "chan<- string") testType(t, 13, typ.Elem(), "string") // make sure tag strings are not part of element type typ = TypeOf(struct { d []uint32 `reflect:"TAG"` }{}).Field(0).Type testType(t, 14, typ, "[]uint32") } func TestInterfaceGet(t *testing.T) { var inter struct { E any } inter.E = 123.456 v1 := ValueOf(&inter) v2 := v1.Elem().Field(0) assert(t, v2.Type().String(), "interface {}") i2 := v2.Interface() v3 := ValueOf(i2) assert(t, v3.Type().String(), "float64") } func TestInterfaceValue(t *testing.T) { var inter struct { E any } inter.E = 123.456 v1 := ValueOf(&inter) v2 := v1.Elem().Field(0) assert(t, v2.Type().String(), "interface {}") v3 := v2.Elem() assert(t, v3.Type().String(), "float64") i3 := v2.Interface() if _, ok := i3.(float64); !ok { t.Error("v2.Interface() did not return float64, got ", TypeOf(i3)) } } /* func TestFunctionValue(t *testing.T) { var x any = func() {} v := ValueOf(x) if fmt.Sprint(v.Interface()) != fmt.Sprint(x) { t.Fatalf("TestFunction returned wrong pointer") } assert(t, v.Type().String(), "func()") } */ func TestGrow(t *testing.T) { v := ValueOf([]int(nil)) shouldPanic("reflect.Value.Grow using unaddressable value", func() { v.Grow(0) }) v = ValueOf(new([]int)).Elem() v.Grow(0) if !v.IsNil() { t.Errorf("v.Grow(0) should still be nil") } v.Grow(1) if v.Cap() == 0 { t.Errorf("v.Cap = %v, want non-zero", v.Cap()) } want := v.UnsafePointer() v.Grow(1) got := v.UnsafePointer() if got != want { t.Errorf("noop v.Grow should not change pointers") } t.Run("Append", func(t *testing.T) { var got, want []T v := ValueOf(&got).Elem() appendValue := func(vt T) { v.Grow(1) v.SetLen(v.Len() + 1) v.Index(v.Len() - 1).Set(ValueOf(vt)) } for i := 0; i < 10; i++ { vt := T{i, float64(i), strconv.Itoa(i), &i} appendValue(vt) want = append(want, vt) } if !DeepEqual(got, want) { t.Errorf("value mismatch:\ngot %v\nwant %v", got, want) } }) t.Run("Rate", func(t *testing.T) { var b []byte v := ValueOf(new([]byte)).Elem() for i := 0; i < 10; i++ { b = append(b[:cap(b)], make([]byte, 1)...) v.SetLen(v.Cap()) v.Grow(1) if v.Cap() != cap(b) { t.Errorf("v.Cap = %v, want %v", v.Cap(), cap(b)) } } }) t.Run("ZeroCapacity", func(t *testing.T) { for i := 0; i < 10; i++ { v := ValueOf(new([]byte)).Elem() v.Grow(61) b := v.Bytes() b = b[:cap(b)] for i, c := range b { if c != 0 { t.Fatalf("Value.Bytes[%d] = 0x%02x, want 0x00", i, c) } b[i] = 0xff } runtime.GC() } }) } var appendTests = []struct { orig, extra []int }{ {nil, nil}, {[]int{}, nil}, {nil, []int{}}, {[]int{}, []int{}}, {nil, []int{22}}, {[]int{}, []int{22}}, {make([]int, 2, 4), nil}, {make([]int, 2, 4), []int{}}, {make([]int, 2, 4), []int{22}}, {make([]int, 2, 4), []int{22, 33, 44}}, } func TestAppend(t *testing.T) { for i, test := range appendTests { origLen, extraLen := len(test.orig), len(test.extra) want := append(test.orig, test.extra...) // Convert extra from []int to []Value. e0 := make([]Value, len(test.extra)) for j, e := range test.extra { e0[j] = ValueOf(e) } // Convert extra from []int to *SliceValue. e1 := ValueOf(test.extra) // Test Append. a0 := ValueOf(&test.orig).Elem() have0 := Append(a0, e0...) if have0.CanAddr() { t.Errorf("Append #%d: have slice should not be addressable", i) } if !DeepEqual(have0.Interface(), want) { t.Errorf("Append #%d: have %v, want %v (%p %p)", i, have0, want, test.orig, have0.Interface()) } // Check that the orig and extra slices were not modified. if a0.Len() != len(test.orig) { t.Errorf("Append #%d: a0.Len: have %d, want %d", i, a0.Len(), origLen) } if len(test.orig) != origLen { t.Errorf("Append #%d origLen: have %v, want %v", i, len(test.orig), origLen) } if len(test.extra) != extraLen { t.Errorf("Append #%d extraLen: have %v, want %v", i, len(test.extra), extraLen) } // Test AppendSlice. a1 := ValueOf(&test.orig).Elem() have1 := AppendSlice(a1, e1) if have1.CanAddr() { t.Errorf("AppendSlice #%d: have slice should not be addressable", i) } if !DeepEqual(have1.Interface(), want) { t.Errorf("AppendSlice #%d: have %v, want %v", i, have1, want) } // Check that the orig and extra slices were not modified. if a1.Len() != len(test.orig) { t.Errorf("AppendSlice #%d: a1.Len: have %d, want %d", i, a0.Len(), origLen) } if len(test.orig) != origLen { t.Errorf("AppendSlice #%d origLen: have %v, want %v", i, len(test.orig), origLen) } if len(test.extra) != extraLen { t.Errorf("AppendSlice #%d extraLen: have %v, want %v", i, len(test.extra), extraLen) } // Test Append and AppendSlice with unexported value. ax := ValueOf(struct{ x []int }{test.orig}).Field(0) shouldPanic("using unexported field", func() { Append(ax, e0...) }) shouldPanic("using unexported field", func() { AppendSlice(ax, e1) }) } } func TestCopy(t *testing.T) { a := []int{1, 2, 3, 4, 10, 9, 8, 7} b := []int{11, 22, 33, 44, 1010, 99, 88, 77, 66, 55, 44} c := []int{11, 22, 33, 44, 1010, 99, 88, 77, 66, 55, 44} for i := 0; i < len(b); i++ { if b[i] != c[i] { t.Fatalf("b != c before test") } } a1 := a b1 := b aa := ValueOf(&a1).Elem() ab := ValueOf(&b1).Elem() for tocopy := 1; tocopy <= 7; tocopy++ { aa.SetLen(tocopy) Copy(ab, aa) aa.SetLen(8) for i := 0; i < tocopy; i++ { if a[i] != b[i] { t.Errorf("(i) tocopy=%d a[%d]=%d, b[%d]=%d", tocopy, i, a[i], i, b[i]) } } for i := tocopy; i < len(b); i++ { if b[i] != c[i] { if i < len(a) { t.Errorf("(ii) tocopy=%d a[%d]=%d, b[%d]=%d, c[%d]=%d", tocopy, i, a[i], i, b[i], i, c[i]) } else { t.Errorf("(iii) tocopy=%d b[%d]=%d, c[%d]=%d", tocopy, i, b[i], i, c[i]) } } else { t.Logf("tocopy=%d elem %d is okay\n", tocopy, i) } } } } func TestCopyString(t *testing.T) { t.Run("Slice", func(t *testing.T) { s := bytes.Repeat([]byte{'_'}, 8) val := ValueOf(s) n := Copy(val, ValueOf("")) if expecting := []byte("________"); n != 0 || !bytes.Equal(s, expecting) { t.Errorf("got n = %d, s = %s, expecting n = 0, s = %s", n, s, expecting) } n = Copy(val, ValueOf("hello")) if expecting := []byte("hello___"); n != 5 || !bytes.Equal(s, expecting) { t.Errorf("got n = %d, s = %s, expecting n = 5, s = %s", n, s, expecting) } n = Copy(val, ValueOf("helloworld")) if expecting := []byte("hellowor"); n != 8 || !bytes.Equal(s, expecting) { t.Errorf("got n = %d, s = %s, expecting n = 8, s = %s", n, s, expecting) } }) t.Run("Array", func(t *testing.T) { s := [...]byte{'_', '_', '_', '_', '_', '_', '_', '_'} val := ValueOf(&s).Elem() n := Copy(val, ValueOf("")) if expecting := []byte("________"); n != 0 || !bytes.Equal(s[:], expecting) { t.Errorf("got n = %d, s = %s, expecting n = 0, s = %s", n, s[:], expecting) } n = Copy(val, ValueOf("hello")) if expecting := []byte("hello___"); n != 5 || !bytes.Equal(s[:], expecting) { t.Errorf("got n = %d, s = %s, expecting n = 5, s = %s", n, s[:], expecting) } n = Copy(val, ValueOf("helloworld")) if expecting := []byte("hellowor"); n != 8 || !bytes.Equal(s[:], expecting) { t.Errorf("got n = %d, s = %s, expecting n = 8, s = %s", n, s[:], expecting) } }) } func TestCopyArray(t *testing.T) { a := [8]int{1, 2, 3, 4, 10, 9, 8, 7} b := [11]int{11, 22, 33, 44, 1010, 99, 88, 77, 66, 55, 44} c := b aa := ValueOf(&a).Elem() ab := ValueOf(&b).Elem() Copy(ab, aa) for i := 0; i < len(a); i++ { if a[i] != b[i] { t.Errorf("(i) a[%d]=%d, b[%d]=%d", i, a[i], i, b[i]) } } for i := len(a); i < len(b); i++ { if b[i] != c[i] { t.Errorf("(ii) b[%d]=%d, c[%d]=%d", i, b[i], i, c[i]) } else { t.Logf("elem %d is okay\n", i) } } } func TestBigUnnamedStruct(t *testing.T) { b := struct{ a, b, c, d int64 }{1, 2, 3, 4} v := ValueOf(b) b1 := v.Interface().(struct { a, b, c, d int64 }) if b1.a != b.a || b1.b != b.b || b1.c != b.c || b1.d != b.d { t.Errorf("ValueOf(%v).Interface().(*Big) = %v", b, b1) } } type big struct { a, b, c, d, e int64 } func TestBigStruct(t *testing.T) { b := big{1, 2, 3, 4, 5} v := ValueOf(b) b1 := v.Interface().(big) if b1.a != b.a || b1.b != b.b || b1.c != b.c || b1.d != b.d || b1.e != b.e { t.Errorf("ValueOf(%v).Interface().(big) = %v", b, b1) } } type Basic struct { x int y float32 } type NotBasic Basic type DeepEqualTest struct { a, b any eq bool } // Simple functions for DeepEqual tests. var ( fn1 func() // nil. fn2 func() // nil. fn3 = func() { fn1() } // Not nil. ) type self struct{} type Loop *Loop type Loopy any var loop1, loop2 Loop var loopy1, loopy2 Loopy var cycleMap1, cycleMap2, cycleMap3 map[string]any type structWithSelfPtr struct { p *structWithSelfPtr s string } func init() { loop1 = &loop2 loop2 = &loop1 loopy1 = &loopy2 loopy2 = &loopy1 cycleMap1 = map[string]any{} cycleMap1["cycle"] = cycleMap1 cycleMap2 = map[string]any{} cycleMap2["cycle"] = cycleMap2 cycleMap3 = map[string]any{} cycleMap3["different"] = cycleMap3 } var deepEqualTests = []DeepEqualTest{ // Equalities {nil, nil, true}, {1, 1, true}, {int32(1), int32(1), true}, {0.5, 0.5, true}, {float32(0.5), float32(0.5), true}, {"hello", "hello", true}, {make([]int, 10), make([]int, 10), true}, {&[3]int{1, 2, 3}, &[3]int{1, 2, 3}, true}, {Basic{1, 0.5}, Basic{1, 0.5}, true}, {error(nil), error(nil), true}, {map[int]string{1: "one", 2: "two"}, map[int]string{2: "two", 1: "one"}, true}, {fn1, fn2, true}, {[]byte{1, 2, 3}, []byte{1, 2, 3}, true}, {[]MyByte{1, 2, 3}, []MyByte{1, 2, 3}, true}, {MyBytes{1, 2, 3}, MyBytes{1, 2, 3}, true}, // Inequalities {1, 2, false}, {int32(1), int32(2), false}, {0.5, 0.6, false}, {float32(0.5), float32(0.6), false}, {"hello", "hey", false}, {make([]int, 10), make([]int, 11), false}, {&[3]int{1, 2, 3}, &[3]int{1, 2, 4}, false}, {Basic{1, 0.5}, Basic{1, 0.6}, false}, {Basic{1, 0}, Basic{2, 0}, false}, {map[int]string{1: "one", 3: "two"}, map[int]string{2: "two", 1: "one"}, false}, {map[int]string{1: "one", 2: "txo"}, map[int]string{2: "two", 1: "one"}, false}, {map[int]string{1: "one"}, map[int]string{2: "two", 1: "one"}, false}, {map[int]string{2: "two", 1: "one"}, map[int]string{1: "one"}, false}, {nil, 1, false}, {1, nil, false}, {fn1, fn3, false}, {fn3, fn3, false}, {[][]int{{1}}, [][]int{{2}}, false}, {&structWithSelfPtr{p: &structWithSelfPtr{s: "a"}}, &structWithSelfPtr{p: &structWithSelfPtr{s: "b"}}, false}, // Fun with floating point. {math.NaN(), math.NaN(), false}, {&[1]float64{math.NaN()}, &[1]float64{math.NaN()}, false}, {&[1]float64{math.NaN()}, self{}, true}, {[]float64{math.NaN()}, []float64{math.NaN()}, false}, {[]float64{math.NaN()}, self{}, true}, {map[float64]float64{math.NaN(): 1}, map[float64]float64{1: 2}, false}, {map[float64]float64{math.NaN(): 1}, self{}, true}, // Nil vs empty: not the same. {[]int{}, []int(nil), false}, {[]int{}, []int{}, true}, {[]int(nil), []int(nil), true}, {map[int]int{}, map[int]int(nil), false}, {map[int]int{}, map[int]int{}, true}, {map[int]int(nil), map[int]int(nil), true}, // Mismatched types {1, 1.0, false}, {int32(1), int64(1), false}, {0.5, "hello", false}, {[]int{1, 2, 3}, [3]int{1, 2, 3}, false}, {&[3]any{1, 2, 4}, &[3]any{1, 2, "s"}, false}, {Basic{1, 0.5}, NotBasic{1, 0.5}, false}, {map[uint]string{1: "one", 2: "two"}, map[int]string{2: "two", 1: "one"}, false}, {[]byte{1, 2, 3}, []MyByte{1, 2, 3}, false}, {[]MyByte{1, 2, 3}, MyBytes{1, 2, 3}, false}, {[]byte{1, 2, 3}, MyBytes{1, 2, 3}, false}, // Possible loops. {&loop1, &loop1, true}, {&loop1, &loop2, true}, {&loopy1, &loopy1, true}, {&loopy1, &loopy2, true}, {&cycleMap1, &cycleMap2, true}, {&cycleMap1, &cycleMap3, false}, } func TestDeepEqual(t *testing.T) { for _, test := range deepEqualTests { if test.b == (self{}) { test.b = test.a } if r := DeepEqual(test.a, test.b); r != test.eq { t.Errorf("DeepEqual(%#v, %#v) = %v, want %v", test.a, test.b, r, test.eq) } } } func TestTypeOf(t *testing.T) { // Special case for nil if typ := TypeOf(nil); typ != nil { t.Errorf("expected nil type for nil value; got %v", typ) } for _, test := range deepEqualTests { v := ValueOf(test.a) if !v.IsValid() { continue } typ := TypeOf(test.a) if typ != v.Type() { t.Errorf("TypeOf(%v) = %v, but ValueOf(%v).Type() = %v", test.a, typ, test.a, v.Type()) } } } type Recursive struct { x int r *Recursive } func TestDeepEqualRecursiveStruct(t *testing.T) { a, b := new(Recursive), new(Recursive) *a = Recursive{12, a} *b = Recursive{12, b} if !DeepEqual(a, b) { t.Error("DeepEqual(recursive same) = false, want true") } } type _Complex struct { a int b [3]*_Complex c *string d map[float64]float64 } func TestDeepEqualComplexStruct(t *testing.T) { m := make(map[float64]float64) stra, strb := "hello", "hello" a, b := new(_Complex), new(_Complex) *a = _Complex{5, [3]*_Complex{a, b, a}, &stra, m} *b = _Complex{5, [3]*_Complex{b, a, a}, &strb, m} if !DeepEqual(a, b) { t.Error("DeepEqual(complex same) = false, want true") } } func TestDeepEqualComplexStructInequality(t *testing.T) { m := make(map[float64]float64) stra, strb := "hello", "helloo" // Difference is here a, b := new(_Complex), new(_Complex) *a = _Complex{5, [3]*_Complex{a, b, a}, &stra, m} *b = _Complex{5, [3]*_Complex{b, a, a}, &strb, m} if DeepEqual(a, b) { t.Error("DeepEqual(complex different) = true, want false") } } type UnexpT struct { m map[int]int } func TestDeepEqualUnexportedMap(t *testing.T) { // Check that DeepEqual can look at unexported fields. x1 := UnexpT{map[int]int{1: 2}} x2 := UnexpT{map[int]int{1: 2}} if !DeepEqual(&x1, &x2) { t.Error("DeepEqual(x1, x2) = false, want true") } y1 := UnexpT{map[int]int{2: 3}} if DeepEqual(&x1, &y1) { t.Error("DeepEqual(x1, y1) = true, want false") } } var deepEqualPerfTests = []struct { x, y any }{ {x: int8(99), y: int8(99)}, {x: []int8{99}, y: []int8{99}}, {x: int16(99), y: int16(99)}, {x: []int16{99}, y: []int16{99}}, {x: int32(99), y: int32(99)}, {x: []int32{99}, y: []int32{99}}, {x: int64(99), y: int64(99)}, {x: []int64{99}, y: []int64{99}}, {x: int(999999), y: int(999999)}, {x: []int{999999}, y: []int{999999}}, {x: uint8(99), y: uint8(99)}, {x: []uint8{99}, y: []uint8{99}}, {x: uint16(99), y: uint16(99)}, {x: []uint16{99}, y: []uint16{99}}, {x: uint32(99), y: uint32(99)}, {x: []uint32{99}, y: []uint32{99}}, {x: uint64(99), y: uint64(99)}, {x: []uint64{99}, y: []uint64{99}}, {x: uint(999999), y: uint(999999)}, {x: []uint{999999}, y: []uint{999999}}, {x: uintptr(999999), y: uintptr(999999)}, {x: []uintptr{999999}, y: []uintptr{999999}}, {x: float32(1.414), y: float32(1.414)}, {x: []float32{1.414}, y: []float32{1.414}}, {x: float64(1.414), y: float64(1.414)}, {x: []float64{1.414}, y: []float64{1.414}}, {x: complex64(1.414), y: complex64(1.414)}, {x: []complex64{1.414}, y: []complex64{1.414}}, {x: complex128(1.414), y: complex128(1.414)}, {x: []complex128{1.414}, y: []complex128{1.414}}, {x: true, y: true}, {x: []bool{true}, y: []bool{true}}, {x: "abcdef", y: "abcdef"}, {x: []string{"abcdef"}, y: []string{"abcdef"}}, {x: []byte("abcdef"), y: []byte("abcdef")}, {x: [][]byte{[]byte("abcdef")}, y: [][]byte{[]byte("abcdef")}}, {x: [6]byte{'a', 'b', 'c', 'a', 'b', 'c'}, y: [6]byte{'a', 'b', 'c', 'a', 'b', 'c'}}, {x: [][6]byte{[6]byte{'a', 'b', 'c', 'a', 'b', 'c'}}, y: [][6]byte{[6]byte{'a', 'b', 'c', 'a', 'b', 'c'}}}, } func TestDeepEqualAllocs(t *testing.T) { for _, tt := range deepEqualPerfTests { t.Run(ValueOf(tt.x).Type().String(), func(t *testing.T) { got := testing.AllocsPerRun(100, func() { if !DeepEqual(tt.x, tt.y) { t.Errorf("DeepEqual(%v, %v)=false", tt.x, tt.y) } }) if int(got) != 0 { t.Errorf("DeepEqual(%v, %v) allocated %d times", tt.x, tt.y, int(got)) } }) } } func check2ndField(x any, offs uintptr, t *testing.T) { s := ValueOf(x) f := s.Type().Field(1) if f.Offset != offs { t.Error("mismatched offsets in structure alignment:", f.Offset, offs) } } // Check that structure alignment & offsets viewed through reflect agree with those // from the compiler itself. func TestAlignment(t *testing.T) { type T1inner struct { a int } type T1 struct { T1inner f int } type T2inner struct { a, b int } type T2 struct { T2inner f int } x := T1{T1inner{2}, 17} check2ndField(x, uintptr(unsafe.Pointer(&x.f))-uintptr(unsafe.Pointer(&x)), t) x1 := T2{T2inner{2, 3}, 17} check2ndField(x1, uintptr(unsafe.Pointer(&x1.f))-uintptr(unsafe.Pointer(&x1)), t) } func Nil(a any, t *testing.T) { n := ValueOf(a).Field(0) if !n.IsNil() { t.Errorf("%v should be nil", a) } } func NotNil(a any, t *testing.T) { n := ValueOf(a).Field(0) if n.IsNil() { t.Errorf("value of type %v should not be nil", ValueOf(a).Type().String()) } } func TestIsNil(t *testing.T) { // These implement IsNil. // Wrap in extra struct to hide interface type. doNil := []any{ struct{ x *int }{}, struct{ x any }{}, struct{ x map[string]int }{}, struct{ x func() bool }{}, struct{ x chan int }{}, struct{ x []string }{}, struct{ x unsafe.Pointer }{}, } for _, ts := range doNil { ty := TypeOf(ts).Field(0).Type v := Zero(ty) v.IsNil() // panics if not okay to call } // Check the implementations var pi struct { x *int } Nil(pi, t) pi.x = new(int) NotNil(pi, t) var si struct { x []int } Nil(si, t) si.x = make([]int, 10) NotNil(si, t) var ci struct { x chan int } Nil(ci, t) ci.x = make(chan int) NotNil(ci, t) var mi struct { x map[int]int } Nil(mi, t) mi.x = make(map[int]int) NotNil(mi, t) var ii struct { x any } Nil(ii, t) ii.x = 2 NotNil(ii, t) var fi struct { x func(t *testing.T) } Nil(fi, t) fi.x = TestIsNil NotNil(fi, t) } func setField[S, V any](in S, offset uintptr, value V) (out S) { *(*V)(unsafe.Add(unsafe.Pointer(&in), offset)) = value return in } func TestIsZero(t *testing.T) { for i, tt := range []struct { x any want bool }{ // Booleans {true, false}, {false, true}, // Numeric types {int(0), true}, {int(1), false}, {int8(0), true}, {int8(1), false}, {int16(0), true}, {int16(1), false}, {int32(0), true}, {int32(1), false}, {int64(0), true}, {int64(1), false}, {uint(0), true}, {uint(1), false}, {uint8(0), true}, {uint8(1), false}, {uint16(0), true}, {uint16(1), false}, {uint32(0), true}, {uint32(1), false}, {uint64(0), true}, {uint64(1), false}, {float32(0), true}, {float32(1.2), false}, {float64(0), true}, {float64(1.2), false}, {math.Copysign(0, -1), true}, {complex64(0), true}, {complex64(1.2), false}, {complex128(0), true}, {complex128(1.2), false}, {complex(math.Copysign(0, -1), 0), true}, {complex(0, math.Copysign(0, -1)), true}, {complex(math.Copysign(0, -1), math.Copysign(0, -1)), true}, {uintptr(0), true}, {uintptr(128), false}, // Array {Zero(TypeOf([5]string{})).Interface(), true}, {[5]string{}, true}, // comparable array {[5]string{"", "", "", "a", ""}, false}, // comparable array {[1]*int{}, true}, // direct pointer array {[1]*int{new(int)}, false}, // direct pointer array {[3][]int{}, true}, // incomparable array {[3][]int{{1}}, false}, // incomparable array {[1 << 12]byte{}, true}, {[1 << 12]byte{1}, false}, {[1]struct{ p *int }{}, true}, {[1]struct{ p *int }{{new(int)}}, false}, {[3]Value{}, true}, {[3]Value{{}, ValueOf(0), {}}, false}, // Chan {(chan string)(nil), true}, {make(chan string), false}, {time.After(1), false}, // Func {(func())(nil), true}, {New, false}, // Interface {New(TypeOf(new(error)).Elem()).Elem(), true}, {(io.Reader)(strings.NewReader("")), false}, // Map {(map[string]string)(nil), true}, {map[string]string{}, false}, {make(map[string]string), false}, // Pointer {(*func())(nil), true}, {(*int)(nil), true}, {new(int), false}, // Slice {[]string{}, false}, {([]string)(nil), true}, {make([]string, 0), false}, // Strings {"", true}, {"not-zero", false}, // Structs {T{}, true}, // comparable struct {T{123, 456.75, "hello", &_i}, false}, // comparable struct {struct{ p *int }{}, true}, // direct pointer struct {struct{ p *int }{new(int)}, false}, // direct pointer struct {struct{ s []int }{}, true}, // incomparable struct {struct{ s []int }{[]int{1}}, false}, // incomparable struct {struct{ Value }{}, true}, {struct{ Value }{ValueOf(0)}, false}, {struct{ _, a, _ uintptr }{}, true}, // comparable struct with blank fields {setField(struct{ _, a, _ uintptr }{}, 0*unsafe.Sizeof(uintptr(0)), 1), true}, {setField(struct{ _, a, _ uintptr }{}, 1*unsafe.Sizeof(uintptr(0)), 1), false}, {setField(struct{ _, a, _ uintptr }{}, 2*unsafe.Sizeof(uintptr(0)), 1), true}, {struct{ _, a, _ func() }{}, true}, // incomparable struct with blank fields {setField(struct{ _, a, _ func() }{}, 0*unsafe.Sizeof((func())(nil)), func() {}), true}, {setField(struct{ _, a, _ func() }{}, 1*unsafe.Sizeof((func())(nil)), func() {}), false}, {setField(struct{ _, a, _ func() }{}, 2*unsafe.Sizeof((func())(nil)), func() {}), true}, {struct{ a [256]S }{}, true}, {struct{ a [256]S }{a: [256]S{2: {i1: 1}}}, false}, {struct{ a [256]float32 }{}, true}, {struct{ a [256]float32 }{a: [256]float32{2: 1.0}}, false}, {struct{ _, a [256]S }{}, true}, {setField(struct{ _, a [256]S }{}, 0*unsafe.Sizeof(int64(0)), int64(1)), true}, // UnsafePointer {(unsafe.Pointer)(nil), true}, {(unsafe.Pointer)(new(int)), false}, } { var x Value if v, ok := tt.x.(Value); ok { x = v } else { x = ValueOf(tt.x) } b := x.IsZero() if b != tt.want { t.Errorf("%d: IsZero((%s)(%+v)) = %t, want %t", i, x.Kind(), tt.x, b, tt.want) } if !Zero(TypeOf(tt.x)).IsZero() { t.Errorf("%d: IsZero(Zero(TypeOf((%s)(%+v)))) is false", i, x.Kind(), tt.x) } p := New(x.Type()).Elem() p.Set(x) p.SetZero() if !p.IsZero() { t.Errorf("%d: IsZero((%s)(%+v)) is true after SetZero", i, p.Kind(), tt.x) } } /* // TODO(tinygo): panic/recover support func() { defer func() { if r := recover(); r == nil { t.Error("should panic for invalid value") } }() (Value{}).IsZero() }() */ } // extra comment for gofmt func TestInterfaceExtraction(t *testing.T) { var s struct { W io.Writer } s.W = os.Stdout v := Indirect(ValueOf(&s)).Field(0).Interface() if v != s.W.(any) { t.Error("Interface() on interface: ", v, s.W) } } func TestNilPtrValueSub(t *testing.T) { var pi *int if pv := ValueOf(pi); pv.Elem().IsValid() { t.Error("ValueOf((*int)(nil)).Elem().IsValid()") } } func TestMap(t *testing.T) { m := map[string]int{"a": 1, "b": 2} mv := ValueOf(m) if n := mv.Len(); n != len(m) { t.Errorf("Len = %d, want %d", n, len(m)) } keys := mv.MapKeys() newmap := MakeMap(mv.Type()) for k, v := range m { // Check that returned Keys match keys in range. // These aren't required to be in the same order. seen := false for _, kv := range keys { if kv.String() == k { seen = true break } } if !seen { t.Errorf("Missing key %q", k) } // Check that value lookup is correct. vv := mv.MapIndex(ValueOf(k)) if vi := vv.Int(); vi != int64(v) { t.Errorf("Key %q: have value %d, want %d", k, vi, v) } // Copy into new map. newmap.SetMapIndex(ValueOf(k), ValueOf(v)) } vv := mv.MapIndex(ValueOf("not-present")) if vv.IsValid() { t.Errorf("Invalid key: got non-nil value %s", valueToString(vv)) } newm := newmap.Interface().(map[string]int) if len(newm) != len(m) { t.Errorf("length after copy: newm=%d, m=%d", len(newm), len(m)) } for k, v := range newm { mv, ok := m[k] if mv != v { t.Errorf("newm[%q] = %d, but m[%q] = %d, %v", k, v, k, mv, ok) } } newmap.SetMapIndex(ValueOf("a"), Value{}) v, ok := newm["a"] if ok { t.Errorf("newm[\"a\"] = %d after delete", v) } mv = ValueOf(&m).Elem() mv.Set(Zero(mv.Type())) if m != nil { t.Errorf("mv.Set(nil) failed") } type S string shouldPanic("not assignable", func() { mv.MapIndex(ValueOf(S("key"))) }) shouldPanic("not assignable", func() { mv.SetMapIndex(ValueOf(S("key")), ValueOf(0)) }) } func TestNilMap(t *testing.T) { var m map[string]int mv := ValueOf(m) keys := mv.MapKeys() if len(keys) != 0 { t.Errorf(">0 keys for nil map: %v", keys) } // Check that value for missing key is zero. x := mv.MapIndex(ValueOf("hello")) if x.Kind() != Invalid { t.Errorf("m.MapIndex(\"hello\") for nil map = %v, want Invalid Value", x) } // Check big value too. var mbig map[string][10 << 20]byte x = ValueOf(mbig).MapIndex(ValueOf("hello")) if x.Kind() != Invalid { t.Errorf("mbig.MapIndex(\"hello\") for nil map = %v, want Invalid Value", x) } // Test that deletes from a nil map succeed. mv.SetMapIndex(ValueOf("hi"), Value{}) } /* // TODO(tinygo): missing chan reflect support func TestChan(t *testing.T) { for loop := 0; loop < 2; loop++ { var c chan int var cv Value // check both ways to allocate channels switch loop { case 1: c = make(chan int, 1) cv = ValueOf(c) case 0: cv = MakeChan(TypeOf(c), 1) c = cv.Interface().(chan int) } // Send cv.Send(ValueOf(2)) if i := <-c; i != 2 { t.Errorf("reflect Send 2, native recv %d", i) } // Recv c <- 3 if i, ok := cv.Recv(); i.Int() != 3 || !ok { t.Errorf("native send 3, reflect Recv %d, %t", i.Int(), ok) } // TryRecv fail val, ok := cv.TryRecv() if val.IsValid() || ok { t.Errorf("TryRecv on empty chan: %s, %t", valueToString(val), ok) } // TryRecv success c <- 4 val, ok = cv.TryRecv() if !val.IsValid() { t.Errorf("TryRecv on ready chan got nil") } else if i := val.Int(); i != 4 || !ok { t.Errorf("native send 4, TryRecv %d, %t", i, ok) } // TrySend fail c <- 100 ok = cv.TrySend(ValueOf(5)) i := <-c if ok { t.Errorf("TrySend on full chan succeeded: value %d", i) } // TrySend success ok = cv.TrySend(ValueOf(6)) if !ok { t.Errorf("TrySend on empty chan failed") select { case x := <-c: t.Errorf("TrySend failed but it did send %d", x) default: } } else { if i = <-c; i != 6 { t.Errorf("TrySend 6, recv %d", i) } } // Close c <- 123 cv.Close() if i, ok := cv.Recv(); i.Int() != 123 || !ok { t.Errorf("send 123 then close; Recv %d, %t", i.Int(), ok) } if i, ok := cv.Recv(); i.Int() != 0 || ok { t.Errorf("after close Recv %d, %t", i.Int(), ok) } } // check creation of unbuffered channel var c chan int cv := MakeChan(TypeOf(c), 0) c = cv.Interface().(chan int) if cv.TrySend(ValueOf(7)) { t.Errorf("TrySend on sync chan succeeded") } if v, ok := cv.TryRecv(); v.IsValid() || ok { t.Errorf("TryRecv on sync chan succeeded: isvalid=%v ok=%v", v.IsValid(), ok) } // len/cap cv = MakeChan(TypeOf(c), 10) c = cv.Interface().(chan int) for i := 0; i < 3; i++ { c <- i } if l, m := cv.Len(), cv.Cap(); l != len(c) || m != cap(c) { t.Errorf("Len/Cap = %d/%d want %d/%d", l, m, len(c), cap(c)) } } // caseInfo describes a single case in a select test. type caseInfo struct { desc string canSelect bool recv Value closed bool helper func() panic bool } var allselect = flag.Bool("allselect", false, "exhaustive select test") func TestSelect(t *testing.T) { selectWatch.once.Do(func() { go selectWatcher() }) var x exhaustive nch := 0 newop := func(n int, cap int) (ch, val Value) { nch++ if nch%101%2 == 1 { c := make(chan int, cap) ch = ValueOf(c) val = ValueOf(n) } else { c := make(chan string, cap) ch = ValueOf(c) val = ValueOf(fmt.Sprint(n)) } return } for n := 0; x.Next(); n++ { if testing.Short() && n >= 1000 { break } if n >= 100000 && !*allselect { break } if n%100000 == 0 && testing.Verbose() { println("TestSelect", n) } var cases []SelectCase var info []caseInfo // Ready send. if x.Maybe() { ch, val := newop(len(cases), 1) cases = append(cases, SelectCase{ Dir: SelectSend, Chan: ch, Send: val, }) info = append(info, caseInfo{desc: "ready send", canSelect: true}) } // Ready recv. if x.Maybe() { ch, val := newop(len(cases), 1) ch.Send(val) cases = append(cases, SelectCase{ Dir: SelectRecv, Chan: ch, }) info = append(info, caseInfo{desc: "ready recv", canSelect: true, recv: val}) } // Blocking send. if x.Maybe() { ch, val := newop(len(cases), 0) cases = append(cases, SelectCase{ Dir: SelectSend, Chan: ch, Send: val, }) // Let it execute? if x.Maybe() { f := func() { ch.Recv() } info = append(info, caseInfo{desc: "blocking send", helper: f}) } else { info = append(info, caseInfo{desc: "blocking send"}) } } // Blocking recv. if x.Maybe() { ch, val := newop(len(cases), 0) cases = append(cases, SelectCase{ Dir: SelectRecv, Chan: ch, }) // Let it execute? if x.Maybe() { f := func() { ch.Send(val) } info = append(info, caseInfo{desc: "blocking recv", recv: val, helper: f}) } else { info = append(info, caseInfo{desc: "blocking recv"}) } } // Zero Chan send. if x.Maybe() { // Maybe include value to send. var val Value if x.Maybe() { val = ValueOf(100) } cases = append(cases, SelectCase{ Dir: SelectSend, Send: val, }) info = append(info, caseInfo{desc: "zero Chan send"}) } // Zero Chan receive. if x.Maybe() { cases = append(cases, SelectCase{ Dir: SelectRecv, }) info = append(info, caseInfo{desc: "zero Chan recv"}) } // nil Chan send. if x.Maybe() { cases = append(cases, SelectCase{ Dir: SelectSend, Chan: ValueOf((chan int)(nil)), Send: ValueOf(101), }) info = append(info, caseInfo{desc: "nil Chan send"}) } // nil Chan recv. if x.Maybe() { cases = append(cases, SelectCase{ Dir: SelectRecv, Chan: ValueOf((chan int)(nil)), }) info = append(info, caseInfo{desc: "nil Chan recv"}) } // closed Chan send. if x.Maybe() { ch := make(chan int) close(ch) cases = append(cases, SelectCase{ Dir: SelectSend, Chan: ValueOf(ch), Send: ValueOf(101), }) info = append(info, caseInfo{desc: "closed Chan send", canSelect: true, panic: true}) } // closed Chan recv. if x.Maybe() { ch, val := newop(len(cases), 0) ch.Close() val = Zero(val.Type()) cases = append(cases, SelectCase{ Dir: SelectRecv, Chan: ch, }) info = append(info, caseInfo{desc: "closed Chan recv", canSelect: true, closed: true, recv: val}) } var helper func() // goroutine to help the select complete // Add default? Must be last case here, but will permute. // Add the default if the select would otherwise // block forever, and maybe add it anyway. numCanSelect := 0 canProceed := false canBlock := true canPanic := false helpers := []int{} for i, c := range info { if c.canSelect { canProceed = true canBlock = false numCanSelect++ if c.panic { canPanic = true } } else if c.helper != nil { canProceed = true helpers = append(helpers, i) } } if !canProceed || x.Maybe() { cases = append(cases, SelectCase{ Dir: SelectDefault, }) info = append(info, caseInfo{desc: "default", canSelect: canBlock}) numCanSelect++ } else if canBlock { // Select needs to communicate with another goroutine. cas := &info[helpers[x.Choose(len(helpers))]] helper = cas.helper cas.canSelect = true numCanSelect++ } // Permute cases and case info. // Doing too much here makes the exhaustive loop // too exhausting, so just do two swaps. for loop := 0; loop < 2; loop++ { i := x.Choose(len(cases)) j := x.Choose(len(cases)) cases[i], cases[j] = cases[j], cases[i] info[i], info[j] = info[j], info[i] } if helper != nil { // We wait before kicking off a goroutine to satisfy a blocked select. // The pause needs to be big enough to let the select block before // we run the helper, but if we lose that race once in a while it's okay: the // select will just proceed immediately. Not a big deal. // For short tests we can grow [sic] the timeout a bit without fear of taking too long pause := 10 * time.Microsecond if testing.Short() { pause = 100 * time.Microsecond } time.AfterFunc(pause, helper) } // Run select. i, recv, recvOK, panicErr := runSelect(cases, info) if panicErr != nil && !canPanic { t.Fatalf("%s\npanicked unexpectedly: %v", fmtSelect(info), panicErr) } if panicErr == nil && canPanic && numCanSelect == 1 { t.Fatalf("%s\nselected #%d incorrectly (should panic)", fmtSelect(info), i) } if panicErr != nil { continue } cas := info[i] if !cas.canSelect { recvStr := "" if recv.IsValid() { recvStr = fmt.Sprintf(", received %v, %v", recv.Interface(), recvOK) } t.Fatalf("%s\nselected #%d incorrectly%s", fmtSelect(info), i, recvStr) } if cas.panic { t.Fatalf("%s\nselected #%d incorrectly (case should panic)", fmtSelect(info), i) } if cases[i].Dir == SelectRecv { if !recv.IsValid() { t.Fatalf("%s\nselected #%d but got %v, %v, want %v, %v", fmtSelect(info), i, recv, recvOK, cas.recv.Interface(), !cas.closed) } if !cas.recv.IsValid() { t.Fatalf("%s\nselected #%d but internal error: missing recv value", fmtSelect(info), i) } if recv.Interface() != cas.recv.Interface() || recvOK != !cas.closed { if recv.Interface() == cas.recv.Interface() && recvOK == !cas.closed { t.Fatalf("%s\nselected #%d, got %#v, %v, and DeepEqual is broken on %T", fmtSelect(info), i, recv.Interface(), recvOK, recv.Interface()) } t.Fatalf("%s\nselected #%d but got %#v, %v, want %#v, %v", fmtSelect(info), i, recv.Interface(), recvOK, cas.recv.Interface(), !cas.closed) } } else { if recv.IsValid() || recvOK { t.Fatalf("%s\nselected #%d but got %v, %v, want %v, %v", fmtSelect(info), i, recv, recvOK, Value{}, false) } } } } func TestSelectMaxCases(t *testing.T) { var sCases []SelectCase channel := make(chan int) close(channel) for i := 0; i < 65536; i++ { sCases = append(sCases, SelectCase{ Dir: SelectRecv, Chan: ValueOf(channel), }) } // Should not panic _, _, _ = Select(sCases) sCases = append(sCases, SelectCase{ Dir: SelectRecv, Chan: ValueOf(channel), }) defer func() { if err := recover(); err != nil { if err.(string) != "reflect.Select: too many cases (max 65536)" { t.Fatalf("unexpected error from select call with greater than max supported cases") } } else { t.Fatalf("expected select call to panic with greater than max supported cases") } }() // Should panic _, _, _ = Select(sCases) } func TestSelectNop(t *testing.T) { // "select { default: }" should always return the default case. chosen, _, _ := Select([]SelectCase{{Dir: SelectDefault}}) if chosen != 0 { t.Fatalf("expected Select to return 0, but got %#v", chosen) } } // selectWatch and the selectWatcher are a watchdog mechanism for running Select. // If the selectWatcher notices that the select has been blocked for >1 second, it prints // an error describing the select and panics the entire test binary. var selectWatch struct { sync.Mutex once sync.Once now time.Time info []caseInfo } func selectWatcher() { for { time.Sleep(1 * time.Second) selectWatch.Lock() if selectWatch.info != nil && time.Since(selectWatch.now) > 10*time.Second { fmt.Fprintf(os.Stderr, "TestSelect:\n%s blocked indefinitely\n", fmtSelect(selectWatch.info)) panic("select stuck") } selectWatch.Unlock() } } // runSelect runs a single select test. // It returns the values returned by Select but also returns // a panic value if the Select panics. func runSelect(cases []SelectCase, info []caseInfo) (chosen int, recv Value, recvOK bool, panicErr any) { defer func() { panicErr = recover() selectWatch.Lock() selectWatch.info = nil selectWatch.Unlock() }() selectWatch.Lock() selectWatch.now = time.Now() selectWatch.info = info selectWatch.Unlock() chosen, recv, recvOK = Select(cases) return } // fmtSelect formats the information about a single select test. func fmtSelect(info []caseInfo) string { var buf strings.Builder fmt.Fprintf(&buf, "\nselect {\n") for i, cas := range info { fmt.Fprintf(&buf, "%d: %s", i, cas.desc) if cas.recv.IsValid() { fmt.Fprintf(&buf, " val=%#v", cas.recv.Interface()) } if cas.canSelect { fmt.Fprintf(&buf, " canselect") } if cas.panic { fmt.Fprintf(&buf, " panic") } fmt.Fprintf(&buf, "\n") } fmt.Fprintf(&buf, "}") return buf.String() } // TODO(tinygo): missing func/method/call support type two [2]uintptr // Difficult test for function call because of // implicit padding between arguments. func dummy(b byte, c int, d byte, e two, f byte, g float32, h byte) (i byte, j int, k byte, l two, m byte, n float32, o byte) { return b, c, d, e, f, g, h } func TestFunc(t *testing.T) { ret := ValueOf(dummy).Call([]Value{ ValueOf(byte(10)), ValueOf(20), ValueOf(byte(30)), ValueOf(two{40, 50}), ValueOf(byte(60)), ValueOf(float32(70)), ValueOf(byte(80)), }) if len(ret) != 7 { t.Fatalf("Call returned %d values, want 7", len(ret)) } i := byte(ret[0].Uint()) j := int(ret[1].Int()) k := byte(ret[2].Uint()) l := ret[3].Interface().(two) m := byte(ret[4].Uint()) n := float32(ret[5].Float()) o := byte(ret[6].Uint()) if i != 10 || j != 20 || k != 30 || l != (two{40, 50}) || m != 60 || n != 70 || o != 80 { t.Errorf("Call returned %d, %d, %d, %v, %d, %g, %d; want 10, 20, 30, [40, 50], 60, 70, 80", i, j, k, l, m, n, o) } for i, v := range ret { if v.CanAddr() { t.Errorf("result %d is addressable", i) } } } func TestCallConvert(t *testing.T) { v := ValueOf(new(io.ReadWriter)).Elem() f := ValueOf(func(r io.Reader) io.Reader { return r }) out := f.Call([]Value{v}) if len(out) != 1 || out[0].Type() != TypeOf(new(io.Reader)).Elem() || !out[0].IsNil() { t.Errorf("expected [nil], got %v", out) } } type emptyStruct struct{} type nonEmptyStruct struct { member int } func returnEmpty() emptyStruct { return emptyStruct{} } func takesEmpty(e emptyStruct) { } func returnNonEmpty(i int) nonEmptyStruct { return nonEmptyStruct{member: i} } func takesNonEmpty(n nonEmptyStruct) int { return n.member } func TestCallWithStruct(t *testing.T) { r := ValueOf(returnEmpty).Call(nil) if len(r) != 1 || r[0].Type() != TypeOf(emptyStruct{}) { t.Errorf("returning empty struct returned %#v instead", r) } r = ValueOf(takesEmpty).Call([]Value{ValueOf(emptyStruct{})}) if len(r) != 0 { t.Errorf("takesEmpty returned values: %#v", r) } r = ValueOf(returnNonEmpty).Call([]Value{ValueOf(42)}) if len(r) != 1 || r[0].Type() != TypeOf(nonEmptyStruct{}) || r[0].Field(0).Int() != 42 { t.Errorf("returnNonEmpty returned %#v", r) } r = ValueOf(takesNonEmpty).Call([]Value{ValueOf(nonEmptyStruct{member: 42})}) if len(r) != 1 || r[0].Type() != TypeOf(1) || r[0].Int() != 42 { t.Errorf("takesNonEmpty returned %#v", r) } } func TestCallReturnsEmpty(t *testing.T) { // Issue 21717: past-the-end pointer write in Call with // nonzero-sized frame and zero-sized return value. runtime.GC() var finalized uint32 f := func() (emptyStruct, *[2]int64) { i := new([2]int64) // big enough to not be tinyalloc'd, so finalizer always runs when i dies runtime.SetFinalizer(i, func(*[2]int64) { atomic.StoreUint32(&finalized, 1) }) return emptyStruct{}, i } v := ValueOf(f).Call(nil)[0] // out[0] should not alias out[1]'s memory, so the finalizer should run. timeout := time.After(5 * time.Second) for atomic.LoadUint32(&finalized) == 0 { select { case <-timeout: t.Fatal("finalizer did not run") default: } runtime.Gosched() runtime.GC() } runtime.KeepAlive(v) } func TestMakeFunc(t *testing.T) { f := dummy fv := MakeFunc(TypeOf(f), func(in []Value) []Value { return in }) ValueOf(&f).Elem().Set(fv) // Call g with small arguments so that there is // something predictable (and different from the // correct results) in those positions on the stack. g := dummy g(1, 2, 3, two{4, 5}, 6, 7, 8) // Call constructed function f. i, j, k, l, m, n, o := f(10, 20, 30, two{40, 50}, 60, 70, 80) if i != 10 || j != 20 || k != 30 || l != (two{40, 50}) || m != 60 || n != 70 || o != 80 { t.Errorf("Call returned %d, %d, %d, %v, %d, %g, %d; want 10, 20, 30, [40, 50], 60, 70, 80", i, j, k, l, m, n, o) } } func TestMakeFuncInterface(t *testing.T) { fn := func(i int) int { return i } incr := func(in []Value) []Value { return []Value{ValueOf(int(in[0].Int() + 1))} } fv := MakeFunc(TypeOf(fn), incr) ValueOf(&fn).Elem().Set(fv) if r := fn(2); r != 3 { t.Errorf("Call returned %d, want 3", r) } if r := fv.Call([]Value{ValueOf(14)})[0].Int(); r != 15 { t.Errorf("Call returned %d, want 15", r) } if r := fv.Interface().(func(int) int)(26); r != 27 { t.Errorf("Call returned %d, want 27", r) } } func TestMakeFuncVariadic(t *testing.T) { // Test that variadic arguments are packed into a slice and passed as last arg fn := func(_ int, is ...int) []int { return nil } fv := MakeFunc(TypeOf(fn), func(in []Value) []Value { return in[1:2] }) ValueOf(&fn).Elem().Set(fv) r := fn(1, 2, 3) if r[0] != 2 || r[1] != 3 { t.Errorf("Call returned [%v, %v]; want 2, 3", r[0], r[1]) } r = fn(1, []int{2, 3}...) if r[0] != 2 || r[1] != 3 { t.Errorf("Call returned [%v, %v]; want 2, 3", r[0], r[1]) } r = fv.Call([]Value{ValueOf(1), ValueOf(2), ValueOf(3)})[0].Interface().([]int) if r[0] != 2 || r[1] != 3 { t.Errorf("Call returned [%v, %v]; want 2, 3", r[0], r[1]) } r = fv.CallSlice([]Value{ValueOf(1), ValueOf([]int{2, 3})})[0].Interface().([]int) if r[0] != 2 || r[1] != 3 { t.Errorf("Call returned [%v, %v]; want 2, 3", r[0], r[1]) } f := fv.Interface().(func(int, ...int) []int) r = f(1, 2, 3) if r[0] != 2 || r[1] != 3 { t.Errorf("Call returned [%v, %v]; want 2, 3", r[0], r[1]) } r = f(1, []int{2, 3}...) if r[0] != 2 || r[1] != 3 { t.Errorf("Call returned [%v, %v]; want 2, 3", r[0], r[1]) } } // Dummy type that implements io.WriteCloser type WC struct { } func (w *WC) Write(p []byte) (n int, err error) { return 0, nil } func (w *WC) Close() error { return nil } func TestMakeFuncValidReturnAssignments(t *testing.T) { // reflect.Values returned from the wrapped function should be assignment-converted // to the types returned by the result of MakeFunc. // Concrete types should be promotable to interfaces they implement. var f func() error f = MakeFunc(TypeOf(f), func([]Value) []Value { return []Value{ValueOf(io.EOF)} }).Interface().(func() error) f() // Super-interfaces should be promotable to simpler interfaces. var g func() io.Writer g = MakeFunc(TypeOf(g), func([]Value) []Value { var w io.WriteCloser = &WC{} return []Value{ValueOf(&w).Elem()} }).Interface().(func() io.Writer) g() // Channels should be promotable to directional channels. var h func() <-chan int h = MakeFunc(TypeOf(h), func([]Value) []Value { return []Value{ValueOf(make(chan int))} }).Interface().(func() <-chan int) h() // Unnamed types should be promotable to named types. type T struct{ a, b, c int } var i func() T i = MakeFunc(TypeOf(i), func([]Value) []Value { return []Value{ValueOf(struct{ a, b, c int }{a: 1, b: 2, c: 3})} }).Interface().(func() T) i() } func TestMakeFuncInvalidReturnAssignments(t *testing.T) { // Type doesn't implement the required interface. shouldPanic("", func() { var f func() error f = MakeFunc(TypeOf(f), func([]Value) []Value { return []Value{ValueOf(int(7))} }).Interface().(func() error) f() }) // Assigning to an interface with additional methods. shouldPanic("", func() { var f func() io.ReadWriteCloser f = MakeFunc(TypeOf(f), func([]Value) []Value { var w io.WriteCloser = &WC{} return []Value{ValueOf(&w).Elem()} }).Interface().(func() io.ReadWriteCloser) f() }) // Directional channels can't be assigned to bidirectional ones. shouldPanic("", func() { var f func() chan int f = MakeFunc(TypeOf(f), func([]Value) []Value { var c <-chan int = make(chan int) return []Value{ValueOf(c)} }).Interface().(func() chan int) f() }) // Two named types which are otherwise identical. shouldPanic("", func() { type T struct{ a, b, c int } type U struct{ a, b, c int } var f func() T f = MakeFunc(TypeOf(f), func([]Value) []Value { return []Value{ValueOf(U{a: 1, b: 2, c: 3})} }).Interface().(func() T) f() }) } */ type Point struct { x, y int } // This will be index 0. func (p Point) AnotherMethod(scale int) int { return -1 } // This will be index 1. func (p Point) Dist(scale int) int { //println("Point.Dist", p.x, p.y, scale) return p.x*p.x*scale + p.y*p.y*scale } // This will be index 2. func (p Point) GCMethod(k int) int { runtime.GC() return k + p.x } // This will be index 3. func (p Point) NoArgs() { // Exercise no-argument/no-result paths. } // This will be index 4. func (p Point) TotalDist(points ...Point) int { tot := 0 for _, q := range points { dx := q.x - p.x dy := q.y - p.y tot += dx*dx + dy*dy // Should call Sqrt, but it's just a test. } return tot } // This will be index 5. func (p *Point) Int64Method(x int64) int64 { return x } // This will be index 6. func (p *Point) Int32Method(x int32) int32 { return x } /* // TODO(tinygo): missing method support func TestMethod(t *testing.T) { // Non-curried method of type. p := Point{3, 4} i := TypeOf(p).Method(1).Func.Call([]Value{ValueOf(p), ValueOf(10)})[0].Int() if i != 250 { t.Errorf("Type Method returned %d; want 250", i) } m, ok := TypeOf(p).MethodByName("Dist") if !ok { t.Fatalf("method by name failed") } i = m.Func.Call([]Value{ValueOf(p), ValueOf(11)})[0].Int() if i != 275 { t.Errorf("Type MethodByName returned %d; want 275", i) } m, ok = TypeOf(p).MethodByName("NoArgs") if !ok { t.Fatalf("method by name failed") } n := len(m.Func.Call([]Value{ValueOf(p)})) if n != 0 { t.Errorf("NoArgs returned %d values; want 0", n) } i = TypeOf(&p).Method(1).Func.Call([]Value{ValueOf(&p), ValueOf(12)})[0].Int() if i != 300 { t.Errorf("Pointer Type Method returned %d; want 300", i) } m, ok = TypeOf(&p).MethodByName("Dist") if !ok { t.Fatalf("ptr method by name failed") } i = m.Func.Call([]Value{ValueOf(&p), ValueOf(13)})[0].Int() if i != 325 { t.Errorf("Pointer Type MethodByName returned %d; want 325", i) } m, ok = TypeOf(&p).MethodByName("NoArgs") if !ok { t.Fatalf("method by name failed") } n = len(m.Func.Call([]Value{ValueOf(&p)})) if n != 0 { t.Errorf("NoArgs returned %d values; want 0", n) } _, ok = TypeOf(&p).MethodByName("AA") if ok { t.Errorf(`MethodByName("AA") should have failed`) } _, ok = TypeOf(&p).MethodByName("ZZ") if ok { t.Errorf(`MethodByName("ZZ") should have failed`) } // Curried method of value. tfunc := TypeOf((func(int) int)(nil)) v := ValueOf(p).Method(1) if tt := v.Type(); tt != tfunc { t.Errorf("Value Method Type is %s; want %s", tt, tfunc) } i = v.Call([]Value{ValueOf(14)})[0].Int() if i != 350 { t.Errorf("Value Method returned %d; want 350", i) } v = ValueOf(p).MethodByName("Dist") if tt := v.Type(); tt != tfunc { t.Errorf("Value MethodByName Type is %s; want %s", tt, tfunc) } i = v.Call([]Value{ValueOf(15)})[0].Int() if i != 375 { t.Errorf("Value MethodByName returned %d; want 375", i) } v = ValueOf(p).MethodByName("NoArgs") v.Call(nil) // Curried method of pointer. v = ValueOf(&p).Method(1) if tt := v.Type(); tt != tfunc { t.Errorf("Pointer Value Method Type is %s; want %s", tt, tfunc) } i = v.Call([]Value{ValueOf(16)})[0].Int() if i != 400 { t.Errorf("Pointer Value Method returned %d; want 400", i) } v = ValueOf(&p).MethodByName("Dist") if tt := v.Type(); tt != tfunc { t.Errorf("Pointer Value MethodByName Type is %s; want %s", tt, tfunc) } i = v.Call([]Value{ValueOf(17)})[0].Int() if i != 425 { t.Errorf("Pointer Value MethodByName returned %d; want 425", i) } v = ValueOf(&p).MethodByName("NoArgs") v.Call(nil) // Curried method of interface value. // Have to wrap interface value in a struct to get at it. // Passing it to ValueOf directly would // access the underlying Point, not the interface. var x interface { Dist(int) int } = p pv := ValueOf(&x).Elem() v = pv.Method(0) if tt := v.Type(); tt != tfunc { t.Errorf("Interface Method Type is %s; want %s", tt, tfunc) } i = v.Call([]Value{ValueOf(18)})[0].Int() if i != 450 { t.Errorf("Interface Method returned %d; want 450", i) } v = pv.MethodByName("Dist") if tt := v.Type(); tt != tfunc { t.Errorf("Interface MethodByName Type is %s; want %s", tt, tfunc) } i = v.Call([]Value{ValueOf(19)})[0].Int() if i != 475 { t.Errorf("Interface MethodByName returned %d; want 475", i) } } func TestMethodValue(t *testing.T) { p := Point{3, 4} var i int64 // Check that method value have the same underlying code pointers. if p1, p2 := ValueOf(Point{1, 1}).Method(1), ValueOf(Point{2, 2}).Method(1); p1.Pointer() != p2.Pointer() { t.Errorf("methodValueCall mismatched: %v - %v", p1, p2) } // Curried method of value. tfunc := TypeOf((func(int) int)(nil)) v := ValueOf(p).Method(1) if tt := v.Type(); tt != tfunc { t.Errorf("Value Method Type is %s; want %s", tt, tfunc) } i = ValueOf(v.Interface()).Call([]Value{ValueOf(10)})[0].Int() if i != 250 { t.Errorf("Value Method returned %d; want 250", i) } v = ValueOf(p).MethodByName("Dist") if tt := v.Type(); tt != tfunc { t.Errorf("Value MethodByName Type is %s; want %s", tt, tfunc) } i = ValueOf(v.Interface()).Call([]Value{ValueOf(11)})[0].Int() if i != 275 { t.Errorf("Value MethodByName returned %d; want 275", i) } v = ValueOf(p).MethodByName("NoArgs") ValueOf(v.Interface()).Call(nil) v.Interface().(func())() // Curried method of pointer. v = ValueOf(&p).Method(1) if tt := v.Type(); tt != tfunc { t.Errorf("Pointer Value Method Type is %s; want %s", tt, tfunc) } i = ValueOf(v.Interface()).Call([]Value{ValueOf(12)})[0].Int() if i != 300 { t.Errorf("Pointer Value Method returned %d; want 300", i) } v = ValueOf(&p).MethodByName("Dist") if tt := v.Type(); tt != tfunc { t.Errorf("Pointer Value MethodByName Type is %s; want %s", tt, tfunc) } i = ValueOf(v.Interface()).Call([]Value{ValueOf(13)})[0].Int() if i != 325 { t.Errorf("Pointer Value MethodByName returned %d; want 325", i) } v = ValueOf(&p).MethodByName("NoArgs") ValueOf(v.Interface()).Call(nil) v.Interface().(func())() // Curried method of pointer to pointer. pp := &p v = ValueOf(&pp).Elem().Method(1) if tt := v.Type(); tt != tfunc { t.Errorf("Pointer Pointer Value Method Type is %s; want %s", tt, tfunc) } i = ValueOf(v.Interface()).Call([]Value{ValueOf(14)})[0].Int() if i != 350 { t.Errorf("Pointer Pointer Value Method returned %d; want 350", i) } v = ValueOf(&pp).Elem().MethodByName("Dist") if tt := v.Type(); tt != tfunc { t.Errorf("Pointer Pointer Value MethodByName Type is %s; want %s", tt, tfunc) } i = ValueOf(v.Interface()).Call([]Value{ValueOf(15)})[0].Int() if i != 375 { t.Errorf("Pointer Pointer Value MethodByName returned %d; want 375", i) } // Curried method of interface value. // Have to wrap interface value in a struct to get at it. // Passing it to ValueOf directly would // access the underlying Point, not the interface. var s = struct { X interface { Dist(int) int } }{p} pv := ValueOf(s).Field(0) v = pv.Method(0) if tt := v.Type(); tt != tfunc { t.Errorf("Interface Method Type is %s; want %s", tt, tfunc) } i = ValueOf(v.Interface()).Call([]Value{ValueOf(16)})[0].Int() if i != 400 { t.Errorf("Interface Method returned %d; want 400", i) } v = pv.MethodByName("Dist") if tt := v.Type(); tt != tfunc { t.Errorf("Interface MethodByName Type is %s; want %s", tt, tfunc) } i = ValueOf(v.Interface()).Call([]Value{ValueOf(17)})[0].Int() if i != 425 { t.Errorf("Interface MethodByName returned %d; want 425", i) } // For issue #33628: method args are not stored at the right offset // on amd64p32. m64 := ValueOf(&p).MethodByName("Int64Method").Interface().(func(int64) int64) if x := m64(123); x != 123 { t.Errorf("Int64Method returned %d; want 123", x) } m32 := ValueOf(&p).MethodByName("Int32Method").Interface().(func(int32) int32) if x := m32(456); x != 456 { t.Errorf("Int32Method returned %d; want 456", x) } } func TestVariadicMethodValue(t *testing.T) { p := Point{3, 4} points := []Point{{20, 21}, {22, 23}, {24, 25}} want := int64(p.TotalDist(points[0], points[1], points[2])) // Variadic method of type. tfunc := TypeOf((func(Point, ...Point) int)(nil)) if tt := TypeOf(p).Method(4).Type; tt != tfunc { t.Errorf("Variadic Method Type from TypeOf is %s; want %s", tt, tfunc) } // Curried method of value. tfunc = TypeOf((func(...Point) int)(nil)) v := ValueOf(p).Method(4) if tt := v.Type(); tt != tfunc { t.Errorf("Variadic Method Type is %s; want %s", tt, tfunc) } i := ValueOf(v.Interface()).Call([]Value{ValueOf(points[0]), ValueOf(points[1]), ValueOf(points[2])})[0].Int() if i != want { t.Errorf("Variadic Method returned %d; want %d", i, want) } i = ValueOf(v.Interface()).CallSlice([]Value{ValueOf(points)})[0].Int() if i != want { t.Errorf("Variadic Method CallSlice returned %d; want %d", i, want) } f := v.Interface().(func(...Point) int) i = int64(f(points[0], points[1], points[2])) if i != want { t.Errorf("Variadic Method Interface returned %d; want %d", i, want) } i = int64(f(points...)) if i != want { t.Errorf("Variadic Method Interface Slice returned %d; want %d", i, want) } } type DirectIfaceT struct { p *int } func (d DirectIfaceT) M() int { return *d.p } func TestDirectIfaceMethod(t *testing.T) { x := 42 v := DirectIfaceT{&x} typ := TypeOf(v) m, ok := typ.MethodByName("M") if !ok { t.Fatalf("cannot find method M") } in := []Value{ValueOf(v)} out := m.Func.Call(in) if got := out[0].Int(); got != 42 { t.Errorf("Call with value receiver got %d, want 42", got) } pv := &v typ = TypeOf(pv) m, ok = typ.MethodByName("M") if !ok { t.Fatalf("cannot find method M") } in = []Value{ValueOf(pv)} out = m.Func.Call(in) if got := out[0].Int(); got != 42 { t.Errorf("Call with pointer receiver got %d, want 42", got) } } // Reflect version of $GOROOT/test/method5.go // Concrete types implementing M method. // Smaller than a word, word-sized, larger than a word. // Value and pointer receivers. type Tinter interface { M(int, byte) (byte, int) } type Tsmallv byte func (v Tsmallv) M(x int, b byte) (byte, int) { return b, x + int(v) } type Tsmallp byte func (p *Tsmallp) M(x int, b byte) (byte, int) { return b, x + int(*p) } type Twordv uintptr func (v Twordv) M(x int, b byte) (byte, int) { return b, x + int(v) } type Twordp uintptr func (p *Twordp) M(x int, b byte) (byte, int) { return b, x + int(*p) } type Tbigv [2]uintptr func (v Tbigv) M(x int, b byte) (byte, int) { return b, x + int(v[0]) + int(v[1]) } type Tbigp [2]uintptr func (p *Tbigp) M(x int, b byte) (byte, int) { return b, x + int(p[0]) + int(p[1]) } type tinter interface { m(int, byte) (byte, int) } // Embedding via pointer. type Tm1 struct { Tm2 } type Tm2 struct { *Tm3 } type Tm3 struct { *Tm4 } type Tm4 struct { } func (t4 Tm4) M(x int, b byte) (byte, int) { return b, x + 40 } func TestMethod5(t *testing.T) { CheckF := func(name string, f func(int, byte) (byte, int), inc int) { b, x := f(1000, 99) if b != 99 || x != 1000+inc { t.Errorf("%s(1000, 99) = %v, %v, want 99, %v", name, b, x, 1000+inc) } } CheckV := func(name string, i Value, inc int) { bx := i.Method(0).Call([]Value{ValueOf(1000), ValueOf(byte(99))}) b := bx[0].Interface() x := bx[1].Interface() if b != byte(99) || x != 1000+inc { t.Errorf("direct %s.M(1000, 99) = %v, %v, want 99, %v", name, b, x, 1000+inc) } CheckF(name+".M", i.Method(0).Interface().(func(int, byte) (byte, int)), inc) } var TinterType = TypeOf(new(Tinter)).Elem() CheckI := func(name string, i any, inc int) { v := ValueOf(i) CheckV(name, v, inc) CheckV("(i="+name+")", v.Convert(TinterType), inc) } sv := Tsmallv(1) CheckI("sv", sv, 1) CheckI("&sv", &sv, 1) sp := Tsmallp(2) CheckI("&sp", &sp, 2) wv := Twordv(3) CheckI("wv", wv, 3) CheckI("&wv", &wv, 3) wp := Twordp(4) CheckI("&wp", &wp, 4) bv := Tbigv([2]uintptr{5, 6}) CheckI("bv", bv, 11) CheckI("&bv", &bv, 11) bp := Tbigp([2]uintptr{7, 8}) CheckI("&bp", &bp, 15) t4 := Tm4{} t3 := Tm3{&t4} t2 := Tm2{&t3} t1 := Tm1{t2} CheckI("t4", t4, 40) CheckI("&t4", &t4, 40) CheckI("t3", t3, 40) CheckI("&t3", &t3, 40) CheckI("t2", t2, 40) CheckI("&t2", &t2, 40) CheckI("t1", t1, 40) CheckI("&t1", &t1, 40) var tnil Tinter vnil := ValueOf(&tnil).Elem() shouldPanic("Method", func() { vnil.Method(0) }) } func TestInterfaceSet(t *testing.T) { p := &Point{3, 4} var s struct { I any P interface { Dist(int) int } } sv := ValueOf(&s).Elem() sv.Field(0).Set(ValueOf(p)) if q := s.I.(*Point); q != p { t.Errorf("i: have %p want %p", q, p) } pv := sv.Field(1) pv.Set(ValueOf(p)) if q := s.P.(*Point); q != p { t.Errorf("i: have %p want %p", q, p) } i := pv.Method(0).Call([]Value{ValueOf(10)})[0].Int() if i != 250 { t.Errorf("Interface Method returned %d; want 250", i) } } */ type T1 struct { a string int } func TestAnonymousFields(t *testing.T) { var field StructField var ok bool var t1 T1 type1 := TypeOf(t1) if field, ok = type1.FieldByName("int"); !ok { t.Fatal("no field 'int'") } if field.Index[0] != 1 { t.Error("field index should be 1; is", field.Index) } } type FTest struct { s any name string index []int value int } type D1 struct { d int } type D2 struct { d int } type S0 struct { A, B, C int D1 D2 } type S1 struct { B int S0 } type S2 struct { A int *S1 } type S1x struct { S1 } type S1y struct { S1 } type S3 struct { S1x S2 D, E int *S1y } type S4 struct { *S4 A int } // The X in S6 and S7 annihilate, but they also block the X in S8.S9. type S5 struct { S6 S7 S8 } type S6 struct { X int } type S7 S6 type S8 struct { S9 } type S9 struct { X int Y int } // The X in S11.S6 and S12.S6 annihilate, but they also block the X in S13.S8.S9. type S10 struct { S11 S12 S13 } type S11 struct { S6 } type S12 struct { S6 } type S13 struct { S8 } // The X in S15.S11.S1 and S16.S11.S1 annihilate. type S14 struct { S15 S16 } type S15 struct { S11 } type S16 struct { S11 } var fieldTests = []FTest{ {struct{}{}, "", nil, 0}, {struct{}{}, "Foo", nil, 0}, {S0{A: 'a'}, "A", []int{0}, 'a'}, {S0{}, "D", nil, 0}, {S1{S0: S0{A: 'a'}}, "A", []int{1, 0}, 'a'}, {S1{B: 'b'}, "B", []int{0}, 'b'}, {S1{}, "S0", []int{1}, 0}, {S1{S0: S0{C: 'c'}}, "C", []int{1, 2}, 'c'}, {S2{A: 'a'}, "A", []int{0}, 'a'}, {S2{}, "S1", []int{1}, 0}, {S2{S1: &S1{B: 'b'}}, "B", []int{1, 0}, 'b'}, {S2{S1: &S1{S0: S0{C: 'c'}}}, "C", []int{1, 1, 2}, 'c'}, {S2{}, "D", nil, 0}, {S3{}, "S1", nil, 0}, {S3{S2: S2{A: 'a'}}, "A", []int{1, 0}, 'a'}, {S3{}, "B", nil, 0}, {S3{D: 'd'}, "D", []int{2}, 0}, {S3{E: 'e'}, "E", []int{3}, 'e'}, {S4{A: 'a'}, "A", []int{1}, 'a'}, {S4{}, "B", nil, 0}, {S5{}, "X", nil, 0}, {S5{}, "Y", []int{2, 0, 1}, 0}, {S10{}, "X", nil, 0}, {S10{}, "Y", []int{2, 0, 0, 1}, 0}, {S14{}, "X", nil, 0}, } func TestFieldByIndex(t *testing.T) { for _, test := range fieldTests { s := TypeOf(test.s) f := s.FieldByIndex(test.index) if f.Name != "" { if test.index != nil { if f.Name != test.name { t.Errorf("%s.%s found; want %s", s.Name(), f.Name, test.name) } } else { t.Errorf("%s.%s found", s.Name(), f.Name) } } else if len(test.index) > 0 { t.Errorf("%s.%s not found", s.Name(), test.name) } if test.value != 0 { v := ValueOf(test.s).FieldByIndex(test.index) if v.IsValid() { if x, ok := v.Interface().(int); ok { if x != test.value { t.Errorf("%s%v is %d; want %d", s.Name(), test.index, x, test.value) } } else { t.Errorf("%s%v value not an int", s.Name(), test.index) } } else { t.Errorf("%s%v value not found", s.Name(), test.index) } } } } /* func TestFieldByName(t *testing.T) { for _, test := range fieldTests { s := TypeOf(test.s) f, found := s.FieldByName(test.name) if found { if test.index != nil { // Verify field depth and index. if len(f.Index) != len(test.index) { t.Errorf("%s.%s depth %d; want %d: %v vs %v", s.Name(), test.name, len(f.Index), len(test.index), f.Index, test.index) } else { for i, x := range f.Index { if x != test.index[i] { t.Errorf("%s.%s.Index[%d] is %d; want %d", s.Name(), test.name, i, x, test.index[i]) } } } } else { t.Errorf("%s.%s found", s.Name(), f.Name) } } else if len(test.index) > 0 { t.Errorf("%s.%s not found", s.Name(), test.name) } if test.value != 0 { v := ValueOf(test.s).FieldByName(test.name) if v.IsValid() { if x, ok := v.Interface().(int); ok { if x != test.value { t.Errorf("%s.%s is %d; want %d", s.Name(), test.name, x, test.value) } } else { t.Errorf("%s.%s value not an int", s.Name(), test.name) } } else { t.Errorf("%s.%s value not found", s.Name(), test.name) } } } } */ func TestImportPath(t *testing.T) { tests := []struct { t Type path string }{ {TypeOf(&base64.Encoding{}).Elem(), "encoding/base64"}, {TypeOf(int(0)), ""}, {TypeOf(int8(0)), ""}, {TypeOf(int16(0)), ""}, {TypeOf(int32(0)), ""}, {TypeOf(int64(0)), ""}, {TypeOf(uint(0)), ""}, {TypeOf(uint8(0)), ""}, {TypeOf(uint16(0)), ""}, {TypeOf(uint32(0)), ""}, {TypeOf(uint64(0)), ""}, {TypeOf(uintptr(0)), ""}, {TypeOf(float32(0)), ""}, {TypeOf(float64(0)), ""}, {TypeOf(complex64(0)), ""}, {TypeOf(complex128(0)), ""}, {TypeOf(byte(0)), ""}, {TypeOf(rune(0)), ""}, {TypeOf([]byte(nil)), ""}, {TypeOf([]rune(nil)), ""}, {TypeOf(string("")), ""}, {TypeOf((*any)(nil)).Elem(), ""}, {TypeOf((*byte)(nil)), ""}, {TypeOf((*rune)(nil)), ""}, {TypeOf((*int64)(nil)), ""}, {TypeOf(map[string]int{}), ""}, {TypeOf((*error)(nil)).Elem(), ""}, {TypeOf((*Point)(nil)), ""}, {TypeOf((*Point)(nil)).Elem(), "reflect_test"}, } for _, test := range tests { if path := test.t.PkgPath(); path != test.path { t.Errorf("%v.PkgPath() = %q, want %q", test.t, path, test.path) } } } func TestFieldPkgPath(t *testing.T) { type x int typ := TypeOf(struct { Exported string unexported string OtherPkgFields int // issue 21702 *x // issue 21122 }{}) type pkgpathTest struct { index []int pkgPath string embedded bool exported bool } checkPkgPath := func(name string, s []pkgpathTest) { for _, test := range s { f := typ.FieldByIndex(test.index) if got, want := f.PkgPath, test.pkgPath; got != want { t.Errorf("%s: Field(%d).PkgPath = %q, want %q", name, test.index, got, want) } if got, want := f.Anonymous, test.embedded; got != want { t.Errorf("%s: Field(%d).Anonymous = %v, want %v", name, test.index, got, want) } if got, want := f.IsExported(), test.exported; got != want { t.Errorf("%s: Field(%d).IsExported = %v, want %v", name, test.index, got, want) } } } checkPkgPath("testStruct", []pkgpathTest{ {[]int{0}, "", false, true}, // Exported {[]int{1}, "reflect_test", false, false}, // unexported {[]int{2}, "", true, true}, // OtherPkgFields {[]int{2, 0}, "", false, true}, // OtherExported {[]int{2, 1}, "reflect", false, false}, // otherUnexported {[]int{3}, "reflect_test", true, false}, // int {[]int{4}, "reflect_test", true, false}, // *x }) type localOtherPkgFields OtherPkgFields typ = TypeOf(localOtherPkgFields{}) checkPkgPath("localOtherPkgFields", []pkgpathTest{ {[]int{0}, "", false, true}, // OtherExported {[]int{1}, "reflect", false, false}, // otherUnexported }) } /* func TestMethodPkgPath(t *testing.T) { type I interface { x() X() } typ := TypeOf((*interface { I y() Y() })(nil)).Elem() tests := []struct { name string pkgPath string exported bool }{ {"X", "", true}, {"Y", "", true}, {"x", "reflect_test", false}, {"y", "reflect_test", false}, } for _, test := range tests { m, _ := typ.MethodByName(test.name) if got, want := m.PkgPath, test.pkgPath; got != want { t.Errorf("MethodByName(%q).PkgPath = %q, want %q", test.name, got, want) } if got, want := m.IsExported(), test.exported; got != want { t.Errorf("MethodByName(%q).IsExported = %v, want %v", test.name, got, want) } } } func TestVariadicType(t *testing.T) { // Test example from Type documentation. var f func(x int, y ...float64) typ := TypeOf(f) if typ.NumIn() == 2 && typ.In(0) == TypeOf(int(0)) { sl := typ.In(1) if sl.Kind() == Slice { if sl.Elem() == TypeOf(0.0) { // ok return } } } // Failed t.Errorf("want NumIn() = 2, In(0) = int, In(1) = []float64") s := fmt.Sprintf("have NumIn() = %d", typ.NumIn()) for i := 0; i < typ.NumIn(); i++ { s += fmt.Sprintf(", In(%d) = %s", i, typ.In(i)) } t.Error(s) } type inner struct { x int } type outer struct { y int inner } func (*inner) M() {} func (*outer) M() {} func TestNestedMethods(t *testing.T) { typ := TypeOf((*outer)(nil)) if typ.NumMethod() != 1 || typ.Method(0).Func.UnsafePointer() != ValueOf((*outer).M).UnsafePointer() { t.Errorf("Wrong method table for outer: (M=%p)", (*outer).M) for i := 0; i < typ.NumMethod(); i++ { m := typ.Method(i) t.Errorf("\t%d: %s %p\n", i, m.Name, m.Func.UnsafePointer()) } } } */ type unexp struct{} func (*unexp) f() (int32, int8) { return 7, 7 } func (*unexp) g() (int64, int8) { return 8, 8 } type unexpI interface { f() (int32, int8) } var unexpi unexpI = new(unexp) func TestUnexportedMethods(t *testing.T) { typ := TypeOf(unexpi) if got := typ.NumMethod(); got != 0 { t.Errorf("NumMethod=%d, want 0 satisfied methods", got) } } type InnerInt struct { X int } type OuterInt struct { Y int InnerInt } func (i *InnerInt) M() int { return i.X } /* func TestEmbeddedMethods(t *testing.T) { typ := TypeOf((*OuterInt)(nil)) if typ.NumMethod() != 1 || typ.Method(0).Func.UnsafePointer() != ValueOf((*OuterInt).M).UnsafePointer() { t.Errorf("Wrong method table for OuterInt: (m=%p)", (*OuterInt).M) for i := 0; i < typ.NumMethod(); i++ { m := typ.Method(i) t.Errorf("\t%d: %s %p\n", i, m.Name, m.Func.UnsafePointer()) } } i := &InnerInt{3} if v := ValueOf(i).Method(0).Call(nil)[0].Int(); v != 3 { t.Errorf("i.M() = %d, want 3", v) } o := &OuterInt{1, InnerInt{2}} if v := ValueOf(o).Method(0).Call(nil)[0].Int(); v != 2 { t.Errorf("i.M() = %d, want 2", v) } f := (*OuterInt).M if v := f(o); v != 2 { t.Errorf("f(o) = %d, want 2", v) } } */ type FuncDDD func(...any) error func (f FuncDDD) M() {} func TestNumMethodOnDDD(t *testing.T) { rv := ValueOf((FuncDDD)(nil)) if n := rv.NumMethod(); n != 1 { t.Fatalf("NumMethod()=%d, want 1", n) } } /* func TestPtrTo(t *testing.T) { // This block of code means that the ptrToThis field of the // reflect data for *unsafe.Pointer is non zero, see // https://golang.org/issue/19003 var x unsafe.Pointer var y = &x var z = &y var i int typ := TypeOf(z) for i = 0; i < 100; i++ { typ = PointerTo(typ) } for i = 0; i < 100; i++ { typ = typ.Elem() } if typ != TypeOf(z) { t.Errorf("after 100 PointerTo and Elem, have %s, want %s", typ, TypeOf(z)) } } func TestPtrToGC(t *testing.T) { type T *uintptr tt := TypeOf(T(nil)) pt := PointerTo(tt) const n = 100 var x []any for i := 0; i < n; i++ { v := New(pt) p := new(*uintptr) *p = new(uintptr) **p = uintptr(i) v.Elem().Set(ValueOf(p).Convert(pt)) x = append(x, v.Interface()) } runtime.GC() for i, xi := range x { k := ValueOf(xi).Elem().Elem().Elem().Interface().(uintptr) if k != uintptr(i) { t.Errorf("lost x[%d] = %d, want %d", i, k, i) } } } */ func TestAddr(t *testing.T) { var p struct { X, Y int } v := ValueOf(&p) v = v.Elem() v = v.Addr() v = v.Elem() v = v.Field(0) v.SetInt(2) if p.X != 2 { t.Errorf("Addr.Elem.Set failed to set value") } // Again but take address of the ValueOf value. // Exercises generation of PtrTypes not present in the binary. q := &p v = ValueOf(&q).Elem() v = v.Addr() v = v.Elem() v = v.Elem() v = v.Addr() v = v.Elem() v = v.Field(0) v.SetInt(3) if p.X != 3 { t.Errorf("Addr.Elem.Set failed to set value") } // Starting without pointer we should get changed value // in interface. qq := p v = ValueOf(&qq).Elem() v0 := v v = v.Addr() v = v.Elem() v = v.Field(0) v.SetInt(4) if p.X != 3 { // should be unchanged from last time t.Errorf("somehow value Set changed original p") } p = v0.Interface().(struct { X, Y int }) if p.X != 4 { t.Errorf("Addr.Elem.Set valued to set value in top value") } // Verify that taking the address of a type gives us a pointer // which we can convert back using the usual interface // notation. var s struct { B *bool } ps := ValueOf(&s).Elem().Field(0).Addr().Interface() *(ps.(**bool)) = new(bool) if s.B == nil { t.Errorf("Addr.Interface direct assignment failed") } } func noAlloc(t *testing.T, n int, f func(int)) { if testing.Short() { t.Skip("skipping malloc count in short mode") } if runtime.GOMAXPROCS(0) > 1 { t.Skip("skipping; GOMAXPROCS>1") } i := -1 allocs := testing.AllocsPerRun(n, func() { f(i) i++ }) if allocs > 0 { t.Errorf("%d iterations: got %v mallocs, want 0", n, allocs) } } func TestAllocations(t *testing.T) { noAlloc(t, 100, func(j int) { var i any var v Value // We can uncomment this when compiler escape analysis // is good enough to see that the integer assigned to i // does not escape and therefore need not be allocated. // // i = 42 + j // v = ValueOf(i) // if int(v.Int()) != 42+j { // panic("wrong int") // } i = func(j int) int { return j } v = ValueOf(i) if v.Interface().(func(int) int)(j) != j { panic("wrong result") } }) } func TestSmallNegativeInt(t *testing.T) { i := int16(-1) v := ValueOf(i) if v.Int() != -1 { t.Errorf("int16(-1).Int() returned %v", v.Int()) } } func TestIndex(t *testing.T) { xs := []byte{1, 2, 3, 4, 5, 6, 7, 8} v := ValueOf(xs).Index(3).Interface().(byte) if v != xs[3] { t.Errorf("xs.Index(3) = %v; expected %v", v, xs[3]) } xa := [8]byte{10, 20, 30, 40, 50, 60, 70, 80} v = ValueOf(xa).Index(2).Interface().(byte) if v != xa[2] { t.Errorf("xa.Index(2) = %v; expected %v", v, xa[2]) } s := "0123456789" v = ValueOf(s).Index(3).Interface().(byte) if v != s[3] { t.Errorf("s.Index(3) = %v; expected %v", v, s[3]) } } /* func TestSlice(t *testing.T) { xs := []int{1, 2, 3, 4, 5, 6, 7, 8} v := ValueOf(xs).Slice(3, 5).Interface().([]int) if len(v) != 2 { t.Errorf("len(xs.Slice(3, 5)) = %d", len(v)) } if cap(v) != 5 { t.Errorf("cap(xs.Slice(3, 5)) = %d", cap(v)) } if !DeepEqual(v[0:5], xs[3:]) { t.Errorf("xs.Slice(3, 5)[0:5] = %v", v[0:5]) } xa := [8]int{10, 20, 30, 40, 50, 60, 70, 80} v = ValueOf(&xa).Elem().Slice(2, 5).Interface().([]int) if len(v) != 3 { t.Errorf("len(xa.Slice(2, 5)) = %d", len(v)) } if cap(v) != 6 { t.Errorf("cap(xa.Slice(2, 5)) = %d", cap(v)) } if !DeepEqual(v[0:6], xa[2:]) { t.Errorf("xs.Slice(2, 5)[0:6] = %v", v[0:6]) } s := "0123456789" vs := ValueOf(s).Slice(3, 5).Interface().(string) if vs != s[3:5] { t.Errorf("s.Slice(3, 5) = %q; expected %q", vs, s[3:5]) } rv := ValueOf(&xs).Elem() rv = rv.Slice(3, 4) ptr2 := rv.UnsafePointer() rv = rv.Slice(5, 5) ptr3 := rv.UnsafePointer() if ptr3 != ptr2 { t.Errorf("xs.Slice(3,4).Slice3(5,5).UnsafePointer() = %p, want %p", ptr3, ptr2) } } func TestSlice3(t *testing.T) { xs := []int{1, 2, 3, 4, 5, 6, 7, 8} v := ValueOf(xs).Slice3(3, 5, 7).Interface().([]int) if len(v) != 2 { t.Errorf("len(xs.Slice3(3, 5, 7)) = %d", len(v)) } if cap(v) != 4 { t.Errorf("cap(xs.Slice3(3, 5, 7)) = %d", cap(v)) } if !DeepEqual(v[0:4], xs[3:7:7]) { t.Errorf("xs.Slice3(3, 5, 7)[0:4] = %v", v[0:4]) } rv := ValueOf(&xs).Elem() shouldPanic("Slice3", func() { rv.Slice3(1, 2, 1) }) shouldPanic("Slice3", func() { rv.Slice3(1, 1, 11) }) shouldPanic("Slice3", func() { rv.Slice3(2, 2, 1) }) xa := [8]int{10, 20, 30, 40, 50, 60, 70, 80} v = ValueOf(&xa).Elem().Slice3(2, 5, 6).Interface().([]int) if len(v) != 3 { t.Errorf("len(xa.Slice(2, 5, 6)) = %d", len(v)) } if cap(v) != 4 { t.Errorf("cap(xa.Slice(2, 5, 6)) = %d", cap(v)) } if !DeepEqual(v[0:4], xa[2:6:6]) { t.Errorf("xs.Slice(2, 5, 6)[0:4] = %v", v[0:4]) } rv = ValueOf(&xa).Elem() shouldPanic("Slice3", func() { rv.Slice3(1, 2, 1) }) shouldPanic("Slice3", func() { rv.Slice3(1, 1, 11) }) shouldPanic("Slice3", func() { rv.Slice3(2, 2, 1) }) s := "hello world" rv = ValueOf(&s).Elem() shouldPanic("Slice3", func() { rv.Slice3(1, 2, 3) }) rv = ValueOf(&xs).Elem() rv = rv.Slice3(3, 5, 7) ptr2 := rv.UnsafePointer() rv = rv.Slice3(4, 4, 4) ptr3 := rv.UnsafePointer() if ptr3 != ptr2 { t.Errorf("xs.Slice3(3,5,7).Slice3(4,4,4).UnsafePointer() = %p, want %p", ptr3, ptr2) } } func TestSetLenCap(t *testing.T) { xs := []int{1, 2, 3, 4, 5, 6, 7, 8} xa := [8]int{10, 20, 30, 40, 50, 60, 70, 80} vs := ValueOf(&xs).Elem() shouldPanic("SetLen", func() { vs.SetLen(10) }) shouldPanic("SetCap", func() { vs.SetCap(10) }) shouldPanic("SetLen", func() { vs.SetLen(-1) }) shouldPanic("SetCap", func() { vs.SetCap(-1) }) shouldPanic("SetCap", func() { vs.SetCap(6) }) // smaller than len vs.SetLen(5) if len(xs) != 5 || cap(xs) != 8 { t.Errorf("after SetLen(5), len, cap = %d, %d, want 5, 8", len(xs), cap(xs)) } vs.SetCap(6) if len(xs) != 5 || cap(xs) != 6 { t.Errorf("after SetCap(6), len, cap = %d, %d, want 5, 6", len(xs), cap(xs)) } vs.SetCap(5) if len(xs) != 5 || cap(xs) != 5 { t.Errorf("after SetCap(5), len, cap = %d, %d, want 5, 5", len(xs), cap(xs)) } shouldPanic("SetCap", func() { vs.SetCap(4) }) // smaller than len shouldPanic("SetLen", func() { vs.SetLen(6) }) // bigger than cap va := ValueOf(&xa).Elem() shouldPanic("SetLen", func() { va.SetLen(8) }) shouldPanic("SetCap", func() { va.SetCap(8) }) } func TestVariadic(t *testing.T) { var b strings.Builder V := ValueOf b.Reset() V(fmt.Fprintf).Call([]Value{V(&b), V("%s, %d world"), V("hello"), V(42)}) if b.String() != "hello, 42 world" { t.Errorf("after Fprintf Call: %q != %q", b.String(), "hello 42 world") } b.Reset() V(fmt.Fprintf).CallSlice([]Value{V(&b), V("%s, %d world"), V([]any{"hello", 42})}) if b.String() != "hello, 42 world" { t.Errorf("after Fprintf CallSlice: %q != %q", b.String(), "hello 42 world") } } func TestFuncArg(t *testing.T) { f1 := func(i int, f func(int) int) int { return f(i) } f2 := func(i int) int { return i + 1 } r := ValueOf(f1).Call([]Value{ValueOf(100), ValueOf(f2)}) if r[0].Int() != 101 { t.Errorf("function returned %d, want 101", r[0].Int()) } } func TestStructArg(t *testing.T) { type padded struct { B string C int32 } var ( gotA padded gotB uint32 wantA = padded{"3", 4} wantB = uint32(5) ) f := func(a padded, b uint32) { gotA, gotB = a, b } ValueOf(f).Call([]Value{ValueOf(wantA), ValueOf(wantB)}) if gotA != wantA || gotB != wantB { t.Errorf("function called with (%v, %v), want (%v, %v)", gotA, gotB, wantA, wantB) } } */ var tagGetTests = []struct { Tag StructTag Key string Value string }{ {`protobuf:"PB(1,2)"`, `protobuf`, `PB(1,2)`}, {`protobuf:"PB(1,2)"`, `foo`, ``}, {`protobuf:"PB(1,2)"`, `rotobuf`, ``}, {`protobuf:"PB(1,2)" json:"name"`, `json`, `name`}, {`protobuf:"PB(1,2)" json:"name"`, `protobuf`, `PB(1,2)`}, {`k0:"values contain spaces" k1:"and\ttabs"`, "k0", "values contain spaces"}, {`k0:"values contain spaces" k1:"and\ttabs"`, "k1", "and\ttabs"}, } func TestTagGet(t *testing.T) { for _, tt := range tagGetTests { if v := tt.Tag.Get(tt.Key); v != tt.Value { t.Errorf("StructTag(%#q).Get(%#q) = %#q, want %#q", tt.Tag, tt.Key, v, tt.Value) } } } func TestBytes(t *testing.T) { shouldPanic("on int Value", func() { ValueOf(0).Bytes() }) shouldPanic("of non-byte slice", func() { ValueOf([]string{}).Bytes() }) type S []byte x := S{1, 2, 3, 4} y := ValueOf(x).Bytes() if !bytes.Equal(x, y) { t.Fatalf("ValueOf(%v).Bytes() = %v", x, y) } if &x[0] != &y[0] { t.Errorf("ValueOf(%p).Bytes() = %p", &x[0], &y[0]) } type A [4]byte a := A{1, 2, 3, 4} shouldPanic("unaddressable", func() { ValueOf(a).Bytes() }) shouldPanic("on ptr Value", func() { ValueOf(&a).Bytes() }) b := ValueOf(&a).Elem().Bytes() if !bytes.Equal(a[:], y) { t.Fatalf("ValueOf(%v).Bytes() = %v", a, b) } if &a[0] != &b[0] { t.Errorf("ValueOf(%p).Bytes() = %p", &a[0], &b[0]) } // Per issue #24746, it was decided that Bytes can be called on byte slices // that normally cannot be converted from per Go language semantics. type B byte type SB []B type AB [4]B ValueOf([]B{1, 2, 3, 4}).Bytes() // should not panic ValueOf(new([4]B)).Elem().Bytes() // should not panic ValueOf(SB{1, 2, 3, 4}).Bytes() // should not panic ValueOf(new(AB)).Elem().Bytes() // should not panic } func TestSetBytes(t *testing.T) { type B []byte var x B y := []byte{1, 2, 3, 4} ValueOf(&x).Elem().SetBytes(y) if !bytes.Equal(x, y) { t.Fatalf("ValueOf(%v).Bytes() = %v", x, y) } if &x[0] != &y[0] { t.Errorf("ValueOf(%p).Bytes() = %p", &x[0], &y[0]) } } type Private struct { x int y **int Z int } func (p *Private) m() { } type private struct { Z int z int S string A [1]Private T []Private } func (p *private) P() { } type Public struct { X int Y **int private } func (p *Public) M() { } /* func TestUnexported(t *testing.T) { var pub Public pub.S = "S" pub.T = pub.A[:] v := ValueOf(&pub) isValid(v.Elem().Field(0)) isValid(v.Elem().Field(1)) isValid(v.Elem().Field(2)) isValid(v.Elem().FieldByName("X")) isValid(v.Elem().FieldByName("Y")) isValid(v.Elem().FieldByName("Z")) isValid(v.Type().Method(0).Func) m, _ := v.Type().MethodByName("M") isValid(m.Func) m, _ = v.Type().MethodByName("P") isValid(m.Func) isNonNil(v.Elem().Field(0).Interface()) isNonNil(v.Elem().Field(1).Interface()) isNonNil(v.Elem().Field(2).Field(2).Index(0)) isNonNil(v.Elem().FieldByName("X").Interface()) isNonNil(v.Elem().FieldByName("Y").Interface()) isNonNil(v.Elem().FieldByName("Z").Interface()) isNonNil(v.Elem().FieldByName("S").Index(0).Interface()) isNonNil(v.Type().Method(0).Func.Interface()) m, _ = v.Type().MethodByName("P") isNonNil(m.Func.Interface()) var priv Private v = ValueOf(&priv) isValid(v.Elem().Field(0)) isValid(v.Elem().Field(1)) isValid(v.Elem().FieldByName("x")) isValid(v.Elem().FieldByName("y")) shouldPanic("Interface", func() { v.Elem().Field(0).Interface() }) shouldPanic("Interface", func() { v.Elem().Field(1).Interface() }) shouldPanic("Interface", func() { v.Elem().FieldByName("x").Interface() }) shouldPanic("Interface", func() { v.Elem().FieldByName("y").Interface() }) shouldPanic("Method", func() { v.Type().Method(0) }) } func TestSetPanic(t *testing.T) { ok := func(f func()) { f() } bad := func(f func()) { shouldPanic("Set", f) } clear := func(v Value) { v.Set(Zero(v.Type())) } type t0 struct { W int } type t1 struct { Y int t0 } type T2 struct { Z int namedT0 t0 } type T struct { X int t1 T2 NamedT1 t1 NamedT2 T2 namedT1 t1 namedT2 T2 } // not addressable v := ValueOf(T{}) bad(func() { clear(v.Field(0)) }) // .X bad(func() { clear(v.Field(1)) }) // .t1 bad(func() { clear(v.Field(1).Field(0)) }) // .t1.Y bad(func() { clear(v.Field(1).Field(1)) }) // .t1.t0 bad(func() { clear(v.Field(1).Field(1).Field(0)) }) // .t1.t0.W bad(func() { clear(v.Field(2)) }) // .T2 bad(func() { clear(v.Field(2).Field(0)) }) // .T2.Z bad(func() { clear(v.Field(2).Field(1)) }) // .T2.namedT0 bad(func() { clear(v.Field(2).Field(1).Field(0)) }) // .T2.namedT0.W bad(func() { clear(v.Field(3)) }) // .NamedT1 bad(func() { clear(v.Field(3).Field(0)) }) // .NamedT1.Y bad(func() { clear(v.Field(3).Field(1)) }) // .NamedT1.t0 bad(func() { clear(v.Field(3).Field(1).Field(0)) }) // .NamedT1.t0.W bad(func() { clear(v.Field(4)) }) // .NamedT2 bad(func() { clear(v.Field(4).Field(0)) }) // .NamedT2.Z bad(func() { clear(v.Field(4).Field(1)) }) // .NamedT2.namedT0 bad(func() { clear(v.Field(4).Field(1).Field(0)) }) // .NamedT2.namedT0.W bad(func() { clear(v.Field(5)) }) // .namedT1 bad(func() { clear(v.Field(5).Field(0)) }) // .namedT1.Y bad(func() { clear(v.Field(5).Field(1)) }) // .namedT1.t0 bad(func() { clear(v.Field(5).Field(1).Field(0)) }) // .namedT1.t0.W bad(func() { clear(v.Field(6)) }) // .namedT2 bad(func() { clear(v.Field(6).Field(0)) }) // .namedT2.Z bad(func() { clear(v.Field(6).Field(1)) }) // .namedT2.namedT0 bad(func() { clear(v.Field(6).Field(1).Field(0)) }) // .namedT2.namedT0.W // addressable v = ValueOf(&T{}).Elem() ok(func() { clear(v.Field(0)) }) // .X bad(func() { clear(v.Field(1)) }) // .t1 ok(func() { clear(v.Field(1).Field(0)) }) // .t1.Y bad(func() { clear(v.Field(1).Field(1)) }) // .t1.t0 ok(func() { clear(v.Field(1).Field(1).Field(0)) }) // .t1.t0.W ok(func() { clear(v.Field(2)) }) // .T2 ok(func() { clear(v.Field(2).Field(0)) }) // .T2.Z bad(func() { clear(v.Field(2).Field(1)) }) // .T2.namedT0 bad(func() { clear(v.Field(2).Field(1).Field(0)) }) // .T2.namedT0.W ok(func() { clear(v.Field(3)) }) // .NamedT1 ok(func() { clear(v.Field(3).Field(0)) }) // .NamedT1.Y bad(func() { clear(v.Field(3).Field(1)) }) // .NamedT1.t0 ok(func() { clear(v.Field(3).Field(1).Field(0)) }) // .NamedT1.t0.W ok(func() { clear(v.Field(4)) }) // .NamedT2 ok(func() { clear(v.Field(4).Field(0)) }) // .NamedT2.Z bad(func() { clear(v.Field(4).Field(1)) }) // .NamedT2.namedT0 bad(func() { clear(v.Field(4).Field(1).Field(0)) }) // .NamedT2.namedT0.W bad(func() { clear(v.Field(5)) }) // .namedT1 bad(func() { clear(v.Field(5).Field(0)) }) // .namedT1.Y bad(func() { clear(v.Field(5).Field(1)) }) // .namedT1.t0 bad(func() { clear(v.Field(5).Field(1).Field(0)) }) // .namedT1.t0.W bad(func() { clear(v.Field(6)) }) // .namedT2 bad(func() { clear(v.Field(6).Field(0)) }) // .namedT2.Z bad(func() { clear(v.Field(6).Field(1)) }) // .namedT2.namedT0 bad(func() { clear(v.Field(6).Field(1).Field(0)) }) // .namedT2.namedT0.W } */ type timp int func (t timp) W() {} func (t timp) Y() {} func (t timp) w() {} func (t timp) y() {} /* func TestCallPanic(t *testing.T) { type t0 interface { W() w() } type T1 interface { Y() y() } type T2 struct { T1 t0 } type T struct { t0 // 0 T1 // 1 NamedT0 t0 // 2 NamedT1 T1 // 3 NamedT2 T2 // 4 namedT0 t0 // 5 namedT1 T1 // 6 namedT2 T2 // 7 } ok := func(f func()) { f() } badCall := func(f func()) { shouldPanic("Call", f) } badMethod := func(f func()) { shouldPanic("Method", f) } call := func(v Value) { v.Call(nil) } i := timp(0) v := ValueOf(T{i, i, i, i, T2{i, i}, i, i, T2{i, i}}) badCall(func() { call(v.Field(0).Method(0)) }) // .t0.W badCall(func() { call(v.Field(0).Elem().Method(0)) }) // .t0.W badCall(func() { call(v.Field(0).Method(1)) }) // .t0.w badMethod(func() { call(v.Field(0).Elem().Method(2)) }) // .t0.w ok(func() { call(v.Field(1).Method(0)) }) // .T1.Y ok(func() { call(v.Field(1).Elem().Method(0)) }) // .T1.Y badCall(func() { call(v.Field(1).Method(1)) }) // .T1.y badMethod(func() { call(v.Field(1).Elem().Method(2)) }) // .T1.y ok(func() { call(v.Field(2).Method(0)) }) // .NamedT0.W ok(func() { call(v.Field(2).Elem().Method(0)) }) // .NamedT0.W badCall(func() { call(v.Field(2).Method(1)) }) // .NamedT0.w badMethod(func() { call(v.Field(2).Elem().Method(2)) }) // .NamedT0.w ok(func() { call(v.Field(3).Method(0)) }) // .NamedT1.Y ok(func() { call(v.Field(3).Elem().Method(0)) }) // .NamedT1.Y badCall(func() { call(v.Field(3).Method(1)) }) // .NamedT1.y badMethod(func() { call(v.Field(3).Elem().Method(3)) }) // .NamedT1.y ok(func() { call(v.Field(4).Field(0).Method(0)) }) // .NamedT2.T1.Y ok(func() { call(v.Field(4).Field(0).Elem().Method(0)) }) // .NamedT2.T1.W badCall(func() { call(v.Field(4).Field(1).Method(0)) }) // .NamedT2.t0.W badCall(func() { call(v.Field(4).Field(1).Elem().Method(0)) }) // .NamedT2.t0.W badCall(func() { call(v.Field(5).Method(0)) }) // .namedT0.W badCall(func() { call(v.Field(5).Elem().Method(0)) }) // .namedT0.W badCall(func() { call(v.Field(5).Method(1)) }) // .namedT0.w badMethod(func() { call(v.Field(5).Elem().Method(2)) }) // .namedT0.w badCall(func() { call(v.Field(6).Method(0)) }) // .namedT1.Y badCall(func() { call(v.Field(6).Elem().Method(0)) }) // .namedT1.Y badCall(func() { call(v.Field(6).Method(0)) }) // .namedT1.y badCall(func() { call(v.Field(6).Elem().Method(0)) }) // .namedT1.y badCall(func() { call(v.Field(7).Field(0).Method(0)) }) // .namedT2.T1.Y badCall(func() { call(v.Field(7).Field(0).Elem().Method(0)) }) // .namedT2.T1.W badCall(func() { call(v.Field(7).Field(1).Method(0)) }) // .namedT2.t0.W badCall(func() { call(v.Field(7).Field(1).Elem().Method(0)) }) // .namedT2.t0.W } func TestValuePanic(t *testing.T) { vo := ValueOf shouldPanic("reflect.Value.Addr of unaddressable value", func() { vo(0).Addr() }) shouldPanic("call of reflect.Value.Bool on float64 Value", func() { vo(0.0).Bool() }) shouldPanic("call of reflect.Value.Bytes on string Value", func() { vo("").Bytes() }) shouldPanic("call of reflect.Value.Call on bool Value", func() { vo(true).Call(nil) }) shouldPanic("call of reflect.Value.CallSlice on int Value", func() { vo(0).CallSlice(nil) }) shouldPanic("call of reflect.Value.Close on string Value", func() { vo("").Close() }) shouldPanic("call of reflect.Value.Complex on float64 Value", func() { vo(0.0).Complex() }) shouldPanic("call of reflect.Value.Elem on bool Value", func() { vo(false).Elem() }) shouldPanic("call of reflect.Value.Field on int Value", func() { vo(0).Field(0) }) shouldPanic("call of reflect.Value.Float on string Value", func() { vo("").Float() }) shouldPanic("call of reflect.Value.Index on float64 Value", func() { vo(0.0).Index(0) }) shouldPanic("call of reflect.Value.Int on bool Value", func() { vo(false).Int() }) shouldPanic("call of reflect.Value.IsNil on int Value", func() { vo(0).IsNil() }) shouldPanic("call of reflect.Value.Len on bool Value", func() { vo(false).Len() }) shouldPanic("call of reflect.Value.MapIndex on float64 Value", func() { vo(0.0).MapIndex(vo(0.0)) }) shouldPanic("call of reflect.Value.MapKeys on string Value", func() { vo("").MapKeys() }) shouldPanic("call of reflect.Value.MapRange on int Value", func() { vo(0).MapRange() }) shouldPanic("call of reflect.Value.Method on zero Value", func() { vo(nil).Method(0) }) shouldPanic("call of reflect.Value.NumField on string Value", func() { vo("").NumField() }) shouldPanic("call of reflect.Value.NumMethod on zero Value", func() { vo(nil).NumMethod() }) shouldPanic("call of reflect.Value.OverflowComplex on float64 Value", func() { vo(float64(0)).OverflowComplex(0) }) shouldPanic("call of reflect.Value.OverflowFloat on int64 Value", func() { vo(int64(0)).OverflowFloat(0) }) shouldPanic("call of reflect.Value.OverflowInt on uint64 Value", func() { vo(uint64(0)).OverflowInt(0) }) shouldPanic("call of reflect.Value.OverflowUint on complex64 Value", func() { vo(complex64(0)).OverflowUint(0) }) shouldPanic("call of reflect.Value.Recv on string Value", func() { vo("").Recv() }) shouldPanic("call of reflect.Value.Send on bool Value", func() { vo(true).Send(vo(true)) }) shouldPanic("value of type string is not assignable to type bool", func() { vo(new(bool)).Elem().Set(vo("")) }) shouldPanic("call of reflect.Value.SetBool on string Value", func() { vo(new(string)).Elem().SetBool(false) }) shouldPanic("reflect.Value.SetBytes using unaddressable value", func() { vo("").SetBytes(nil) }) shouldPanic("call of reflect.Value.SetCap on string Value", func() { vo(new(string)).Elem().SetCap(0) }) shouldPanic("call of reflect.Value.SetComplex on string Value", func() { vo(new(string)).Elem().SetComplex(0) }) shouldPanic("call of reflect.Value.SetFloat on string Value", func() { vo(new(string)).Elem().SetFloat(0) }) shouldPanic("call of reflect.Value.SetInt on string Value", func() { vo(new(string)).Elem().SetInt(0) }) shouldPanic("call of reflect.Value.SetLen on string Value", func() { vo(new(string)).Elem().SetLen(0) }) shouldPanic("call of reflect.Value.SetString on int Value", func() { vo(new(int)).Elem().SetString("") }) shouldPanic("reflect.Value.SetUint using unaddressable value", func() { vo(0.0).SetUint(0) }) shouldPanic("call of reflect.Value.Slice on bool Value", func() { vo(true).Slice(1, 2) }) shouldPanic("call of reflect.Value.Slice3 on int Value", func() { vo(0).Slice3(1, 2, 3) }) shouldPanic("call of reflect.Value.TryRecv on bool Value", func() { vo(true).TryRecv() }) shouldPanic("call of reflect.Value.TrySend on string Value", func() { vo("").TrySend(vo("")) }) shouldPanic("call of reflect.Value.Uint on float64 Value", func() { vo(0.0).Uint() }) } */ func shouldPanic(expect string, f func()) { return defer func() { r := recover() if r == nil { panic("did not panic") } if expect != "" { var s string switch r := r.(type) { case string: s = r case *ValueError: s = r.Error() default: panic(fmt.Sprintf("panicked with unexpected type %T", r)) } if !strings.HasPrefix(s, "reflect") { panic(`panic string does not start with "reflect": ` + s) } if !strings.Contains(s, expect) { panic(`panic string does not contain "` + expect + `": ` + s) } } }() f() } func isNonNil(x any) { if x == nil { panic("nil interface") } } func isValid(v Value) { if !v.IsValid() { panic("zero Value") } } /* func TestAlias(t *testing.T) { x := string("hello") v := ValueOf(&x).Elem() oldvalue := v.Interface() v.SetString("world") newvalue := v.Interface() if oldvalue != "hello" || newvalue != "world" { t.Errorf("aliasing: old=%q new=%q, want hello, world", oldvalue, newvalue) } } */ var V = ValueOf func EmptyInterfaceV(x any) Value { return ValueOf(&x).Elem() } func ReaderV(x io.Reader) Value { return ValueOf(&x).Elem() } func ReadWriterV(x io.ReadWriter) Value { return ValueOf(&x).Elem() } type Empty struct{} type MyStruct struct { x int `some:"tag"` } type MyStruct1 struct { x struct { int `some:"bar"` } } type MyStruct2 struct { x struct { int `some:"foo"` } } type MyString string type MyBytes []byte type MyBytesArrayPtr0 *[0]byte type MyBytesArrayPtr *[4]byte type MyBytesArray0 [0]byte type MyBytesArray [4]byte type MyRunes []int32 type MyFunc func() type MyByte byte type IntChan chan int type IntChanRecv <-chan int type IntChanSend chan<- int type BytesChan chan []byte type BytesChanRecv <-chan []byte type BytesChanSend chan<- []byte /* var convertTests = []struct { in Value out Value }{ // numbers /* Edit .+1,/\*\//-1>cat >/tmp/x.go && go run /tmp/x.go package main import "fmt" var numbers = []string{ "int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", "uint64", "int", "uint", "uintptr", "float32", "float64", } func main() { // all pairs but in an unusual order, // to emit all the int8, uint8 cases // before n grows too big. n := 1 for i, f := range numbers { for _, g := range numbers[i:] { fmt.Printf("\t{V(%s(%d)), V(%s(%d))},\n", f, n, g, n) n++ if f != g { fmt.Printf("\t{V(%s(%d)), V(%s(%d))},\n", g, n, f, n) n++ } } } } */ /* {V(int8(1)), V(int8(1))}, {V(int8(2)), V(uint8(2))}, {V(uint8(3)), V(int8(3))}, {V(int8(4)), V(int16(4))}, {V(int16(5)), V(int8(5))}, {V(int8(6)), V(uint16(6))}, {V(uint16(7)), V(int8(7))}, {V(int8(8)), V(int32(8))}, {V(int32(9)), V(int8(9))}, {V(int8(10)), V(uint32(10))}, {V(uint32(11)), V(int8(11))}, {V(int8(12)), V(int64(12))}, {V(int64(13)), V(int8(13))}, {V(int8(14)), V(uint64(14))}, {V(uint64(15)), V(int8(15))}, {V(int8(16)), V(int(16))}, {V(int(17)), V(int8(17))}, {V(int8(18)), V(uint(18))}, {V(uint(19)), V(int8(19))}, {V(int8(20)), V(uintptr(20))}, {V(uintptr(21)), V(int8(21))}, {V(int8(22)), V(float32(22))}, {V(float32(23)), V(int8(23))}, {V(int8(24)), V(float64(24))}, {V(float64(25)), V(int8(25))}, {V(uint8(26)), V(uint8(26))}, {V(uint8(27)), V(int16(27))}, {V(int16(28)), V(uint8(28))}, {V(uint8(29)), V(uint16(29))}, {V(uint16(30)), V(uint8(30))}, {V(uint8(31)), V(int32(31))}, {V(int32(32)), V(uint8(32))}, {V(uint8(33)), V(uint32(33))}, {V(uint32(34)), V(uint8(34))}, {V(uint8(35)), V(int64(35))}, {V(int64(36)), V(uint8(36))}, {V(uint8(37)), V(uint64(37))}, {V(uint64(38)), V(uint8(38))}, {V(uint8(39)), V(int(39))}, {V(int(40)), V(uint8(40))}, {V(uint8(41)), V(uint(41))}, {V(uint(42)), V(uint8(42))}, {V(uint8(43)), V(uintptr(43))}, {V(uintptr(44)), V(uint8(44))}, {V(uint8(45)), V(float32(45))}, {V(float32(46)), V(uint8(46))}, {V(uint8(47)), V(float64(47))}, {V(float64(48)), V(uint8(48))}, {V(int16(49)), V(int16(49))}, {V(int16(50)), V(uint16(50))}, {V(uint16(51)), V(int16(51))}, {V(int16(52)), V(int32(52))}, {V(int32(53)), V(int16(53))}, {V(int16(54)), V(uint32(54))}, {V(uint32(55)), V(int16(55))}, {V(int16(56)), V(int64(56))}, {V(int64(57)), V(int16(57))}, {V(int16(58)), V(uint64(58))}, {V(uint64(59)), V(int16(59))}, {V(int16(60)), V(int(60))}, {V(int(61)), V(int16(61))}, {V(int16(62)), V(uint(62))}, {V(uint(63)), V(int16(63))}, {V(int16(64)), V(uintptr(64))}, {V(uintptr(65)), V(int16(65))}, {V(int16(66)), V(float32(66))}, {V(float32(67)), V(int16(67))}, {V(int16(68)), V(float64(68))}, {V(float64(69)), V(int16(69))}, {V(uint16(70)), V(uint16(70))}, {V(uint16(71)), V(int32(71))}, {V(int32(72)), V(uint16(72))}, {V(uint16(73)), V(uint32(73))}, {V(uint32(74)), V(uint16(74))}, {V(uint16(75)), V(int64(75))}, {V(int64(76)), V(uint16(76))}, {V(uint16(77)), V(uint64(77))}, {V(uint64(78)), V(uint16(78))}, {V(uint16(79)), V(int(79))}, {V(int(80)), V(uint16(80))}, {V(uint16(81)), V(uint(81))}, {V(uint(82)), V(uint16(82))}, {V(uint16(83)), V(uintptr(83))}, {V(uintptr(84)), V(uint16(84))}, {V(uint16(85)), V(float32(85))}, {V(float32(86)), V(uint16(86))}, {V(uint16(87)), V(float64(87))}, {V(float64(88)), V(uint16(88))}, {V(int32(89)), V(int32(89))}, {V(int32(90)), V(uint32(90))}, {V(uint32(91)), V(int32(91))}, {V(int32(92)), V(int64(92))}, {V(int64(93)), V(int32(93))}, {V(int32(94)), V(uint64(94))}, {V(uint64(95)), V(int32(95))}, {V(int32(96)), V(int(96))}, {V(int(97)), V(int32(97))}, {V(int32(98)), V(uint(98))}, {V(uint(99)), V(int32(99))}, {V(int32(100)), V(uintptr(100))}, {V(uintptr(101)), V(int32(101))}, {V(int32(102)), V(float32(102))}, {V(float32(103)), V(int32(103))}, {V(int32(104)), V(float64(104))}, {V(float64(105)), V(int32(105))}, {V(uint32(106)), V(uint32(106))}, {V(uint32(107)), V(int64(107))}, {V(int64(108)), V(uint32(108))}, {V(uint32(109)), V(uint64(109))}, {V(uint64(110)), V(uint32(110))}, {V(uint32(111)), V(int(111))}, {V(int(112)), V(uint32(112))}, {V(uint32(113)), V(uint(113))}, {V(uint(114)), V(uint32(114))}, {V(uint32(115)), V(uintptr(115))}, {V(uintptr(116)), V(uint32(116))}, {V(uint32(117)), V(float32(117))}, {V(float32(118)), V(uint32(118))}, {V(uint32(119)), V(float64(119))}, {V(float64(120)), V(uint32(120))}, {V(int64(121)), V(int64(121))}, {V(int64(122)), V(uint64(122))}, {V(uint64(123)), V(int64(123))}, {V(int64(124)), V(int(124))}, {V(int(125)), V(int64(125))}, {V(int64(126)), V(uint(126))}, {V(uint(127)), V(int64(127))}, {V(int64(128)), V(uintptr(128))}, {V(uintptr(129)), V(int64(129))}, {V(int64(130)), V(float32(130))}, {V(float32(131)), V(int64(131))}, {V(int64(132)), V(float64(132))}, {V(float64(133)), V(int64(133))}, {V(uint64(134)), V(uint64(134))}, {V(uint64(135)), V(int(135))}, {V(int(136)), V(uint64(136))}, {V(uint64(137)), V(uint(137))}, {V(uint(138)), V(uint64(138))}, {V(uint64(139)), V(uintptr(139))}, {V(uintptr(140)), V(uint64(140))}, {V(uint64(141)), V(float32(141))}, {V(float32(142)), V(uint64(142))}, {V(uint64(143)), V(float64(143))}, {V(float64(144)), V(uint64(144))}, {V(int(145)), V(int(145))}, {V(int(146)), V(uint(146))}, {V(uint(147)), V(int(147))}, {V(int(148)), V(uintptr(148))}, {V(uintptr(149)), V(int(149))}, {V(int(150)), V(float32(150))}, {V(float32(151)), V(int(151))}, {V(int(152)), V(float64(152))}, {V(float64(153)), V(int(153))}, {V(uint(154)), V(uint(154))}, {V(uint(155)), V(uintptr(155))}, {V(uintptr(156)), V(uint(156))}, {V(uint(157)), V(float32(157))}, {V(float32(158)), V(uint(158))}, {V(uint(159)), V(float64(159))}, {V(float64(160)), V(uint(160))}, {V(uintptr(161)), V(uintptr(161))}, {V(uintptr(162)), V(float32(162))}, {V(float32(163)), V(uintptr(163))}, {V(uintptr(164)), V(float64(164))}, {V(float64(165)), V(uintptr(165))}, {V(float32(166)), V(float32(166))}, {V(float32(167)), V(float64(167))}, {V(float64(168)), V(float32(168))}, {V(float64(169)), V(float64(169))}, // truncation {V(float64(1.5)), V(int(1))}, // complex {V(complex64(1i)), V(complex64(1i))}, {V(complex64(2i)), V(complex128(2i))}, {V(complex128(3i)), V(complex64(3i))}, {V(complex128(4i)), V(complex128(4i))}, // string {V(string("hello")), V(string("hello"))}, {V(string("bytes1")), V([]byte("bytes1"))}, {V([]byte("bytes2")), V(string("bytes2"))}, {V([]byte("bytes3")), V([]byte("bytes3"))}, {V(string("runes♝")), V([]rune("runes♝"))}, {V([]rune("runes♕")), V(string("runes♕"))}, {V([]rune("runes🙈🙉🙊")), V([]rune("runes🙈🙉🙊"))}, {V(int('a')), V(string("a"))}, {V(int8('a')), V(string("a"))}, {V(int16('a')), V(string("a"))}, {V(int32('a')), V(string("a"))}, {V(int64('a')), V(string("a"))}, {V(uint('a')), V(string("a"))}, {V(uint8('a')), V(string("a"))}, {V(uint16('a')), V(string("a"))}, {V(uint32('a')), V(string("a"))}, {V(uint64('a')), V(string("a"))}, {V(uintptr('a')), V(string("a"))}, {V(int(-1)), V(string("\uFFFD"))}, {V(int8(-2)), V(string("\uFFFD"))}, {V(int16(-3)), V(string("\uFFFD"))}, {V(int32(-4)), V(string("\uFFFD"))}, {V(int64(-5)), V(string("\uFFFD"))}, {V(int64(-1 << 32)), V(string("\uFFFD"))}, {V(int64(1 << 32)), V(string("\uFFFD"))}, {V(uint(0x110001)), V(string("\uFFFD"))}, {V(uint32(0x110002)), V(string("\uFFFD"))}, {V(uint64(0x110003)), V(string("\uFFFD"))}, {V(uint64(1 << 32)), V(string("\uFFFD"))}, {V(uintptr(0x110004)), V(string("\uFFFD"))}, // named string {V(MyString("hello")), V(string("hello"))}, {V(string("hello")), V(MyString("hello"))}, {V(string("hello")), V(string("hello"))}, {V(MyString("hello")), V(MyString("hello"))}, {V(MyString("bytes1")), V([]byte("bytes1"))}, {V([]byte("bytes2")), V(MyString("bytes2"))}, {V([]byte("bytes3")), V([]byte("bytes3"))}, {V(MyString("runes♝")), V([]rune("runes♝"))}, {V([]rune("runes♕")), V(MyString("runes♕"))}, {V([]rune("runes🙈🙉🙊")), V([]rune("runes🙈🙉🙊"))}, {V([]rune("runes🙈🙉🙊")), V(MyRunes("runes🙈🙉🙊"))}, {V(MyRunes("runes🙈🙉🙊")), V([]rune("runes🙈🙉🙊"))}, {V(int('a')), V(MyString("a"))}, {V(int8('a')), V(MyString("a"))}, {V(int16('a')), V(MyString("a"))}, {V(int32('a')), V(MyString("a"))}, {V(int64('a')), V(MyString("a"))}, {V(uint('a')), V(MyString("a"))}, {V(uint8('a')), V(MyString("a"))}, {V(uint16('a')), V(MyString("a"))}, {V(uint32('a')), V(MyString("a"))}, {V(uint64('a')), V(MyString("a"))}, {V(uintptr('a')), V(MyString("a"))}, {V(int(-1)), V(MyString("\uFFFD"))}, {V(int8(-2)), V(MyString("\uFFFD"))}, {V(int16(-3)), V(MyString("\uFFFD"))}, {V(int32(-4)), V(MyString("\uFFFD"))}, {V(int64(-5)), V(MyString("\uFFFD"))}, {V(uint(0x110001)), V(MyString("\uFFFD"))}, {V(uint32(0x110002)), V(MyString("\uFFFD"))}, {V(uint64(0x110003)), V(MyString("\uFFFD"))}, {V(uintptr(0x110004)), V(MyString("\uFFFD"))}, // named []byte {V(string("bytes1")), V(MyBytes("bytes1"))}, {V(MyBytes("bytes2")), V(string("bytes2"))}, {V(MyBytes("bytes3")), V(MyBytes("bytes3"))}, {V(MyString("bytes1")), V(MyBytes("bytes1"))}, {V(MyBytes("bytes2")), V(MyString("bytes2"))}, // named []rune {V(string("runes♝")), V(MyRunes("runes♝"))}, {V(MyRunes("runes♕")), V(string("runes♕"))}, {V(MyRunes("runes🙈🙉🙊")), V(MyRunes("runes🙈🙉🙊"))}, {V(MyString("runes♝")), V(MyRunes("runes♝"))}, {V(MyRunes("runes♕")), V(MyString("runes♕"))}, // slice to array {V([]byte(nil)), V([0]byte{})}, {V([]byte{}), V([0]byte{})}, {V([]byte{1}), V([1]byte{1})}, {V([]byte{1, 2}), V([2]byte{1, 2})}, {V([]byte{1, 2, 3}), V([3]byte{1, 2, 3})}, {V(MyBytes([]byte(nil))), V([0]byte{})}, {V(MyBytes{}), V([0]byte{})}, {V(MyBytes{1}), V([1]byte{1})}, {V(MyBytes{1, 2}), V([2]byte{1, 2})}, {V(MyBytes{1, 2, 3}), V([3]byte{1, 2, 3})}, {V([]byte(nil)), V(MyBytesArray0{})}, {V([]byte{}), V(MyBytesArray0([0]byte{}))}, {V([]byte{1, 2, 3, 4}), V(MyBytesArray([4]byte{1, 2, 3, 4}))}, {V(MyBytes{}), V(MyBytesArray0([0]byte{}))}, {V(MyBytes{5, 6, 7, 8}), V(MyBytesArray([4]byte{5, 6, 7, 8}))}, {V([]MyByte{}), V([0]MyByte{})}, {V([]MyByte{1, 2}), V([2]MyByte{1, 2})}, // slice to array pointer {V([]byte(nil)), V((*[0]byte)(nil))}, {V([]byte{}), V(new([0]byte))}, {V([]byte{7}), V(&[1]byte{7})}, {V(MyBytes([]byte(nil))), V((*[0]byte)(nil))}, {V(MyBytes([]byte{})), V(new([0]byte))}, {V(MyBytes([]byte{9})), V(&[1]byte{9})}, {V([]byte(nil)), V(MyBytesArrayPtr0(nil))}, {V([]byte{}), V(MyBytesArrayPtr0(new([0]byte)))}, {V([]byte{1, 2, 3, 4}), V(MyBytesArrayPtr(&[4]byte{1, 2, 3, 4}))}, {V(MyBytes([]byte{})), V(MyBytesArrayPtr0(new([0]byte)))}, {V(MyBytes([]byte{5, 6, 7, 8})), V(MyBytesArrayPtr(&[4]byte{5, 6, 7, 8}))}, {V([]byte(nil)), V((*MyBytesArray0)(nil))}, {V([]byte{}), V((*MyBytesArray0)(new([0]byte)))}, {V([]byte{1, 2, 3, 4}), V(&MyBytesArray{1, 2, 3, 4})}, {V(MyBytes([]byte(nil))), V((*MyBytesArray0)(nil))}, {V(MyBytes([]byte{})), V((*MyBytesArray0)(new([0]byte)))}, {V(MyBytes([]byte{5, 6, 7, 8})), V(&MyBytesArray{5, 6, 7, 8})}, {V(new([0]byte)), V(new(MyBytesArray0))}, {V(new(MyBytesArray0)), V(new([0]byte))}, {V(MyBytesArrayPtr0(nil)), V((*[0]byte)(nil))}, {V((*[0]byte)(nil)), V(MyBytesArrayPtr0(nil))}, // named types and equal underlying types {V(new(int)), V(new(integer))}, {V(new(integer)), V(new(int))}, {V(Empty{}), V(struct{}{})}, {V(new(Empty)), V(new(struct{}))}, {V(struct{}{}), V(Empty{})}, {V(new(struct{})), V(new(Empty))}, {V(Empty{}), V(Empty{})}, {V(MyBytes{}), V([]byte{})}, {V([]byte{}), V(MyBytes{})}, {V((func())(nil)), V(MyFunc(nil))}, {V((MyFunc)(nil)), V((func())(nil))}, // structs with different tags {V(struct { x int `some:"foo"` }{}), V(struct { x int `some:"bar"` }{})}, {V(struct { x int `some:"bar"` }{}), V(struct { x int `some:"foo"` }{})}, {V(MyStruct{}), V(struct { x int `some:"foo"` }{})}, {V(struct { x int `some:"foo"` }{}), V(MyStruct{})}, {V(MyStruct{}), V(struct { x int `some:"bar"` }{})}, {V(struct { x int `some:"bar"` }{}), V(MyStruct{})}, {V(MyStruct1{}), V(MyStruct2{})}, {V(MyStruct2{}), V(MyStruct1{})}, // can convert *byte and *MyByte {V((*byte)(nil)), V((*MyByte)(nil))}, {V((*MyByte)(nil)), V((*byte)(nil))}, // cannot convert mismatched array sizes {V([2]byte{}), V([2]byte{})}, {V([3]byte{}), V([3]byte{})}, {V(MyBytesArray0{}), V([0]byte{})}, {V([0]byte{}), V(MyBytesArray0{})}, // cannot convert other instances {V((**byte)(nil)), V((**byte)(nil))}, {V((**MyByte)(nil)), V((**MyByte)(nil))}, {V((chan byte)(nil)), V((chan byte)(nil))}, {V((chan MyByte)(nil)), V((chan MyByte)(nil))}, {V(([]byte)(nil)), V(([]byte)(nil))}, {V(([]MyByte)(nil)), V(([]MyByte)(nil))}, {V((map[int]byte)(nil)), V((map[int]byte)(nil))}, {V((map[int]MyByte)(nil)), V((map[int]MyByte)(nil))}, {V((map[byte]int)(nil)), V((map[byte]int)(nil))}, {V((map[MyByte]int)(nil)), V((map[MyByte]int)(nil))}, {V([2]byte{}), V([2]byte{})}, {V([2]MyByte{}), V([2]MyByte{})}, // other {V((***int)(nil)), V((***int)(nil))}, {V((***byte)(nil)), V((***byte)(nil))}, {V((***int32)(nil)), V((***int32)(nil))}, {V((***int64)(nil)), V((***int64)(nil))}, {V((chan byte)(nil)), V((chan byte)(nil))}, {V((chan MyByte)(nil)), V((chan MyByte)(nil))}, {V((map[int]bool)(nil)), V((map[int]bool)(nil))}, {V((map[int]byte)(nil)), V((map[int]byte)(nil))}, {V((map[uint]bool)(nil)), V((map[uint]bool)(nil))}, {V([]uint(nil)), V([]uint(nil))}, {V([]int(nil)), V([]int(nil))}, {V(new(any)), V(new(any))}, {V(new(io.Reader)), V(new(io.Reader))}, {V(new(io.Writer)), V(new(io.Writer))}, // channels {V(IntChan(nil)), V((chan<- int)(nil))}, {V(IntChan(nil)), V((<-chan int)(nil))}, {V((chan int)(nil)), V(IntChanRecv(nil))}, {V((chan int)(nil)), V(IntChanSend(nil))}, {V(IntChanRecv(nil)), V((<-chan int)(nil))}, {V((<-chan int)(nil)), V(IntChanRecv(nil))}, {V(IntChanSend(nil)), V((chan<- int)(nil))}, {V((chan<- int)(nil)), V(IntChanSend(nil))}, {V(IntChan(nil)), V((chan int)(nil))}, {V((chan int)(nil)), V(IntChan(nil))}, {V((chan int)(nil)), V((<-chan int)(nil))}, {V((chan int)(nil)), V((chan<- int)(nil))}, {V(BytesChan(nil)), V((chan<- []byte)(nil))}, {V(BytesChan(nil)), V((<-chan []byte)(nil))}, {V((chan []byte)(nil)), V(BytesChanRecv(nil))}, {V((chan []byte)(nil)), V(BytesChanSend(nil))}, {V(BytesChanRecv(nil)), V((<-chan []byte)(nil))}, {V((<-chan []byte)(nil)), V(BytesChanRecv(nil))}, {V(BytesChanSend(nil)), V((chan<- []byte)(nil))}, {V((chan<- []byte)(nil)), V(BytesChanSend(nil))}, {V(BytesChan(nil)), V((chan []byte)(nil))}, {V((chan []byte)(nil)), V(BytesChan(nil))}, {V((chan []byte)(nil)), V((<-chan []byte)(nil))}, {V((chan []byte)(nil)), V((chan<- []byte)(nil))}, // cannot convert other instances (channels) {V(IntChan(nil)), V(IntChan(nil))}, {V(IntChanRecv(nil)), V(IntChanRecv(nil))}, {V(IntChanSend(nil)), V(IntChanSend(nil))}, {V(BytesChan(nil)), V(BytesChan(nil))}, {V(BytesChanRecv(nil)), V(BytesChanRecv(nil))}, {V(BytesChanSend(nil)), V(BytesChanSend(nil))}, // interfaces {V(int(1)), EmptyInterfaceV(int(1))}, {V(string("hello")), EmptyInterfaceV(string("hello"))}, {V(new(bytes.Buffer)), ReaderV(new(bytes.Buffer))}, {ReadWriterV(new(bytes.Buffer)), ReaderV(new(bytes.Buffer))}, {V(new(bytes.Buffer)), ReadWriterV(new(bytes.Buffer))}, } func TestConvert(t *testing.T) { canConvert := map[[2]Type]bool{} all := map[Type]bool{} for _, tt := range convertTests { t1 := tt.in.Type() if !t1.ConvertibleTo(t1) { t.Errorf("(%s).ConvertibleTo(%s) = false, want true", t1, t1) continue } t2 := tt.out.Type() if !t1.ConvertibleTo(t2) { t.Errorf("(%s).ConvertibleTo(%s) = false, want true", t1, t2) continue } all[t1] = true all[t2] = true canConvert[[2]Type{t1, t2}] = true // vout1 represents the in value converted to the in type. v1 := tt.in if !v1.CanConvert(t1) { t.Errorf("ValueOf(%T(%[1]v)).CanConvert(%s) = false, want true", tt.in.Interface(), t1) } vout1 := v1.Convert(t1) out1 := vout1.Interface() if vout1.Type() != tt.in.Type() || !DeepEqual(out1, tt.in.Interface()) { t.Errorf("ValueOf(%T(%[1]v)).Convert(%s) = %T(%[3]v), want %T(%[4]v)", tt.in.Interface(), t1, out1, tt.in.Interface()) } // vout2 represents the in value converted to the out type. if !v1.CanConvert(t2) { t.Errorf("ValueOf(%T(%[1]v)).CanConvert(%s) = false, want true", tt.in.Interface(), t2) } vout2 := v1.Convert(t2) out2 := vout2.Interface() if vout2.Type() != tt.out.Type() || !DeepEqual(out2, tt.out.Interface()) { t.Errorf("ValueOf(%T(%[1]v)).Convert(%s) = %T(%[3]v), want %T(%[4]v)", tt.in.Interface(), t2, out2, tt.out.Interface()) } if got, want := vout2.Kind(), vout2.Type().Kind(); got != want { t.Errorf("ValueOf(%T(%[1]v)).Convert(%s) has internal kind %v want %v", tt.in.Interface(), t1, got, want) } // vout3 represents a new value of the out type, set to vout2. This makes // sure the converted value vout2 is really usable as a regular value. vout3 := New(t2).Elem() vout3.Set(vout2) out3 := vout3.Interface() if vout3.Type() != tt.out.Type() || !DeepEqual(out3, tt.out.Interface()) { t.Errorf("Set(ValueOf(%T(%[1]v)).Convert(%s)) = %T(%[3]v), want %T(%[4]v)", tt.in.Interface(), t2, out3, tt.out.Interface()) } if IsRO(v1) { t.Errorf("table entry %v is RO, should not be", v1) } if IsRO(vout1) { t.Errorf("self-conversion output %v is RO, should not be", vout1) } if IsRO(vout2) { t.Errorf("conversion output %v is RO, should not be", vout2) } if IsRO(vout3) { t.Errorf("set(conversion output) %v is RO, should not be", vout3) } if !IsRO(MakeRO(v1).Convert(t1)) { t.Errorf("RO self-conversion output %v is not RO, should be", v1) } if !IsRO(MakeRO(v1).Convert(t2)) { t.Errorf("RO conversion output %v is not RO, should be", v1) } } // Assume that of all the types we saw during the tests, // if there wasn't an explicit entry for a conversion between // a pair of types, then it's not to be allowed. This checks for // things like 'int64' converting to '*int'. for t1 := range all { for t2 := range all { expectOK := t1 == t2 || canConvert[[2]Type{t1, t2}] || t2.Kind() == Interface && t2.NumMethod() == 0 if ok := t1.ConvertibleTo(t2); ok != expectOK { t.Errorf("(%s).ConvertibleTo(%s) = %v, want %v", t1, t2, ok, expectOK) } } } } func TestConvertPanic(t *testing.T) { s := make([]byte, 4) p := new([8]byte) v := ValueOf(s) pt := TypeOf(p) if !v.Type().ConvertibleTo(pt) { t.Errorf("[]byte should be convertible to *[8]byte") } if v.CanConvert(pt) { t.Errorf("slice with length 4 should not be convertible to *[8]byte") } shouldPanic("reflect: cannot convert slice with length 4 to pointer to array with length 8", func() { _ = v.Convert(pt) }) if v.CanConvert(pt.Elem()) { t.Errorf("slice with length 4 should not be convertible to [8]byte") } shouldPanic("reflect: cannot convert slice with length 4 to array with length 8", func() { _ = v.Convert(pt.Elem()) }) } func TestConvertSlice2Array(t *testing.T) { s := make([]int, 4) p := [4]int{} pt := TypeOf(p) ov := ValueOf(s) v := ov.Convert(pt) // Converting a slice to non-empty array needs to return // a non-addressable copy of the original memory. if v.CanAddr() { t.Fatalf("convert slice to non-empty array returns a addressable copy array") } for i := range s { ov.Index(i).Set(ValueOf(i + 1)) } for i := range s { if v.Index(i).Int() != 0 { t.Fatalf("slice (%v) mutation visible in converted result (%v)", ov, v) } } } var gFloat32 float32 const snan uint32 = 0x7f800001 func TestConvertNaNs(t *testing.T) { // Test to see if a store followed by a load of a signaling NaN // maintains the signaling bit. (This used to fail on the 387 port.) gFloat32 = math.Float32frombits(snan) runtime.Gosched() // make sure we don't optimize the store/load away if got := math.Float32bits(gFloat32); got != snan { t.Errorf("store/load of sNaN not faithful, got %x want %x", got, snan) } // Test reflect's conversion between float32s. See issue 36400. type myFloat32 float32 x := V(myFloat32(math.Float32frombits(snan))) y := x.Convert(TypeOf(float32(0))) z := y.Interface().(float32) if got := math.Float32bits(z); got != snan { t.Errorf("signaling nan conversion got %x, want %x", got, snan) } } */ type ComparableStruct struct { X int } type NonComparableStruct struct { X int Y map[string]int } var comparableTests = []struct { typ Type ok bool }{ {TypeOf(1), true}, {TypeOf("hello"), true}, {TypeOf(new(byte)), true}, {TypeOf((func())(nil)), false}, {TypeOf([]byte{}), false}, {TypeOf(map[string]int{}), false}, {TypeOf(make(chan int)), true}, {TypeOf(1.5), true}, {TypeOf(false), true}, {TypeOf(1i), true}, {TypeOf(ComparableStruct{}), true}, {TypeOf(NonComparableStruct{}), false}, {TypeOf([10]map[string]int{}), false}, {TypeOf([10]string{}), true}, {TypeOf(new(any)).Elem(), true}, } func TestComparable(t *testing.T) { for _, tt := range comparableTests { if ok := tt.typ.Comparable(); ok != tt.ok { t.Errorf("TypeOf(%v).Comparable() = %v, want %v", tt.typ, ok, tt.ok) } } } func TestValueOverflow(t *testing.T) { if ovf := V(float64(0)).OverflowFloat(1e300); ovf { t.Errorf("%v wrongly overflows float64", 1e300) } maxFloat32 := float64((1<<24 - 1) << (127 - 23)) if ovf := V(float32(0)).OverflowFloat(maxFloat32); ovf { t.Errorf("%v wrongly overflows float32", maxFloat32) } ovfFloat32 := float64((1<<24-1)<<(127-23) + 1<<(127-52)) if ovf := V(float32(0)).OverflowFloat(ovfFloat32); !ovf { t.Errorf("%v should overflow float32", ovfFloat32) } if ovf := V(float32(0)).OverflowFloat(-ovfFloat32); !ovf { t.Errorf("%v should overflow float32", -ovfFloat32) } maxInt32 := int64(0x7fffffff) if ovf := V(int32(0)).OverflowInt(maxInt32); ovf { t.Errorf("%v wrongly overflows int32", maxInt32) } if ovf := V(int32(0)).OverflowInt(-1 << 31); ovf { t.Errorf("%v wrongly overflows int32", -int64(1)<<31) } ovfInt32 := int64(1 << 31) if ovf := V(int32(0)).OverflowInt(ovfInt32); !ovf { t.Errorf("%v should overflow int32", ovfInt32) } maxUint32 := uint64(0xffffffff) if ovf := V(uint32(0)).OverflowUint(maxUint32); ovf { t.Errorf("%v wrongly overflows uint32", maxUint32) } ovfUint32 := uint64(1 << 32) if ovf := V(uint32(0)).OverflowUint(ovfUint32); !ovf { t.Errorf("%v should overflow uint32", ovfUint32) } } func TestTypeOverflow(t *testing.T) { if ovf := TypeFor[float64]().OverflowFloat(1e300); ovf { t.Errorf("%v wrongly overflows float64", 1e300) } maxFloat32 := float64((1<<24 - 1) << (127 - 23)) if ovf := TypeFor[float32]().OverflowFloat(maxFloat32); ovf { t.Errorf("%v wrongly overflows float32", maxFloat32) } ovfFloat32 := float64((1<<24-1)<<(127-23) + 1<<(127-52)) if ovf := TypeFor[float32]().OverflowFloat(ovfFloat32); !ovf { t.Errorf("%v should overflow float32", ovfFloat32) } if ovf := TypeFor[float32]().OverflowFloat(-ovfFloat32); !ovf { t.Errorf("%v should overflow float32", -ovfFloat32) } maxInt32 := int64(0x7fffffff) if ovf := TypeFor[int32]().OverflowInt(maxInt32); ovf { t.Errorf("%v wrongly overflows int32", maxInt32) } if ovf := TypeFor[int32]().OverflowInt(-1 << 31); ovf { t.Errorf("%v wrongly overflows int32", -int64(1)<<31) } ovfInt32 := int64(1 << 31) if ovf := TypeFor[int32]().OverflowInt(ovfInt32); !ovf { t.Errorf("%v should overflow int32", ovfInt32) } maxUint32 := uint64(0xffffffff) if ovf := TypeFor[uint32]().OverflowUint(maxUint32); ovf { t.Errorf("%v wrongly overflows uint32", maxUint32) } ovfUint32 := uint64(1 << 32) if ovf := TypeFor[uint32]().OverflowUint(ovfUint32); !ovf { t.Errorf("%v should overflow uint32", ovfUint32) } } /* func checkSameType(t *testing.T, x Type, y any) { if x != TypeOf(y) || TypeOf(Zero(x).Interface()) != TypeOf(y) { t.Errorf("did not find preexisting type for %s (vs %s)", TypeOf(x), TypeOf(y)) } } func TestArrayOf(t *testing.T) { // check construction and use of type not in binary tests := []struct { n int value func(i int) any comparable bool want string }{ { n: 0, value: func(i int) any { type Tint int; return Tint(i) }, comparable: true, want: "[]", }, { n: 10, value: func(i int) any { type Tint int; return Tint(i) }, comparable: true, want: "[0 1 2 3 4 5 6 7 8 9]", }, { n: 10, value: func(i int) any { type Tfloat float64; return Tfloat(i) }, comparable: true, want: "[0 1 2 3 4 5 6 7 8 9]", }, { n: 10, value: func(i int) any { type Tstring string; return Tstring(strconv.Itoa(i)) }, comparable: true, want: "[0 1 2 3 4 5 6 7 8 9]", }, { n: 10, value: func(i int) any { type Tstruct struct{ V int }; return Tstruct{i} }, comparable: true, want: "[{0} {1} {2} {3} {4} {5} {6} {7} {8} {9}]", }, { n: 10, value: func(i int) any { type Tint int; return []Tint{Tint(i)} }, comparable: false, want: "[[0] [1] [2] [3] [4] [5] [6] [7] [8] [9]]", }, { n: 10, value: func(i int) any { type Tint int; return [1]Tint{Tint(i)} }, comparable: true, want: "[[0] [1] [2] [3] [4] [5] [6] [7] [8] [9]]", }, { n: 10, value: func(i int) any { type Tstruct struct{ V [1]int }; return Tstruct{[1]int{i}} }, comparable: true, want: "[{[0]} {[1]} {[2]} {[3]} {[4]} {[5]} {[6]} {[7]} {[8]} {[9]}]", }, { n: 10, value: func(i int) any { type Tstruct struct{ V []int }; return Tstruct{[]int{i}} }, comparable: false, want: "[{[0]} {[1]} {[2]} {[3]} {[4]} {[5]} {[6]} {[7]} {[8]} {[9]}]", }, { n: 10, value: func(i int) any { type TstructUV struct{ U, V int }; return TstructUV{i, i} }, comparable: true, want: "[{0 0} {1 1} {2 2} {3 3} {4 4} {5 5} {6 6} {7 7} {8 8} {9 9}]", }, { n: 10, value: func(i int) any { type TstructUV struct { U int V float64 } return TstructUV{i, float64(i)} }, comparable: true, want: "[{0 0} {1 1} {2 2} {3 3} {4 4} {5 5} {6 6} {7 7} {8 8} {9 9}]", }, } for _, table := range tests { at := ArrayOf(table.n, TypeOf(table.value(0))) v := New(at).Elem() vok := New(at).Elem() vnot := New(at).Elem() for i := 0; i < v.Len(); i++ { v.Index(i).Set(ValueOf(table.value(i))) vok.Index(i).Set(ValueOf(table.value(i))) j := i if i+1 == v.Len() { j = i + 1 } vnot.Index(i).Set(ValueOf(table.value(j))) // make it differ only by last element } s := fmt.Sprint(v.Interface()) if s != table.want { t.Errorf("constructed array = %s, want %s", s, table.want) } if table.comparable != at.Comparable() { t.Errorf("constructed array (%#v) is comparable=%v, want=%v", v.Interface(), at.Comparable(), table.comparable) } if table.comparable { if table.n > 0 { if DeepEqual(vnot.Interface(), v.Interface()) { t.Errorf( "arrays (%#v) compare ok (but should not)", v.Interface(), ) } } if !DeepEqual(vok.Interface(), v.Interface()) { t.Errorf( "arrays (%#v) compare NOT-ok (but should)", v.Interface(), ) } } } // check that type already in binary is found type T int checkSameType(t, ArrayOf(5, TypeOf(T(1))), [5]T{}) } func TestArrayOfGC(t *testing.T) { type T *uintptr tt := TypeOf(T(nil)) const n = 100 var x []any for i := 0; i < n; i++ { v := New(ArrayOf(n, tt)).Elem() for j := 0; j < v.Len(); j++ { p := new(uintptr) *p = uintptr(i*n + j) v.Index(j).Set(ValueOf(p).Convert(tt)) } x = append(x, v.Interface()) } runtime.GC() for i, xi := range x { v := ValueOf(xi) for j := 0; j < v.Len(); j++ { k := v.Index(j).Elem().Interface() if k != uintptr(i*n+j) { t.Errorf("lost x[%d][%d] = %d, want %d", i, j, k, i*n+j) } } } } func TestArrayOfAlg(t *testing.T) { at := ArrayOf(6, TypeOf(byte(0))) v1 := New(at).Elem() v2 := New(at).Elem() if v1.Interface() != v1.Interface() { t.Errorf("constructed array %v not equal to itself", v1.Interface()) } v1.Index(5).Set(ValueOf(byte(1))) if i1, i2 := v1.Interface(), v2.Interface(); i1 == i2 { t.Errorf("constructed arrays %v and %v should not be equal", i1, i2) } at = ArrayOf(6, TypeOf([]int(nil))) v1 = New(at).Elem() shouldPanic("", func() { _ = v1.Interface() == v1.Interface() }) } func TestArrayOfGenericAlg(t *testing.T) { at1 := ArrayOf(5, TypeOf(string(""))) at := ArrayOf(6, at1) v1 := New(at).Elem() v2 := New(at).Elem() if v1.Interface() != v1.Interface() { t.Errorf("constructed array %v not equal to itself", v1.Interface()) } v1.Index(0).Index(0).Set(ValueOf("abc")) v2.Index(0).Index(0).Set(ValueOf("efg")) if i1, i2 := v1.Interface(), v2.Interface(); i1 == i2 { t.Errorf("constructed arrays %v and %v should not be equal", i1, i2) } v1.Index(0).Index(0).Set(ValueOf("abc")) v2.Index(0).Index(0).Set(ValueOf((v1.Index(0).Index(0).String() + " ")[:3])) if i1, i2 := v1.Interface(), v2.Interface(); i1 != i2 { t.Errorf("constructed arrays %v and %v should be equal", i1, i2) } // Test hash m := MakeMap(MapOf(at, TypeOf(int(0)))) m.SetMapIndex(v1, ValueOf(1)) if i1, i2 := v1.Interface(), v2.Interface(); !m.MapIndex(v2).IsValid() { t.Errorf("constructed arrays %v and %v have different hashes", i1, i2) } } func TestArrayOfDirectIface(t *testing.T) { { type T [1]*byte i1 := Zero(TypeOf(T{})).Interface() v1 := ValueOf(&i1).Elem() p1 := v1.InterfaceData()[1] i2 := Zero(ArrayOf(1, PointerTo(TypeOf(int8(0))))).Interface() v2 := ValueOf(&i2).Elem() p2 := v2.InterfaceData()[1] if p1 != 0 { t.Errorf("got p1=%v. want=%v", p1, nil) } if p2 != 0 { t.Errorf("got p2=%v. want=%v", p2, nil) } } { type T [0]*byte i1 := Zero(TypeOf(T{})).Interface() v1 := ValueOf(&i1).Elem() p1 := v1.InterfaceData()[1] i2 := Zero(ArrayOf(0, PointerTo(TypeOf(int8(0))))).Interface() v2 := ValueOf(&i2).Elem() p2 := v2.InterfaceData()[1] if p1 == 0 { t.Errorf("got p1=%v. want=not-%v", p1, nil) } if p2 == 0 { t.Errorf("got p2=%v. want=not-%v", p2, nil) } } } // Ensure passing in negative lengths panics. // See https://golang.org/issue/43603 func TestArrayOfPanicOnNegativeLength(t *testing.T) { shouldPanic("reflect: negative length passed to ArrayOf", func() { ArrayOf(-1, TypeOf(byte(0))) }) } func TestSliceOf(t *testing.T) { // check construction and use of type not in binary type T int st := SliceOf(TypeOf(T(1))) if got, want := st.String(), "[]reflect_test.T"; got != want { t.Errorf("SliceOf(T(1)).String()=%q, want %q", got, want) } v := MakeSlice(st, 10, 10) runtime.GC() for i := 0; i < v.Len(); i++ { v.Index(i).Set(ValueOf(T(i))) runtime.GC() } s := fmt.Sprint(v.Interface()) want := "[0 1 2 3 4 5 6 7 8 9]" if s != want { t.Errorf("constructed slice = %s, want %s", s, want) } // check that type already in binary is found type T1 int checkSameType(t, SliceOf(TypeOf(T1(1))), []T1{}) } func TestSliceOverflow(t *testing.T) { // check that MakeSlice panics when size of slice overflows uint const S = 1e6 s := uint(S) l := (1<<(unsafe.Sizeof((*byte)(nil))*8)-1)/s + 1 if l*s >= s { t.Fatal("slice size does not overflow") } var x [S]byte st := SliceOf(TypeOf(x)) defer func() { err := recover() if err == nil { t.Fatal("slice overflow does not panic") } }() MakeSlice(st, int(l), int(l)) } func TestSliceOfGC(t *testing.T) { type T *uintptr tt := TypeOf(T(nil)) st := SliceOf(tt) const n = 100 var x []any for i := 0; i < n; i++ { v := MakeSlice(st, n, n) for j := 0; j < v.Len(); j++ { p := new(uintptr) *p = uintptr(i*n + j) v.Index(j).Set(ValueOf(p).Convert(tt)) } x = append(x, v.Interface()) } runtime.GC() for i, xi := range x { v := ValueOf(xi) for j := 0; j < v.Len(); j++ { k := v.Index(j).Elem().Interface() if k != uintptr(i*n+j) { t.Errorf("lost x[%d][%d] = %d, want %d", i, j, k, i*n+j) } } } } func TestStructOfFieldName(t *testing.T) { // invalid field name "1nvalid" shouldPanic("has invalid name", func() { StructOf([]StructField{ {Name: "Valid", Type: TypeOf("")}, {Name: "1nvalid", Type: TypeOf("")}, }) }) // invalid field name "+" shouldPanic("has invalid name", func() { StructOf([]StructField{ {Name: "Val1d", Type: TypeOf("")}, {Name: "+", Type: TypeOf("")}, }) }) // no field name shouldPanic("has no name", func() { StructOf([]StructField{ {Name: "", Type: TypeOf("")}, }) }) // verify creation of a struct with valid struct fields validFields := []StructField{ { Name: "φ", Type: TypeOf(""), }, { Name: "ValidName", Type: TypeOf(""), }, { Name: "Val1dNam5", Type: TypeOf(""), }, } validStruct := StructOf(validFields) const structStr = `struct { φ string; ValidName string; Val1dNam5 string }` if got, want := validStruct.String(), structStr; got != want { t.Errorf("StructOf(validFields).String()=%q, want %q", got, want) } } func TestStructOf(t *testing.T) { // check construction and use of type not in binary fields := []StructField{ { Name: "S", Tag: "s", Type: TypeOf(""), }, { Name: "X", Tag: "x", Type: TypeOf(byte(0)), }, { Name: "Y", Type: TypeOf(uint64(0)), }, { Name: "Z", Type: TypeOf([3]uint16{}), }, } st := StructOf(fields) v := New(st).Elem() runtime.GC() v.FieldByName("X").Set(ValueOf(byte(2))) v.FieldByIndex([]int{1}).Set(ValueOf(byte(1))) runtime.GC() s := fmt.Sprint(v.Interface()) want := `{ 1 0 [0 0 0]}` if s != want { t.Errorf("constructed struct = %s, want %s", s, want) } const stStr = `struct { S string "s"; X uint8 "x"; Y uint64; Z [3]uint16 }` if got, want := st.String(), stStr; got != want { t.Errorf("StructOf(fields).String()=%q, want %q", got, want) } // check the size, alignment and field offsets stt := TypeOf(struct { String string X byte Y uint64 Z [3]uint16 }{}) if st.Size() != stt.Size() { t.Errorf("constructed struct size = %v, want %v", st.Size(), stt.Size()) } if st.Align() != stt.Align() { t.Errorf("constructed struct align = %v, want %v", st.Align(), stt.Align()) } if st.FieldAlign() != stt.FieldAlign() { t.Errorf("constructed struct field align = %v, want %v", st.FieldAlign(), stt.FieldAlign()) } for i := 0; i < st.NumField(); i++ { o1 := st.Field(i).Offset o2 := stt.Field(i).Offset if o1 != o2 { t.Errorf("constructed struct field %v offset = %v, want %v", i, o1, o2) } } // Check size and alignment with a trailing zero-sized field. st = StructOf([]StructField{ { Name: "F1", Type: TypeOf(byte(0)), }, { Name: "F2", Type: TypeOf([0]*byte{}), }, }) stt = TypeOf(struct { G1 byte G2 [0]*byte }{}) if st.Size() != stt.Size() { t.Errorf("constructed zero-padded struct size = %v, want %v", st.Size(), stt.Size()) } if st.Align() != stt.Align() { t.Errorf("constructed zero-padded struct align = %v, want %v", st.Align(), stt.Align()) } if st.FieldAlign() != stt.FieldAlign() { t.Errorf("constructed zero-padded struct field align = %v, want %v", st.FieldAlign(), stt.FieldAlign()) } for i := 0; i < st.NumField(); i++ { o1 := st.Field(i).Offset o2 := stt.Field(i).Offset if o1 != o2 { t.Errorf("constructed zero-padded struct field %v offset = %v, want %v", i, o1, o2) } } // check duplicate names shouldPanic("duplicate field", func() { StructOf([]StructField{ {Name: "string", PkgPath: "p", Type: TypeOf("")}, {Name: "string", PkgPath: "p", Type: TypeOf("")}, }) }) shouldPanic("has no name", func() { StructOf([]StructField{ {Type: TypeOf("")}, {Name: "string", PkgPath: "p", Type: TypeOf("")}, }) }) shouldPanic("has no name", func() { StructOf([]StructField{ {Type: TypeOf("")}, {Type: TypeOf("")}, }) }) // check that type already in binary is found checkSameType(t, StructOf(fields[2:3]), struct{ Y uint64 }{}) // gccgo used to fail this test. type structFieldType any checkSameType(t, StructOf([]StructField{ { Name: "F", Type: TypeOf((*structFieldType)(nil)).Elem(), }, }), struct{ F structFieldType }{}) } func TestStructOfExportRules(t *testing.T) { type S1 struct{} type s2 struct{} type ΦType struct{} type φType struct{} testPanic := func(i int, mustPanic bool, f func()) { defer func() { err := recover() if err == nil && mustPanic { t.Errorf("test-%d did not panic", i) } if err != nil && !mustPanic { t.Errorf("test-%d panicked: %v\n", i, err) } }() f() } tests := []struct { field StructField mustPanic bool exported bool }{ { field: StructField{Name: "S1", Anonymous: true, Type: TypeOf(S1{})}, exported: true, }, { field: StructField{Name: "S1", Anonymous: true, Type: TypeOf((*S1)(nil))}, exported: true, }, { field: StructField{Name: "s2", Anonymous: true, Type: TypeOf(s2{})}, mustPanic: true, }, { field: StructField{Name: "s2", Anonymous: true, Type: TypeOf((*s2)(nil))}, mustPanic: true, }, { field: StructField{Name: "Name", Type: nil, PkgPath: ""}, mustPanic: true, }, { field: StructField{Name: "", Type: TypeOf(S1{}), PkgPath: ""}, mustPanic: true, }, { field: StructField{Name: "S1", Anonymous: true, Type: TypeOf(S1{}), PkgPath: "other/pkg"}, mustPanic: true, }, { field: StructField{Name: "S1", Anonymous: true, Type: TypeOf((*S1)(nil)), PkgPath: "other/pkg"}, mustPanic: true, }, { field: StructField{Name: "s2", Anonymous: true, Type: TypeOf(s2{}), PkgPath: "other/pkg"}, mustPanic: true, }, { field: StructField{Name: "s2", Anonymous: true, Type: TypeOf((*s2)(nil)), PkgPath: "other/pkg"}, mustPanic: true, }, { field: StructField{Name: "s2", Type: TypeOf(int(0)), PkgPath: "other/pkg"}, }, { field: StructField{Name: "s2", Type: TypeOf(int(0)), PkgPath: "other/pkg"}, }, { field: StructField{Name: "S", Type: TypeOf(S1{})}, exported: true, }, { field: StructField{Name: "S", Type: TypeOf((*S1)(nil))}, exported: true, }, { field: StructField{Name: "S", Type: TypeOf(s2{})}, exported: true, }, { field: StructField{Name: "S", Type: TypeOf((*s2)(nil))}, exported: true, }, { field: StructField{Name: "s", Type: TypeOf(S1{})}, mustPanic: true, }, { field: StructField{Name: "s", Type: TypeOf((*S1)(nil))}, mustPanic: true, }, { field: StructField{Name: "s", Type: TypeOf(s2{})}, mustPanic: true, }, { field: StructField{Name: "s", Type: TypeOf((*s2)(nil))}, mustPanic: true, }, { field: StructField{Name: "s", Type: TypeOf(S1{}), PkgPath: "other/pkg"}, }, { field: StructField{Name: "s", Type: TypeOf((*S1)(nil)), PkgPath: "other/pkg"}, }, { field: StructField{Name: "s", Type: TypeOf(s2{}), PkgPath: "other/pkg"}, }, { field: StructField{Name: "s", Type: TypeOf((*s2)(nil)), PkgPath: "other/pkg"}, }, { field: StructField{Name: "", Type: TypeOf(ΦType{})}, mustPanic: true, }, { field: StructField{Name: "", Type: TypeOf(φType{})}, mustPanic: true, }, { field: StructField{Name: "Φ", Type: TypeOf(0)}, exported: true, }, { field: StructField{Name: "φ", Type: TypeOf(0)}, exported: false, }, } for i, test := range tests { testPanic(i, test.mustPanic, func() { typ := StructOf([]StructField{test.field}) if typ == nil { t.Errorf("test-%d: error creating struct type", i) return } field := typ.Field(0) n := field.Name if n == "" { panic("field.Name must not be empty") } exported := token.IsExported(n) if exported != test.exported { t.Errorf("test-%d: got exported=%v want exported=%v", i, exported, test.exported) } if field.PkgPath != test.field.PkgPath { t.Errorf("test-%d: got PkgPath=%q want pkgPath=%q", i, field.PkgPath, test.field.PkgPath) } }) } } func TestStructOfGC(t *testing.T) { type T *uintptr tt := TypeOf(T(nil)) fields := []StructField{ {Name: "X", Type: tt}, {Name: "Y", Type: tt}, } st := StructOf(fields) const n = 10000 var x []any for i := 0; i < n; i++ { v := New(st).Elem() for j := 0; j < v.NumField(); j++ { p := new(uintptr) *p = uintptr(i*n + j) v.Field(j).Set(ValueOf(p).Convert(tt)) } x = append(x, v.Interface()) } runtime.GC() for i, xi := range x { v := ValueOf(xi) for j := 0; j < v.NumField(); j++ { k := v.Field(j).Elem().Interface() if k != uintptr(i*n+j) { t.Errorf("lost x[%d].%c = %d, want %d", i, "XY"[j], k, i*n+j) } } } } func TestStructOfAlg(t *testing.T) { st := StructOf([]StructField{{Name: "X", Tag: "x", Type: TypeOf(int(0))}}) v1 := New(st).Elem() v2 := New(st).Elem() if !DeepEqual(v1.Interface(), v1.Interface()) { t.Errorf("constructed struct %v not equal to itself", v1.Interface()) } v1.FieldByName("X").Set(ValueOf(int(1))) if i1, i2 := v1.Interface(), v2.Interface(); DeepEqual(i1, i2) { t.Errorf("constructed structs %v and %v should not be equal", i1, i2) } st = StructOf([]StructField{{Name: "X", Tag: "x", Type: TypeOf([]int(nil))}}) v1 = New(st).Elem() shouldPanic("", func() { _ = v1.Interface() == v1.Interface() }) } func TestStructOfGenericAlg(t *testing.T) { st1 := StructOf([]StructField{ {Name: "X", Tag: "x", Type: TypeOf(int64(0))}, {Name: "Y", Type: TypeOf(string(""))}, }) st := StructOf([]StructField{ {Name: "S0", Type: st1}, {Name: "S1", Type: st1}, }) tests := []struct { rt Type idx []int }{ { rt: st, idx: []int{0, 1}, }, { rt: st1, idx: []int{1}, }, { rt: StructOf( []StructField{ {Name: "XX", Type: TypeOf([0]int{})}, {Name: "YY", Type: TypeOf("")}, }, ), idx: []int{1}, }, { rt: StructOf( []StructField{ {Name: "XX", Type: TypeOf([0]int{})}, {Name: "YY", Type: TypeOf("")}, {Name: "ZZ", Type: TypeOf([2]int{})}, }, ), idx: []int{1}, }, { rt: StructOf( []StructField{ {Name: "XX", Type: TypeOf([1]int{})}, {Name: "YY", Type: TypeOf("")}, }, ), idx: []int{1}, }, { rt: StructOf( []StructField{ {Name: "XX", Type: TypeOf([1]int{})}, {Name: "YY", Type: TypeOf("")}, {Name: "ZZ", Type: TypeOf([1]int{})}, }, ), idx: []int{1}, }, { rt: StructOf( []StructField{ {Name: "XX", Type: TypeOf([2]int{})}, {Name: "YY", Type: TypeOf("")}, {Name: "ZZ", Type: TypeOf([2]int{})}, }, ), idx: []int{1}, }, { rt: StructOf( []StructField{ {Name: "XX", Type: TypeOf(int64(0))}, {Name: "YY", Type: TypeOf(byte(0))}, {Name: "ZZ", Type: TypeOf("")}, }, ), idx: []int{2}, }, { rt: StructOf( []StructField{ {Name: "XX", Type: TypeOf(int64(0))}, {Name: "YY", Type: TypeOf(int64(0))}, {Name: "ZZ", Type: TypeOf("")}, {Name: "AA", Type: TypeOf([1]int64{})}, }, ), idx: []int{2}, }, } for _, table := range tests { v1 := New(table.rt).Elem() v2 := New(table.rt).Elem() if !DeepEqual(v1.Interface(), v1.Interface()) { t.Errorf("constructed struct %v not equal to itself", v1.Interface()) } v1.FieldByIndex(table.idx).Set(ValueOf("abc")) v2.FieldByIndex(table.idx).Set(ValueOf("def")) if i1, i2 := v1.Interface(), v2.Interface(); DeepEqual(i1, i2) { t.Errorf("constructed structs %v and %v should not be equal", i1, i2) } abc := "abc" v1.FieldByIndex(table.idx).Set(ValueOf(abc)) val := "+" + abc + "-" v2.FieldByIndex(table.idx).Set(ValueOf(val[1:4])) if i1, i2 := v1.Interface(), v2.Interface(); !DeepEqual(i1, i2) { t.Errorf("constructed structs %v and %v should be equal", i1, i2) } // Test hash m := MakeMap(MapOf(table.rt, TypeOf(int(0)))) m.SetMapIndex(v1, ValueOf(1)) if i1, i2 := v1.Interface(), v2.Interface(); !m.MapIndex(v2).IsValid() { t.Errorf("constructed structs %#v and %#v have different hashes", i1, i2) } v2.FieldByIndex(table.idx).Set(ValueOf("abc")) if i1, i2 := v1.Interface(), v2.Interface(); !DeepEqual(i1, i2) { t.Errorf("constructed structs %v and %v should be equal", i1, i2) } if i1, i2 := v1.Interface(), v2.Interface(); !m.MapIndex(v2).IsValid() { t.Errorf("constructed structs %v and %v have different hashes", i1, i2) } } } func TestStructOfDirectIface(t *testing.T) { { type T struct{ X [1]*byte } i1 := Zero(TypeOf(T{})).Interface() v1 := ValueOf(&i1).Elem() p1 := v1.InterfaceData()[1] i2 := Zero(StructOf([]StructField{ { Name: "X", Type: ArrayOf(1, TypeOf((*int8)(nil))), }, })).Interface() v2 := ValueOf(&i2).Elem() p2 := v2.InterfaceData()[1] if p1 != 0 { t.Errorf("got p1=%v. want=%v", p1, nil) } if p2 != 0 { t.Errorf("got p2=%v. want=%v", p2, nil) } } { type T struct{ X [0]*byte } i1 := Zero(TypeOf(T{})).Interface() v1 := ValueOf(&i1).Elem() p1 := v1.InterfaceData()[1] i2 := Zero(StructOf([]StructField{ { Name: "X", Type: ArrayOf(0, TypeOf((*int8)(nil))), }, })).Interface() v2 := ValueOf(&i2).Elem() p2 := v2.InterfaceData()[1] if p1 == 0 { t.Errorf("got p1=%v. want=not-%v", p1, nil) } if p2 == 0 { t.Errorf("got p2=%v. want=not-%v", p2, nil) } } } type StructI int func (i StructI) Get() int { return int(i) } type StructIPtr int func (i *StructIPtr) Get() int { return int(*i) } func (i *StructIPtr) Set(v int) { *(*int)(i) = v } type SettableStruct struct { SettableField int } func (p *SettableStruct) Set(v int) { p.SettableField = v } type SettablePointer struct { SettableField *int } func (p *SettablePointer) Set(v int) { *p.SettableField = v } func TestStructOfWithInterface(t *testing.T) { const want = 42 type Iface interface { Get() int } type IfaceSet interface { Set(int) } tests := []struct { name string typ Type val Value impl bool }{ { name: "StructI", typ: TypeOf(StructI(want)), val: ValueOf(StructI(want)), impl: true, }, { name: "StructI", typ: PointerTo(TypeOf(StructI(want))), val: ValueOf(func() any { v := StructI(want) return &v }()), impl: true, }, { name: "StructIPtr", typ: PointerTo(TypeOf(StructIPtr(want))), val: ValueOf(func() any { v := StructIPtr(want) return &v }()), impl: true, }, { name: "StructIPtr", typ: TypeOf(StructIPtr(want)), val: ValueOf(StructIPtr(want)), impl: false, }, // { // typ: TypeOf((*Iface)(nil)).Elem(), // FIXME(sbinet): fix method.ifn/tfn // val: ValueOf(StructI(want)), // impl: true, // }, } for i, table := range tests { for j := 0; j < 2; j++ { var fields []StructField if j == 1 { fields = append(fields, StructField{ Name: "Dummy", PkgPath: "", Type: TypeOf(int(0)), }) } fields = append(fields, StructField{ Name: table.name, Anonymous: true, PkgPath: "", Type: table.typ, }) // We currently do not correctly implement methods // for embedded fields other than the first. // Therefore, for now, we expect those methods // to not exist. See issues 15924 and 20824. // When those issues are fixed, this test of panic // should be removed. if j == 1 && table.impl { func() { defer func() { if err := recover(); err == nil { t.Errorf("test-%d-%d did not panic", i, j) } }() _ = StructOf(fields) }() continue } rt := StructOf(fields) rv := New(rt).Elem() rv.Field(j).Set(table.val) if _, ok := rv.Interface().(Iface); ok != table.impl { if table.impl { t.Errorf("test-%d-%d: type=%v fails to implement Iface.\n", i, j, table.typ) } else { t.Errorf("test-%d-%d: type=%v should NOT implement Iface\n", i, j, table.typ) } continue } if !table.impl { continue } v := rv.Interface().(Iface).Get() if v != want { t.Errorf("test-%d-%d: x.Get()=%v. want=%v\n", i, j, v, want) } fct := rv.MethodByName("Get") out := fct.Call(nil) if !DeepEqual(out[0].Interface(), want) { t.Errorf("test-%d-%d: x.Get()=%v. want=%v\n", i, j, out[0].Interface(), want) } } } // Test an embedded nil pointer with pointer methods. fields := []StructField{{ Name: "StructIPtr", Anonymous: true, Type: PointerTo(TypeOf(StructIPtr(want))), }} rt := StructOf(fields) rv := New(rt).Elem() // This should panic since the pointer is nil. shouldPanic("", func() { rv.Interface().(IfaceSet).Set(want) }) // Test an embedded nil pointer to a struct with pointer methods. fields = []StructField{{ Name: "SettableStruct", Anonymous: true, Type: PointerTo(TypeOf(SettableStruct{})), }} rt = StructOf(fields) rv = New(rt).Elem() // This should panic since the pointer is nil. shouldPanic("", func() { rv.Interface().(IfaceSet).Set(want) }) // The behavior is different if there is a second field, // since now an interface value holds a pointer to the struct // rather than just holding a copy of the struct. fields = []StructField{ { Name: "SettableStruct", Anonymous: true, Type: PointerTo(TypeOf(SettableStruct{})), }, { Name: "EmptyStruct", Anonymous: true, Type: StructOf(nil), }, } // With the current implementation this is expected to panic. // Ideally it should work and we should be able to see a panic // if we call the Set method. shouldPanic("", func() { StructOf(fields) }) // Embed a field that can be stored directly in an interface, // with a second field. fields = []StructField{ { Name: "SettablePointer", Anonymous: true, Type: TypeOf(SettablePointer{}), }, { Name: "EmptyStruct", Anonymous: true, Type: StructOf(nil), }, } // With the current implementation this is expected to panic. // Ideally it should work and we should be able to call the // Set and Get methods. shouldPanic("", func() { StructOf(fields) }) } func TestStructOfTooManyFields(t *testing.T) { // Bug Fix: #25402 - this should not panic tt := StructOf([]StructField{ {Name: "Time", Type: TypeOf(time.Time{}), Anonymous: true}, }) if _, present := tt.MethodByName("After"); !present { t.Errorf("Expected method `After` to be found") } } func TestStructOfDifferentPkgPath(t *testing.T) { fields := []StructField{ { Name: "f1", PkgPath: "p1", Type: TypeOf(int(0)), }, { Name: "f2", PkgPath: "p2", Type: TypeOf(int(0)), }, } shouldPanic("different PkgPath", func() { StructOf(fields) }) } func TestStructOfTooLarge(t *testing.T) { t1 := TypeOf(byte(0)) t2 := TypeOf(int16(0)) t4 := TypeOf(int32(0)) t0 := ArrayOf(0, t1) // 2^64-3 sized type (or 2^32-3 on 32-bit archs) bigType := StructOf([]StructField{ {Name: "F1", Type: ArrayOf(int(^uintptr(0)>>1), t1)}, {Name: "F2", Type: ArrayOf(int(^uintptr(0)>>1-1), t1)}, }) type test struct { shouldPanic bool fields []StructField } tests := [...]test{ { shouldPanic: false, // 2^64-1, ok fields: []StructField{ {Name: "F1", Type: bigType}, {Name: "F2", Type: ArrayOf(2, t1)}, }, }, { shouldPanic: true, // overflow in total size fields: []StructField{ {Name: "F1", Type: bigType}, {Name: "F2", Type: ArrayOf(3, t1)}, }, }, { shouldPanic: true, // overflow while aligning F2 fields: []StructField{ {Name: "F1", Type: bigType}, {Name: "F2", Type: t4}, }, }, { shouldPanic: true, // overflow while adding trailing byte for zero-sized fields fields: []StructField{ {Name: "F1", Type: bigType}, {Name: "F2", Type: ArrayOf(2, t1)}, {Name: "F3", Type: t0}, }, }, { shouldPanic: true, // overflow while aligning total size fields: []StructField{ {Name: "F1", Type: t2}, {Name: "F2", Type: bigType}, }, }, } for i, tt := range tests { func() { defer func() { err := recover() if !tt.shouldPanic { if err != nil { t.Errorf("test %d should not panic, got %s", i, err) } return } if err == nil { t.Errorf("test %d expected to panic", i) return } s := fmt.Sprintf("%s", err) if s != "reflect.StructOf: struct size would exceed virtual address space" { t.Errorf("test %d wrong panic message: %s", i, s) return } }() _ = StructOf(tt.fields) }() } } func TestChanOf(t *testing.T) { // check construction and use of type not in binary type T string ct := ChanOf(BothDir, TypeOf(T(""))) v := MakeChan(ct, 2) runtime.GC() v.Send(ValueOf(T("hello"))) runtime.GC() v.Send(ValueOf(T("world"))) runtime.GC() sv1, _ := v.Recv() sv2, _ := v.Recv() s1 := sv1.String() s2 := sv2.String() if s1 != "hello" || s2 != "world" { t.Errorf("constructed chan: have %q, %q, want %q, %q", s1, s2, "hello", "world") } // check that type already in binary is found type T1 int checkSameType(t, ChanOf(BothDir, TypeOf(T1(1))), (chan T1)(nil)) // Check arrow token association in undefined chan types. var left chan<- chan T var right chan (<-chan T) tLeft := ChanOf(SendDir, ChanOf(BothDir, TypeOf(T("")))) tRight := ChanOf(BothDir, ChanOf(RecvDir, TypeOf(T("")))) if tLeft != TypeOf(left) { t.Errorf("chan<-chan: have %s, want %T", tLeft, left) } if tRight != TypeOf(right) { t.Errorf("chan<-chan: have %s, want %T", tRight, right) } } func TestChanOfDir(t *testing.T) { // check construction and use of type not in binary type T string crt := ChanOf(RecvDir, TypeOf(T(""))) cst := ChanOf(SendDir, TypeOf(T(""))) // check that type already in binary is found type T1 int checkSameType(t, ChanOf(RecvDir, TypeOf(T1(1))), (<-chan T1)(nil)) checkSameType(t, ChanOf(SendDir, TypeOf(T1(1))), (chan<- T1)(nil)) // check String form of ChanDir if crt.ChanDir().String() != "<-chan" { t.Errorf("chan dir: have %q, want %q", crt.ChanDir().String(), "<-chan") } if cst.ChanDir().String() != "chan<-" { t.Errorf("chan dir: have %q, want %q", cst.ChanDir().String(), "chan<-") } } func TestChanOfGC(t *testing.T) { done := make(chan bool, 1) go func() { select { case <-done: case <-time.After(5 * time.Second): panic("deadlock in TestChanOfGC") } }() defer func() { done <- true }() type T *uintptr tt := TypeOf(T(nil)) ct := ChanOf(BothDir, tt) // NOTE: The garbage collector handles allocated channels specially, // so we have to save pointers to channels in x; the pointer code will // use the gc info in the newly constructed chan type. const n = 100 var x []any for i := 0; i < n; i++ { v := MakeChan(ct, n) for j := 0; j < n; j++ { p := new(uintptr) *p = uintptr(i*n + j) v.Send(ValueOf(p).Convert(tt)) } pv := New(ct) pv.Elem().Set(v) x = append(x, pv.Interface()) } runtime.GC() for i, xi := range x { v := ValueOf(xi).Elem() for j := 0; j < n; j++ { pv, _ := v.Recv() k := pv.Elem().Interface() if k != uintptr(i*n+j) { t.Errorf("lost x[%d][%d] = %d, want %d", i, j, k, i*n+j) } } } } func TestMapOf(t *testing.T) { // check construction and use of type not in binary type K string type V float64 v := MakeMap(MapOf(TypeOf(K("")), TypeOf(V(0)))) runtime.GC() v.SetMapIndex(ValueOf(K("a")), ValueOf(V(1))) runtime.GC() s := fmt.Sprint(v.Interface()) want := "map[a:1]" if s != want { t.Errorf("constructed map = %s, want %s", s, want) } // check that type already in binary is found checkSameType(t, MapOf(TypeOf(V(0)), TypeOf(K(""))), map[V]K(nil)) // check that invalid key type panics shouldPanic("invalid key type", func() { MapOf(TypeOf((func())(nil)), TypeOf(false)) }) } func TestMapOfGCKeys(t *testing.T) { type T *uintptr tt := TypeOf(T(nil)) mt := MapOf(tt, TypeOf(false)) // NOTE: The garbage collector handles allocated maps specially, // so we have to save pointers to maps in x; the pointer code will // use the gc info in the newly constructed map type. const n = 100 var x []any for i := 0; i < n; i++ { v := MakeMap(mt) for j := 0; j < n; j++ { p := new(uintptr) *p = uintptr(i*n + j) v.SetMapIndex(ValueOf(p).Convert(tt), ValueOf(true)) } pv := New(mt) pv.Elem().Set(v) x = append(x, pv.Interface()) } runtime.GC() for i, xi := range x { v := ValueOf(xi).Elem() var out []int for _, kv := range v.MapKeys() { out = append(out, int(kv.Elem().Interface().(uintptr))) } sort.Ints(out) for j, k := range out { if k != i*n+j { t.Errorf("lost x[%d][%d] = %d, want %d", i, j, k, i*n+j) } } } } func TestMapOfGCValues(t *testing.T) { type T *uintptr tt := TypeOf(T(nil)) mt := MapOf(TypeOf(1), tt) // NOTE: The garbage collector handles allocated maps specially, // so we have to save pointers to maps in x; the pointer code will // use the gc info in the newly constructed map type. const n = 100 var x []any for i := 0; i < n; i++ { v := MakeMap(mt) for j := 0; j < n; j++ { p := new(uintptr) *p = uintptr(i*n + j) v.SetMapIndex(ValueOf(j), ValueOf(p).Convert(tt)) } pv := New(mt) pv.Elem().Set(v) x = append(x, pv.Interface()) } runtime.GC() for i, xi := range x { v := ValueOf(xi).Elem() for j := 0; j < n; j++ { k := v.MapIndex(ValueOf(j)).Elem().Interface().(uintptr) if k != uintptr(i*n+j) { t.Errorf("lost x[%d][%d] = %d, want %d", i, j, k, i*n+j) } } } } func TestTypelinksSorted(t *testing.T) { var last string for i, n := range TypeLinks() { if n < last { t.Errorf("typelinks not sorted: %q [%d] > %q [%d]", last, i-1, n, i) } last = n } } func TestFuncOf(t *testing.T) { // check construction and use of type not in binary type K string type V float64 fn := func(args []Value) []Value { if len(args) != 1 { t.Errorf("args == %v, want exactly one arg", args) } else if args[0].Type() != TypeOf(K("")) { t.Errorf("args[0] is type %v, want %v", args[0].Type(), TypeOf(K(""))) } else if args[0].String() != "gopher" { t.Errorf("args[0] = %q, want %q", args[0].String(), "gopher") } return []Value{ValueOf(V(3.14))} } v := MakeFunc(FuncOf([]Type{TypeOf(K(""))}, []Type{TypeOf(V(0))}, false), fn) outs := v.Call([]Value{ValueOf(K("gopher"))}) if len(outs) != 1 { t.Fatalf("v.Call returned %v, want exactly one result", outs) } else if outs[0].Type() != TypeOf(V(0)) { t.Fatalf("c.Call[0] is type %v, want %v", outs[0].Type(), TypeOf(V(0))) } f := outs[0].Float() if f != 3.14 { t.Errorf("constructed func returned %f, want %f", f, 3.14) } // check that types already in binary are found type T1 int testCases := []struct { in, out []Type variadic bool want any }{ {in: []Type{TypeOf(T1(0))}, want: (func(T1))(nil)}, {in: []Type{TypeOf(int(0))}, want: (func(int))(nil)}, {in: []Type{SliceOf(TypeOf(int(0)))}, variadic: true, want: (func(...int))(nil)}, {in: []Type{TypeOf(int(0))}, out: []Type{TypeOf(false)}, want: (func(int) bool)(nil)}, {in: []Type{TypeOf(int(0))}, out: []Type{TypeOf(false), TypeOf("")}, want: (func(int) (bool, string))(nil)}, } for _, tt := range testCases { checkSameType(t, FuncOf(tt.in, tt.out, tt.variadic), tt.want) } // check that variadic requires last element be a slice. FuncOf([]Type{TypeOf(1), TypeOf(""), SliceOf(TypeOf(false))}, nil, true) shouldPanic("must be slice", func() { FuncOf([]Type{TypeOf(0), TypeOf(""), TypeOf(false)}, nil, true) }) shouldPanic("must be slice", func() { FuncOf(nil, nil, true) }) //testcase for #54669 var in []Type for i := 0; i < 51; i++ { in = append(in, TypeOf(1)) } FuncOf(in, nil, false) } */ type R0 struct { *R1 *R2 *R3 *R4 } type R1 struct { *R5 *R6 *R7 *R8 } type R2 R1 type R3 R1 type R4 R1 type R5 struct { *R9 *R10 *R11 *R12 } type R6 R5 type R7 R5 type R8 R5 type R9 struct { *R13 *R14 *R15 *R16 } type R10 R9 type R11 R9 type R12 R9 type R13 struct { *R17 *R18 *R19 *R20 } type R14 R13 type R15 R13 type R16 R13 type R17 struct { *R21 *R22 *R23 *R24 } type R18 R17 type R19 R17 type R20 R17 type R21 struct { X int } type R22 R21 type R23 R21 type R24 R21 func TestEmbed(t *testing.T) { typ := TypeOf(R0{}) f, ok := typ.FieldByName("X") if ok { t.Fatalf(`FieldByName("X") should fail, returned %v`, f.Index) } } /* func TestAllocsInterfaceBig(t *testing.T) { if testing.Short() { t.Skip("skipping malloc count in short mode") } v := ValueOf(S{}) if allocs := testing.AllocsPerRun(100, func() { v.Interface() }); allocs > 0 { t.Error("allocs:", allocs) } } func TestAllocsInterfaceSmall(t *testing.T) { if testing.Short() { t.Skip("skipping malloc count in short mode") } v := ValueOf(int64(0)) if allocs := testing.AllocsPerRun(100, func() { v.Interface() }); allocs > 0 { t.Error("allocs:", allocs) } } // An exhaustive is a mechanism for writing exhaustive or stochastic tests. // The basic usage is: // // for x.Next() { // ... code using x.Maybe() or x.Choice(n) to create test cases ... // } // // Each iteration of the loop returns a different set of results, until all // possible result sets have been explored. It is okay for different code paths // to make different method call sequences on x, but there must be no // other source of non-determinism in the call sequences. // // When faced with a new decision, x chooses randomly. Future explorations // of that path will choose successive values for the result. Thus, stopping // the loop after a fixed number of iterations gives somewhat stochastic // testing. // // Example: // // for x.Next() { // v := make([]bool, x.Choose(4)) // for i := range v { // v[i] = x.Maybe() // } // fmt.Println(v) // } // // prints (in some order): // // [] // [false] // [true] // [false false] // [false true] // ... // [true true] // [false false false] // ... // [true true true] // [false false false false] // ... // [true true true true] type exhaustive struct { r *rand.Rand pos int last []choice } type choice struct { off int n int max int } func (x *exhaustive) Next() bool { if x.r == nil { x.r = rand.New(rand.NewSource(time.Now().UnixNano())) } x.pos = 0 if x.last == nil { x.last = []choice{} return true } for i := len(x.last) - 1; i >= 0; i-- { c := &x.last[i] if c.n+1 < c.max { c.n++ x.last = x.last[:i+1] return true } } return false } func (x *exhaustive) Choose(max int) int { if x.pos >= len(x.last) { x.last = append(x.last, choice{x.r.Intn(max), 0, max}) } c := &x.last[x.pos] x.pos++ if c.max != max { panic("inconsistent use of exhaustive tester") } return (c.n + c.off) % max } func (x *exhaustive) Maybe() bool { return x.Choose(2) == 1 } func GCFunc(args []Value) []Value { runtime.GC() return []Value{} } func TestReflectFuncTraceback(t *testing.T) { f := MakeFunc(TypeOf(func() {}), GCFunc) f.Call([]Value{}) } func TestReflectMethodTraceback(t *testing.T) { p := Point{3, 4} m := ValueOf(p).MethodByName("GCMethod") i := ValueOf(m.Interface()).Call([]Value{ValueOf(5)})[0].Int() if i != 8 { t.Errorf("Call returned %d; want 8", i) } } func TestSmallZero(t *testing.T) { type T [10]byte typ := TypeOf(T{}) if allocs := testing.AllocsPerRun(100, func() { Zero(typ) }); allocs > 0 { t.Errorf("Creating small zero values caused %f allocs, want 0", allocs) } } func TestBigZero(t *testing.T) { const size = 1 << 10 var v [size]byte z := Zero(ValueOf(v).Type()).Interface().([size]byte) for i := 0; i < size; i++ { if z[i] != 0 { t.Fatalf("Zero object not all zero, index %d", i) } } } func TestZeroSet(t *testing.T) { type T [16]byte type S struct { a uint64 T T b uint64 } v := S{ a: 0xaaaaaaaaaaaaaaaa, T: T{9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}, b: 0xbbbbbbbbbbbbbbbb, } ValueOf(&v).Elem().Field(1).Set(Zero(TypeOf(T{}))) if v != (S{ a: 0xaaaaaaaaaaaaaaaa, b: 0xbbbbbbbbbbbbbbbb, }) { t.Fatalf("Setting a field to a Zero value didn't work") } } func TestFieldByIndexNil(t *testing.T) { type P struct { F int } type T struct { *P } v := ValueOf(T{}) v.FieldByName("P") // should be fine defer func() { if err := recover(); err == nil { t.Fatalf("no error") } else if !strings.Contains(fmt.Sprint(err), "nil pointer to embedded struct") { t.Fatalf(`err=%q, wanted error containing "nil pointer to embedded struct"`, err) } }() v.FieldByName("F") // should panic t.Fatalf("did not panic") } // Given // type Outer struct { // *Inner // ... // } // the compiler generates the implementation of (*Outer).M dispatching to the embedded Inner. // The implementation is logically: // func (p *Outer) M() { // (p.Inner).M() // } // but since the only change here is the replacement of one pointer receiver with another, // the actual generated code overwrites the original receiver with the p.Inner pointer and // then jumps to the M method expecting the *Inner receiver. // // During reflect.Value.Call, we create an argument frame and the associated data structures // to describe it to the garbage collector, populate the frame, call reflect.call to // run a function call using that frame, and then copy the results back out of the frame. // The reflect.call function does a memmove of the frame structure onto the // stack (to set up the inputs), runs the call, and the memmoves the stack back to // the frame structure (to preserve the outputs). // // Originally reflect.call did not distinguish inputs from outputs: both memmoves // were for the full stack frame. However, in the case where the called function was // one of these wrappers, the rewritten receiver is almost certainly a different type // than the original receiver. This is not a problem on the stack, where we use the // program counter to determine the type information and understand that // during (*Outer).M the receiver is an *Outer while during (*Inner).M the receiver in the same // memory word is now an *Inner. But in the statically typed argument frame created // by reflect, the receiver is always an *Outer. Copying the modified receiver pointer // off the stack into the frame will store an *Inner there, and then if a garbage collection // happens to scan that argument frame before it is discarded, it will scan the *Inner // memory as if it were an *Outer. If the two have different memory layouts, the // collection will interpret the memory incorrectly. // // One such possible incorrect interpretation is to treat two arbitrary memory words // (Inner.P1 and Inner.P2 below) as an interface (Outer.R below). Because interpreting // an interface requires dereferencing the itab word, the misinterpretation will try to // deference Inner.P1, causing a crash during garbage collection. // // This came up in a real program in issue 7725. type Outer struct { *Inner R io.Reader } type Inner struct { X *Outer P1 uintptr P2 uintptr } func (pi *Inner) M() { // Clear references to pi so that the only way the // garbage collection will find the pointer is in the // argument frame, typed as a *Outer. pi.X.Inner = nil // Set up an interface value that will cause a crash. // P1 = 1 is a non-zero, so the interface looks non-nil. // P2 = pi ensures that the data word points into the // allocated heap; if not the collection skips the interface // value as irrelevant, without dereferencing P1. pi.P1 = 1 pi.P2 = uintptr(unsafe.Pointer(pi)) } func TestCallMethodJump(t *testing.T) { // In reflect.Value.Call, trigger a garbage collection after reflect.call // returns but before the args frame has been discarded. // This is a little clumsy but makes the failure repeatable. *CallGC = true p := &Outer{Inner: new(Inner)} p.Inner.X = p ValueOf(p).Method(0).Call(nil) // Stop garbage collecting during reflect.call. *CallGC = false } func TestCallArgLive(t *testing.T) { type T struct{ X, Y *string } // pointerful aggregate F := func(t T) { *t.X = "ok" } // In reflect.Value.Call, trigger a garbage collection in reflect.call // between marshaling argument and the actual call. *CallGC = true x := new(string) runtime.SetFinalizer(x, func(p *string) { if *p != "ok" { t.Errorf("x dead prematurely") } }) v := T{x, nil} ValueOf(F).Call([]Value{ValueOf(v)}) // Stop garbage collecting during reflect.call. *CallGC = false } func TestMakeFuncStackCopy(t *testing.T) { target := func(in []Value) []Value { runtime.GC() useStack(16) return []Value{ValueOf(9)} } var concrete func(*int, int) int fn := MakeFunc(ValueOf(concrete).Type(), target) ValueOf(&concrete).Elem().Set(fn) x := concrete(nil, 7) if x != 9 { t.Errorf("have %#q want 9", x) } } // use about n KB of stack func useStack(n int) { if n == 0 { return } var b [1024]byte // makes frame about 1KB useStack(n - 1 + int(b[99])) } type Impl struct{} func (Impl) F() {} func TestValueString(t *testing.T) { rv := ValueOf(Impl{}) if rv.String() != "" { t.Errorf("ValueOf(Impl{}).String() = %q, want %q", rv.String(), "") } method := rv.Method(0) if method.String() != "" { t.Errorf("ValueOf(Impl{}).Method(0).String() = %q, want %q", method.String(), "") } } */ func TestInvalid(t *testing.T) { // Used to have inconsistency between IsValid() and Kind() != Invalid. type T struct{ v any } v := ValueOf(T{}).Field(0) if v.IsValid() != true || v.Kind() != Interface { t.Errorf("field: IsValid=%v, Kind=%v, want true, Interface", v.IsValid(), v.Kind()) } v = v.Elem() if v.IsValid() != false || v.Kind() != Invalid { t.Errorf("field elem: IsValid=%v, Kind=%v, want false, Invalid", v.IsValid(), v.Kind()) } } /* // Issue 8917. func TestLargeGCProg(t *testing.T) { fv := ValueOf(func([256]*byte) {}) fv.Call([]Value{ValueOf([256]*byte{})}) } func fieldIndexRecover(t Type, i int) (recovered any) { defer func() { recovered = recover() }() t.Field(i) return } // Issue 15046. func TestTypeFieldOutOfRangePanic(t *testing.T) { typ := TypeOf(struct{ X int }{10}) testIndices := [...]struct { i int mustPanic bool }{ 0: {-2, true}, 1: {0, false}, 2: {1, true}, 3: {1 << 10, true}, } for i, tt := range testIndices { recoveredErr := fieldIndexRecover(typ, tt.i) if tt.mustPanic { if recoveredErr == nil { t.Errorf("#%d: fieldIndex %d expected to panic", i, tt.i) } } else { if recoveredErr != nil { t.Errorf("#%d: got err=%v, expected no panic", i, recoveredErr) } } } } // Issue 9179. func TestCallGC(t *testing.T) { f := func(a, b, c, d, e string) { } g := func(in []Value) []Value { runtime.GC() return nil } typ := ValueOf(f).Type() f2 := MakeFunc(typ, g).Interface().(func(string, string, string, string, string)) f2("four", "five5", "six666", "seven77", "eight888") } // Issue 18635 (function version). func TestKeepFuncLive(t *testing.T) { // Test that we keep makeFuncImpl live as long as it is // referenced on the stack. typ := TypeOf(func(i int) {}) var f, g func(in []Value) []Value f = func(in []Value) []Value { clobber() i := int(in[0].Int()) if i > 0 { // We can't use Value.Call here because // runtime.call* will keep the makeFuncImpl // alive. However, by converting it to an // interface value and calling that, // reflect.callReflect is the only thing that // can keep the makeFuncImpl live. // // Alternate between f and g so that if we do // reuse the memory prematurely it's more // likely to get obviously corrupted. MakeFunc(typ, g).Interface().(func(i int))(i - 1) } return nil } g = func(in []Value) []Value { clobber() i := int(in[0].Int()) MakeFunc(typ, f).Interface().(func(i int))(i) return nil } MakeFunc(typ, f).Call([]Value{ValueOf(10)}) } type UnExportedFirst int func (i UnExportedFirst) ΦExported() {} func (i UnExportedFirst) unexported() {} // Issue 21177 func TestMethodByNameUnExportedFirst(t *testing.T) { defer func() { if recover() != nil { t.Errorf("should not panic") } }() typ := TypeOf(UnExportedFirst(0)) m, _ := typ.MethodByName("ΦExported") if m.Name != "ΦExported" { t.Errorf("got %s, expected ΦExported", m.Name) } } // Issue 18635 (method version). type KeepMethodLive struct{} func (k KeepMethodLive) Method1(i int) { clobber() if i > 0 { ValueOf(k).MethodByName("Method2").Interface().(func(i int))(i - 1) } } func (k KeepMethodLive) Method2(i int) { clobber() ValueOf(k).MethodByName("Method1").Interface().(func(i int))(i) } func TestKeepMethodLive(t *testing.T) { // Test that we keep methodValue live as long as it is // referenced on the stack. KeepMethodLive{}.Method1(10) } // clobber tries to clobber unreachable memory. func clobber() { runtime.GC() for i := 1; i < 32; i++ { for j := 0; j < 10; j++ { obj := make([]*byte, i) sink = obj } } runtime.GC() } func TestFuncLayout(t *testing.T) { align := func(x uintptr) uintptr { return (x + goarch.PtrSize - 1) &^ (goarch.PtrSize - 1) } var r []byte if goarch.PtrSize == 4 { r = []byte{0, 0, 0, 1} } else { r = []byte{0, 0, 1} } type S struct { a, b uintptr c, d *byte } type test struct { rcvr, typ Type size, argsize, retOffset uintptr stack, gc, inRegs, outRegs []byte // pointer bitmap: 1 is pointer, 0 is scalar intRegs, floatRegs int floatRegSize uintptr } tests := []test{ { typ: ValueOf(func(a, b string) string { return "" }).Type(), size: 6 * goarch.PtrSize, argsize: 4 * goarch.PtrSize, retOffset: 4 * goarch.PtrSize, stack: []byte{1, 0, 1, 0, 1}, gc: []byte{1, 0, 1, 0, 1}, }, { typ: ValueOf(func(a, b, c uint32, p *byte, d uint16) {}).Type(), size: align(align(3*4) + goarch.PtrSize + 2), argsize: align(3*4) + goarch.PtrSize + 2, retOffset: align(align(3*4) + goarch.PtrSize + 2), stack: r, gc: r, }, { typ: ValueOf(func(a map[int]int, b uintptr, c any) {}).Type(), size: 4 * goarch.PtrSize, argsize: 4 * goarch.PtrSize, retOffset: 4 * goarch.PtrSize, stack: []byte{1, 0, 1, 1}, gc: []byte{1, 0, 1, 1}, }, { typ: ValueOf(func(a S) {}).Type(), size: 4 * goarch.PtrSize, argsize: 4 * goarch.PtrSize, retOffset: 4 * goarch.PtrSize, stack: []byte{0, 0, 1, 1}, gc: []byte{0, 0, 1, 1}, }, { rcvr: ValueOf((*byte)(nil)).Type(), typ: ValueOf(func(a uintptr, b *int) {}).Type(), size: 3 * goarch.PtrSize, argsize: 3 * goarch.PtrSize, retOffset: 3 * goarch.PtrSize, stack: []byte{1, 0, 1}, gc: []byte{1, 0, 1}, }, { typ: ValueOf(func(a uintptr) {}).Type(), size: goarch.PtrSize, argsize: goarch.PtrSize, retOffset: goarch.PtrSize, stack: []byte{}, gc: []byte{}, }, { typ: ValueOf(func() uintptr { return 0 }).Type(), size: goarch.PtrSize, argsize: 0, retOffset: 0, stack: []byte{}, gc: []byte{}, }, { rcvr: ValueOf(uintptr(0)).Type(), typ: ValueOf(func(a uintptr) {}).Type(), size: 2 * goarch.PtrSize, argsize: 2 * goarch.PtrSize, retOffset: 2 * goarch.PtrSize, stack: []byte{1}, gc: []byte{1}, // Note: this one is tricky, as the receiver is not a pointer. But we // pass the receiver by reference to the autogenerated pointer-receiver // version of the function. }, // TODO(mknyszek): Add tests for non-zero register count. } for _, lt := range tests { name := lt.typ.String() if lt.rcvr != nil { name = lt.rcvr.String() + "." + name } t.Run(name, func(t *testing.T) { defer SetArgRegs(SetArgRegs(lt.intRegs, lt.floatRegs, lt.floatRegSize)) typ, argsize, retOffset, stack, gc, inRegs, outRegs, ptrs := FuncLayout(lt.typ, lt.rcvr) if typ.Size() != lt.size { t.Errorf("funcLayout(%v, %v).size=%d, want %d", lt.typ, lt.rcvr, typ.Size(), lt.size) } if argsize != lt.argsize { t.Errorf("funcLayout(%v, %v).argsize=%d, want %d", lt.typ, lt.rcvr, argsize, lt.argsize) } if retOffset != lt.retOffset { t.Errorf("funcLayout(%v, %v).retOffset=%d, want %d", lt.typ, lt.rcvr, retOffset, lt.retOffset) } if !bytes.Equal(stack, lt.stack) { t.Errorf("funcLayout(%v, %v).stack=%v, want %v", lt.typ, lt.rcvr, stack, lt.stack) } if !bytes.Equal(gc, lt.gc) { t.Errorf("funcLayout(%v, %v).gc=%v, want %v", lt.typ, lt.rcvr, gc, lt.gc) } if !bytes.Equal(inRegs, lt.inRegs) { t.Errorf("funcLayout(%v, %v).inRegs=%v, want %v", lt.typ, lt.rcvr, inRegs, lt.inRegs) } if !bytes.Equal(outRegs, lt.outRegs) { t.Errorf("funcLayout(%v, %v).outRegs=%v, want %v", lt.typ, lt.rcvr, outRegs, lt.outRegs) } if ptrs && len(stack) == 0 || !ptrs && len(stack) > 0 { t.Errorf("funcLayout(%v, %v) pointers flag=%v, want %v", lt.typ, lt.rcvr, ptrs, !ptrs) } }) } } // trimBitmap removes trailing 0 elements from b and returns the result. func trimBitmap(b []byte) []byte { for len(b) > 0 && b[len(b)-1] == 0 { b = b[:len(b)-1] } return b } func verifyGCBits(t *testing.T, typ Type, bits []byte) { heapBits := GCBits(New(typ).Interface()) // Trim scalars at the end, as bits might end in zero, // e.g. with rep(2, lit(1, 0)). bits = trimBitmap(bits) if !bytes.Equal(heapBits, bits) { _, _, line, _ := runtime.Caller(1) t.Errorf("line %d: heapBits incorrect for %v\nhave %v\nwant %v", line, typ, heapBits, bits) } } func verifyGCBitsSlice(t *testing.T, typ Type, cap int, bits []byte) { // Creating a slice causes the runtime to repeat a bitmap, // which exercises a different path from making the compiler // repeat a bitmap for a small array or executing a repeat in // a GC program. val := MakeSlice(typ, 0, cap) data := NewAt(ArrayOf(cap, typ), val.UnsafePointer()) heapBits := GCBits(data.Interface()) // Repeat the bitmap for the slice size, trimming scalars in // the last element. bits = trimBitmap(rep(cap, bits)) if !bytes.Equal(heapBits, bits) { _, _, line, _ := runtime.Caller(1) t.Errorf("line %d: heapBits incorrect for make(%v, 0, %v)\nhave %v\nwant %v", line, typ, cap, heapBits, bits) } } func TestGCBits(t *testing.T) { verifyGCBits(t, TypeOf((*byte)(nil)), []byte{1}) // Building blocks for types seen by the compiler (like [2]Xscalar). // The compiler will create the type structures for the derived types, // including their GC metadata. type Xscalar struct{ x uintptr } type Xptr struct{ x *byte } type Xptrscalar struct { *byte uintptr } type Xscalarptr struct { uintptr *byte } type Xbigptrscalar struct { _ [100]*byte _ [100]uintptr } var Tscalar, Tint64, Tptr, Tscalarptr, Tptrscalar, Tbigptrscalar Type { // Building blocks for types constructed by reflect. // This code is in a separate block so that code below // cannot accidentally refer to these. // The compiler must NOT see types derived from these // (for example, [2]Scalar must NOT appear in the program), // or else reflect will use it instead of having to construct one. // The goal is to test the construction. type Scalar struct{ x uintptr } type Ptr struct{ x *byte } type Ptrscalar struct { *byte uintptr } type Scalarptr struct { uintptr *byte } type Bigptrscalar struct { _ [100]*byte _ [100]uintptr } type Int64 int64 Tscalar = TypeOf(Scalar{}) Tint64 = TypeOf(Int64(0)) Tptr = TypeOf(Ptr{}) Tscalarptr = TypeOf(Scalarptr{}) Tptrscalar = TypeOf(Ptrscalar{}) Tbigptrscalar = TypeOf(Bigptrscalar{}) } empty := []byte{} verifyGCBits(t, TypeOf(Xscalar{}), empty) verifyGCBits(t, Tscalar, empty) verifyGCBits(t, TypeOf(Xptr{}), lit(1)) verifyGCBits(t, Tptr, lit(1)) verifyGCBits(t, TypeOf(Xscalarptr{}), lit(0, 1)) verifyGCBits(t, Tscalarptr, lit(0, 1)) verifyGCBits(t, TypeOf(Xptrscalar{}), lit(1)) verifyGCBits(t, Tptrscalar, lit(1)) verifyGCBits(t, TypeOf([0]Xptr{}), empty) verifyGCBits(t, ArrayOf(0, Tptr), empty) verifyGCBits(t, TypeOf([1]Xptrscalar{}), lit(1)) verifyGCBits(t, ArrayOf(1, Tptrscalar), lit(1)) verifyGCBits(t, TypeOf([2]Xscalar{}), empty) verifyGCBits(t, ArrayOf(2, Tscalar), empty) verifyGCBits(t, TypeOf([10000]Xscalar{}), empty) verifyGCBits(t, ArrayOf(10000, Tscalar), empty) verifyGCBits(t, TypeOf([2]Xptr{}), lit(1, 1)) verifyGCBits(t, ArrayOf(2, Tptr), lit(1, 1)) verifyGCBits(t, TypeOf([10000]Xptr{}), rep(10000, lit(1))) verifyGCBits(t, ArrayOf(10000, Tptr), rep(10000, lit(1))) verifyGCBits(t, TypeOf([2]Xscalarptr{}), lit(0, 1, 0, 1)) verifyGCBits(t, ArrayOf(2, Tscalarptr), lit(0, 1, 0, 1)) verifyGCBits(t, TypeOf([10000]Xscalarptr{}), rep(10000, lit(0, 1))) verifyGCBits(t, ArrayOf(10000, Tscalarptr), rep(10000, lit(0, 1))) verifyGCBits(t, TypeOf([2]Xptrscalar{}), lit(1, 0, 1)) verifyGCBits(t, ArrayOf(2, Tptrscalar), lit(1, 0, 1)) verifyGCBits(t, TypeOf([10000]Xptrscalar{}), rep(10000, lit(1, 0))) verifyGCBits(t, ArrayOf(10000, Tptrscalar), rep(10000, lit(1, 0))) verifyGCBits(t, TypeOf([1][10000]Xptrscalar{}), rep(10000, lit(1, 0))) verifyGCBits(t, ArrayOf(1, ArrayOf(10000, Tptrscalar)), rep(10000, lit(1, 0))) verifyGCBits(t, TypeOf([2][10000]Xptrscalar{}), rep(2*10000, lit(1, 0))) verifyGCBits(t, ArrayOf(2, ArrayOf(10000, Tptrscalar)), rep(2*10000, lit(1, 0))) verifyGCBits(t, TypeOf([4]Xbigptrscalar{}), join(rep(3, join(rep(100, lit(1)), rep(100, lit(0)))), rep(100, lit(1)))) verifyGCBits(t, ArrayOf(4, Tbigptrscalar), join(rep(3, join(rep(100, lit(1)), rep(100, lit(0)))), rep(100, lit(1)))) verifyGCBitsSlice(t, TypeOf([]Xptr{}), 0, empty) verifyGCBitsSlice(t, SliceOf(Tptr), 0, empty) verifyGCBitsSlice(t, TypeOf([]Xptrscalar{}), 1, lit(1)) verifyGCBitsSlice(t, SliceOf(Tptrscalar), 1, lit(1)) verifyGCBitsSlice(t, TypeOf([]Xscalar{}), 2, lit(0)) verifyGCBitsSlice(t, SliceOf(Tscalar), 2, lit(0)) verifyGCBitsSlice(t, TypeOf([]Xscalar{}), 10000, lit(0)) verifyGCBitsSlice(t, SliceOf(Tscalar), 10000, lit(0)) verifyGCBitsSlice(t, TypeOf([]Xptr{}), 2, lit(1)) verifyGCBitsSlice(t, SliceOf(Tptr), 2, lit(1)) verifyGCBitsSlice(t, TypeOf([]Xptr{}), 10000, lit(1)) verifyGCBitsSlice(t, SliceOf(Tptr), 10000, lit(1)) verifyGCBitsSlice(t, TypeOf([]Xscalarptr{}), 2, lit(0, 1)) verifyGCBitsSlice(t, SliceOf(Tscalarptr), 2, lit(0, 1)) verifyGCBitsSlice(t, TypeOf([]Xscalarptr{}), 10000, lit(0, 1)) verifyGCBitsSlice(t, SliceOf(Tscalarptr), 10000, lit(0, 1)) verifyGCBitsSlice(t, TypeOf([]Xptrscalar{}), 2, lit(1, 0)) verifyGCBitsSlice(t, SliceOf(Tptrscalar), 2, lit(1, 0)) verifyGCBitsSlice(t, TypeOf([]Xptrscalar{}), 10000, lit(1, 0)) verifyGCBitsSlice(t, SliceOf(Tptrscalar), 10000, lit(1, 0)) verifyGCBitsSlice(t, TypeOf([][10000]Xptrscalar{}), 1, rep(10000, lit(1, 0))) verifyGCBitsSlice(t, SliceOf(ArrayOf(10000, Tptrscalar)), 1, rep(10000, lit(1, 0))) verifyGCBitsSlice(t, TypeOf([][10000]Xptrscalar{}), 2, rep(10000, lit(1, 0))) verifyGCBitsSlice(t, SliceOf(ArrayOf(10000, Tptrscalar)), 2, rep(10000, lit(1, 0))) verifyGCBitsSlice(t, TypeOf([]Xbigptrscalar{}), 4, join(rep(100, lit(1)), rep(100, lit(0)))) verifyGCBitsSlice(t, SliceOf(Tbigptrscalar), 4, join(rep(100, lit(1)), rep(100, lit(0)))) verifyGCBits(t, TypeOf((chan [100]Xscalar)(nil)), lit(1)) verifyGCBits(t, ChanOf(BothDir, ArrayOf(100, Tscalar)), lit(1)) verifyGCBits(t, TypeOf((func([10000]Xscalarptr))(nil)), lit(1)) verifyGCBits(t, FuncOf([]Type{ArrayOf(10000, Tscalarptr)}, nil, false), lit(1)) verifyGCBits(t, TypeOf((map[[10000]Xscalarptr]Xscalar)(nil)), lit(1)) verifyGCBits(t, MapOf(ArrayOf(10000, Tscalarptr), Tscalar), lit(1)) verifyGCBits(t, TypeOf((*[10000]Xscalar)(nil)), lit(1)) verifyGCBits(t, PointerTo(ArrayOf(10000, Tscalar)), lit(1)) verifyGCBits(t, TypeOf(([][10000]Xscalar)(nil)), lit(1)) verifyGCBits(t, SliceOf(ArrayOf(10000, Tscalar)), lit(1)) hdr := make([]byte, 8/goarch.PtrSize) verifyMapBucket := func(t *testing.T, k, e Type, m any, want []byte) { verifyGCBits(t, MapBucketOf(k, e), want) verifyGCBits(t, CachedBucketOf(TypeOf(m)), want) } verifyMapBucket(t, Tscalar, Tptr, map[Xscalar]Xptr(nil), join(hdr, rep(8, lit(0)), rep(8, lit(1)), lit(1))) verifyMapBucket(t, Tscalarptr, Tptr, map[Xscalarptr]Xptr(nil), join(hdr, rep(8, lit(0, 1)), rep(8, lit(1)), lit(1))) verifyMapBucket(t, Tint64, Tptr, map[int64]Xptr(nil), join(hdr, rep(8, rep(8/goarch.PtrSize, lit(0))), rep(8, lit(1)), lit(1))) verifyMapBucket(t, Tscalar, Tscalar, map[Xscalar]Xscalar(nil), empty) verifyMapBucket(t, ArrayOf(2, Tscalarptr), ArrayOf(3, Tptrscalar), map[[2]Xscalarptr][3]Xptrscalar(nil), join(hdr, rep(8*2, lit(0, 1)), rep(8*3, lit(1, 0)), lit(1))) verifyMapBucket(t, ArrayOf(64/goarch.PtrSize, Tscalarptr), ArrayOf(64/goarch.PtrSize, Tptrscalar), map[[64 / goarch.PtrSize]Xscalarptr][64 / goarch.PtrSize]Xptrscalar(nil), join(hdr, rep(8*64/goarch.PtrSize, lit(0, 1)), rep(8*64/goarch.PtrSize, lit(1, 0)), lit(1))) verifyMapBucket(t, ArrayOf(64/goarch.PtrSize+1, Tscalarptr), ArrayOf(64/goarch.PtrSize, Tptrscalar), map[[64/goarch.PtrSize + 1]Xscalarptr][64 / goarch.PtrSize]Xptrscalar(nil), join(hdr, rep(8, lit(1)), rep(8*64/goarch.PtrSize, lit(1, 0)), lit(1))) verifyMapBucket(t, ArrayOf(64/goarch.PtrSize, Tscalarptr), ArrayOf(64/goarch.PtrSize+1, Tptrscalar), map[[64 / goarch.PtrSize]Xscalarptr][64/goarch.PtrSize + 1]Xptrscalar(nil), join(hdr, rep(8*64/goarch.PtrSize, lit(0, 1)), rep(8, lit(1)), lit(1))) verifyMapBucket(t, ArrayOf(64/goarch.PtrSize+1, Tscalarptr), ArrayOf(64/goarch.PtrSize+1, Tptrscalar), map[[64/goarch.PtrSize + 1]Xscalarptr][64/goarch.PtrSize + 1]Xptrscalar(nil), join(hdr, rep(8, lit(1)), rep(8, lit(1)), lit(1))) } func rep(n int, b []byte) []byte { return bytes.Repeat(b, n) } func join(b ...[]byte) []byte { return bytes.Join(b, nil) } func lit(x ...byte) []byte { return x } func TestTypeOfTypeOf(t *testing.T) { // Check that all the type constructors return concrete *rtype implementations. // It's difficult to test directly because the reflect package is only at arm's length. // The easiest thing to do is just call a function that crashes if it doesn't get an *rtype. check := func(name string, typ Type) { if underlying := TypeOf(typ).String(); underlying != "*reflect.rtype" { t.Errorf("%v returned %v, not *reflect.rtype", name, underlying) } } type T struct{ int } check("TypeOf", TypeOf(T{})) check("ArrayOf", ArrayOf(10, TypeOf(T{}))) check("ChanOf", ChanOf(BothDir, TypeOf(T{}))) check("FuncOf", FuncOf([]Type{TypeOf(T{})}, nil, false)) check("MapOf", MapOf(TypeOf(T{}), TypeOf(T{}))) check("PtrTo", PointerTo(TypeOf(T{}))) check("SliceOf", SliceOf(TypeOf(T{}))) } type XM struct{ _ bool } func (*XM) String() string { return "" } func TestPtrToMethods(t *testing.T) { var y struct{ XM } yp := New(TypeOf(y)).Interface() _, ok := yp.(fmt.Stringer) if !ok { t.Fatal("does not implement Stringer, but should") } } func TestMapAlloc(t *testing.T) { m := ValueOf(make(map[int]int, 10)) k := ValueOf(5) v := ValueOf(7) allocs := testing.AllocsPerRun(100, func() { m.SetMapIndex(k, v) }) if allocs > 0.5 { t.Errorf("allocs per map assignment: want 0 got %f", allocs) } const size = 1000 tmp := 0 val := ValueOf(&tmp).Elem() allocs = testing.AllocsPerRun(100, func() { mv := MakeMapWithSize(TypeOf(map[int]int{}), size) // Only adding half of the capacity to not trigger re-allocations due too many overloaded buckets. for i := 0; i < size/2; i++ { val.SetInt(int64(i)) mv.SetMapIndex(val, val) } }) if allocs > 10 { t.Errorf("allocs per map assignment: want at most 10 got %f", allocs) } // Empirical testing shows that with capacity hint single run will trigger 3 allocations and without 91. I set // the threshold to 10, to not make it overly brittle if something changes in the initial allocation of the // map, but to still catch a regression where we keep re-allocating in the hashmap as new entries are added. } func TestChanAlloc(t *testing.T) { // Note: for a chan int, the return Value must be allocated, so we // use a chan *int instead. c := ValueOf(make(chan *int, 1)) v := ValueOf(new(int)) allocs := testing.AllocsPerRun(100, func() { c.Send(v) _, _ = c.Recv() }) if allocs < 0.5 || allocs > 1.5 { t.Errorf("allocs per chan send/recv: want 1 got %f", allocs) } // Note: there is one allocation in reflect.recv which seems to be // a limitation of escape analysis. If that is ever fixed the // allocs < 0.5 condition will trigger and this test should be fixed. } */ type TheNameOfThisTypeIsExactly255BytesLongSoWhenTheCompilerPrependsTheReflectTestPackageNameAndExtraStarTheLinkerRuntimeAndReflectPackagesWillHaveToCorrectlyDecodeTheSecondLengthByte0123456789_0123456789_0123456789_0123456789_0123456789_012345678 int type nameTest struct { v any want string } var nameTests = []nameTest{ {(*int32)(nil), "int32"}, {(*D1)(nil), "D1"}, {(*[]D1)(nil), ""}, {(*chan D1)(nil), ""}, {(*func() D1)(nil), ""}, {(*<-chan D1)(nil), ""}, {(*chan<- D1)(nil), ""}, {(*any)(nil), ""}, {(*interface { F() })(nil), ""}, {(*TheNameOfThisTypeIsExactly255BytesLongSoWhenTheCompilerPrependsTheReflectTestPackageNameAndExtraStarTheLinkerRuntimeAndReflectPackagesWillHaveToCorrectlyDecodeTheSecondLengthByte0123456789_0123456789_0123456789_0123456789_0123456789_012345678)(nil), "TheNameOfThisTypeIsExactly255BytesLongSoWhenTheCompilerPrependsTheReflectTestPackageNameAndExtraStarTheLinkerRuntimeAndReflectPackagesWillHaveToCorrectlyDecodeTheSecondLengthByte0123456789_0123456789_0123456789_0123456789_0123456789_012345678"}, } func TestNames(t *testing.T) { for _, test := range nameTests { typ := TypeOf(test.v).Elem() if got := typ.Name(); got != test.want { t.Errorf("%v Name()=%q, want %q", typ, got, test.want) } } } /* func TestExported(t *testing.T) { type ΦExported struct{} type φUnexported struct{} type BigP *big type P int type p *P type P2 p type p3 p type exportTest struct { v any want bool } exportTests := []exportTest{ {D1{}, true}, {(*D1)(nil), true}, {big{}, false}, {(*big)(nil), false}, {(BigP)(nil), true}, {(*BigP)(nil), true}, {ΦExported{}, true}, {φUnexported{}, false}, {P(0), true}, {(p)(nil), false}, {(P2)(nil), true}, {(p3)(nil), false}, } for i, test := range exportTests { typ := TypeOf(test.v) if got := IsExported(typ); got != test.want { t.Errorf("%d: %s exported=%v, want %v", i, typ.Name(), got, test.want) } } } func TestTypeStrings(t *testing.T) { type stringTest struct { typ Type want string } stringTests := []stringTest{ {TypeOf(func(int) {}), "func(int)"}, {FuncOf([]Type{TypeOf(int(0))}, nil, false), "func(int)"}, {TypeOf(XM{}), "reflect_test.XM"}, {TypeOf(new(XM)), "*reflect_test.XM"}, {TypeOf(new(XM).String), "func() string"}, {TypeOf(new(XM)).Method(0).Type, "func(*reflect_test.XM) string"}, {ChanOf(3, TypeOf(XM{})), "chan reflect_test.XM"}, {MapOf(TypeOf(int(0)), TypeOf(XM{})), "map[int]reflect_test.XM"}, {ArrayOf(3, TypeOf(XM{})), "[3]reflect_test.XM"}, {ArrayOf(3, TypeOf(struct{}{})), "[3]struct {}"}, } for i, test := range stringTests { if got, want := test.typ.String(), test.want; got != want { t.Errorf("type %d String()=%q, want %q", i, got, want) } } } func TestOffsetLock(t *testing.T) { var wg sync.WaitGroup for i := 0; i < 4; i++ { i := i wg.Add(1) go func() { for j := 0; j < 50; j++ { ResolveReflectName(fmt.Sprintf("OffsetLockName:%d:%d", i, j)) } wg.Done() }() } wg.Wait() } */ func TestSwapper(t *testing.T) { type I int var a, b, c I type pair struct { x, y int } type pairPtr struct { x, y int p *I } type S string tests := []struct { in any i, j int want any }{ { in: []int{1, 20, 300}, i: 0, j: 2, want: []int{300, 20, 1}, }, { in: []uintptr{1, 20, 300}, i: 0, j: 2, want: []uintptr{300, 20, 1}, }, { in: []int16{1, 20, 300}, i: 0, j: 2, want: []int16{300, 20, 1}, }, { in: []int8{1, 20, 100}, i: 0, j: 2, want: []int8{100, 20, 1}, }, { in: []*I{&a, &b, &c}, i: 0, j: 2, want: []*I{&c, &b, &a}, }, { in: []string{"eric", "sergey", "larry"}, i: 0, j: 2, want: []string{"larry", "sergey", "eric"}, }, { in: []S{"eric", "sergey", "larry"}, i: 0, j: 2, want: []S{"larry", "sergey", "eric"}, }, { in: []pair{{1, 2}, {3, 4}, {5, 6}}, i: 0, j: 2, want: []pair{{5, 6}, {3, 4}, {1, 2}}, }, { in: []pairPtr{{1, 2, &a}, {3, 4, &b}, {5, 6, &c}}, i: 0, j: 2, want: []pairPtr{{5, 6, &c}, {3, 4, &b}, {1, 2, &a}}, }, } for i, tt := range tests { inStr := fmt.Sprint(tt.in) Swapper(tt.in)(tt.i, tt.j) if !DeepEqual(tt.in, tt.want) { t.Errorf("%d. swapping %v and %v of %v = %v; want %v", i, tt.i, tt.j, inStr, tt.in, tt.want) } } } /* // TestUnaddressableField tests that the reflect package will not allow // a type from another package to be used as a named type with an // unexported field. // // This ensures that unexported fields cannot be modified by other packages. func TestUnaddressableField(t *testing.T) { var b Buffer // type defined in reflect, a different package var localBuffer struct { buf []byte } lv := ValueOf(&localBuffer).Elem() rv := ValueOf(b) shouldPanic("Set", func() { lv.Set(rv) }) } */ type Tint int type Tint2 = Tint type Talias1 struct { byte uint8 int int32 rune } type Talias2 struct { Tint Tint2 } func TestAliasNames(t *testing.T) { t1 := Talias1{byte: 1, uint8: 2, int: 3, int32: 4, rune: 5} out := fmt.Sprintf("%#v", t1) want := "reflect_test.Talias1{byte:0x1, uint8:0x2, int:3, int32:4, rune:5}" if out != want { t.Errorf("Talias1 print:\nhave: %s\nwant: %s", out, want) } t2 := Talias2{Tint: 1, Tint2: 2} out = fmt.Sprintf("%#v", t2) want = "reflect_test.Talias2{Tint:1, Tint2:2}" if out != want { t.Errorf("Talias2 print:\nhave: %s\nwant: %s", out, want) } } func TestIssue22031(t *testing.T) { type s []struct{ C int } type t1 struct{ s } type t2 struct{ f s } tests := []Value{ ValueOf(t1{s{{}}}).Field(0).Index(0).Field(0), ValueOf(t2{s{{}}}).Field(0).Index(0).Field(0), } for i, test := range tests { if test.CanSet() { t.Errorf("%d: CanSet: got true, want false", i) } } } /* type NonExportedFirst int func (i NonExportedFirst) ΦExported() {} func (i NonExportedFirst) nonexported() int { panic("wrong") } func TestIssue22073(t *testing.T) { m := ValueOf(NonExportedFirst(0)).Method(0) if got := m.Type().NumOut(); got != 0 { t.Errorf("NumOut: got %v, want 0", got) } // Shouldn't panic. m.Call(nil) } */ func TestMapIterNonEmptyMap(t *testing.T) { m := map[string]int{"one": 1, "two": 2, "three": 3} iter := ValueOf(m).MapRange() if got, want := iterateToString(iter), `[one: 1, three: 3, two: 2]`; got != want { t.Errorf("iterator returned %s (after sorting), want %s", got, want) } } func TestMapIterNilMap(t *testing.T) { var m map[string]int iter := ValueOf(m).MapRange() if got, want := iterateToString(iter), `[]`; got != want { t.Errorf("non-empty result iteratoring nil map: %s", got) } } /* func TestMapIterReset(t *testing.T) { iter := new(MapIter) // Use of zero iterator should panic. func() { defer func() { recover() }() iter.Next() t.Error("Next did not panic") }() // Reset to new Map should work. m := map[string]int{"one": 1, "two": 2, "three": 3} iter.Reset(ValueOf(m)) if got, want := iterateToString(iter), `[one: 1, three: 3, two: 2]`; got != want { t.Errorf("iterator returned %s (after sorting), want %s", got, want) } // Reset to Zero value should work, but iterating over it should panic. iter.Reset(Value{}) func() { defer func() { recover() }() iter.Next() t.Error("Next did not panic") }() // Reset to a different Map with different types should work. m2 := map[int]string{1: "one", 2: "two", 3: "three"} iter.Reset(ValueOf(m2)) if got, want := iterateToString(iter), `[1: one, 2: two, 3: three]`; got != want { t.Errorf("iterator returned %s (after sorting), want %s", got, want) } // Check that Reset, Next, and SetKey/SetValue play nicely together. m3 := map[uint64]uint64{ 1 << 0: 1 << 1, 1 << 1: 1 << 2, 1 << 2: 1 << 3, } kv := New(TypeOf(uint64(0))).Elem() for i := 0; i < 5; i++ { var seenk, seenv uint64 iter.Reset(ValueOf(m3)) for iter.Next() { kv.SetIterKey(iter) seenk ^= kv.Uint() kv.SetIterValue(iter) seenv ^= kv.Uint() } if seenk != 0b111 { t.Errorf("iteration yielded keys %b, want %b", seenk, 0b111) } if seenv != 0b1110 { t.Errorf("iteration yielded values %b, want %b", seenv, 0b1110) } } // Reset should not allocate. n := int(testing.AllocsPerRun(10, func() { iter.Reset(ValueOf(m2)) iter.Reset(Value{}) })) if n > 0 { t.Errorf("MapIter.Reset allocated %d times", n) } } func TestMapIterSafety(t *testing.T) { // Using a zero MapIter causes a panic, but not a crash. func() { defer func() { recover() }() new(MapIter).Key() t.Fatal("Key did not panic") }() func() { defer func() { recover() }() new(MapIter).Value() t.Fatal("Value did not panic") }() func() { defer func() { recover() }() new(MapIter).Next() t.Fatal("Next did not panic") }() // Calling Key/Value on a MapIter before Next // causes a panic, but not a crash. var m map[string]int iter := ValueOf(m).MapRange() func() { defer func() { recover() }() iter.Key() t.Fatal("Key did not panic") }() func() { defer func() { recover() }() iter.Value() t.Fatal("Value did not panic") }() // Calling Next, Key, or Value on an exhausted iterator // causes a panic, but not a crash. iter.Next() // -> false func() { defer func() { recover() }() iter.Key() t.Fatal("Key did not panic") }() func() { defer func() { recover() }() iter.Value() t.Fatal("Value did not panic") }() func() { defer func() { recover() }() iter.Next() t.Fatal("Next did not panic") }() } */ func TestMapIterNext(t *testing.T) { // The first call to Next should reflect any // insertions to the map since the iterator was created. m := map[string]int{} iter := ValueOf(m).MapRange() m["one"] = 1 if got, want := iterateToString(iter), `[one: 1]`; got != want { t.Errorf("iterator returned deleted elements: got %s, want %s", got, want) } } func TestMapIterDelete0(t *testing.T) { // Delete all elements before first iteration. m := map[string]int{"one": 1, "two": 2, "three": 3} iter := ValueOf(m).MapRange() delete(m, "one") delete(m, "two") delete(m, "three") if got, want := iterateToString(iter), `[]`; got != want { t.Errorf("iterator returned deleted elements: got %s, want %s", got, want) } } func TestMapIterDelete1(t *testing.T) { // Delete all elements after first iteration. m := map[string]int{"one": 1, "two": 2, "three": 3} iter := ValueOf(m).MapRange() var got []string for iter.Next() { got = append(got, fmt.Sprint(iter.Key(), iter.Value())) delete(m, "one") delete(m, "two") delete(m, "three") } if len(got) != 1 { t.Errorf("iterator returned wrong number of elements: got %d, want 1", len(got)) } } // iterateToString returns the set of elements // returned by an iterator in readable form. func iterateToString(it *MapIter) string { var got []string for it.Next() { line := fmt.Sprintf("%v: %v", it.Key(), it.Value()) got = append(got, line) } sort.Strings(got) return "[" + strings.Join(got, ", ") + "]" } /* func TestConvertibleTo(t *testing.T) { t1 := ValueOf(example1.MyStruct{}).Type() t2 := ValueOf(example2.MyStruct{}).Type() // Shouldn't raise stack overflow if t1.ConvertibleTo(t2) { t.Fatalf("(%s).ConvertibleTo(%s) = true, want false", t1, t2) } t3 := ValueOf([]example1.MyStruct{}).Type() t4 := ValueOf([]example2.MyStruct{}).Type() if t3.ConvertibleTo(t4) { t.Fatalf("(%s).ConvertibleTo(%s) = true, want false", t3, t4) } } */ func TestSetIter(t *testing.T) { data := map[string]int{ "foo": 1, "bar": 2, "baz": 3, } m := ValueOf(data) i := m.MapRange() k := New(TypeOf("")).Elem() v := New(TypeOf(0)).Elem() shouldPanic("Value.SetIterKey called before Next", func() { k.SetIterKey(i) }) shouldPanic("Value.SetIterValue called before Next", func() { v.SetIterValue(i) }) data2 := map[string]int{} for i.Next() { k.SetIterKey(i) v.SetIterValue(i) data2[k.Interface().(string)] = v.Interface().(int) } if !DeepEqual(data, data2) { t.Errorf("maps not equal, got %v want %v", data2, data) } shouldPanic("Value.SetIterKey called on exhausted iterator", func() { k.SetIterKey(i) }) shouldPanic("Value.SetIterValue called on exhausted iterator", func() { v.SetIterValue(i) }) i.Reset(m) i.Next() shouldPanic("Value.SetIterKey using unaddressable value", func() { ValueOf("").SetIterKey(i) }) shouldPanic("Value.SetIterValue using unaddressable value", func() { ValueOf(0).SetIterValue(i) }) shouldPanic("value of type string is not assignable to type int", func() { New(TypeOf(0)).Elem().SetIterKey(i) }) shouldPanic("value of type int is not assignable to type string", func() { New(TypeOf("")).Elem().SetIterValue(i) }) // Make sure assignment conversion works. var x any y := ValueOf(&x).Elem() y.SetIterKey(i) if _, ok := data[x.(string)]; !ok { t.Errorf("got key %s which is not in map", x) } y.SetIterValue(i) if x.(int) < 1 || x.(int) > 3 { t.Errorf("got value %d which is not in map", x) } // Try some key/value types which are direct interfaces. a := 88 b := 99 pp := map[*int]*int{ &a: &b, } i = ValueOf(pp).MapRange() i.Next() y.SetIterKey(i) if got := *y.Interface().(*int); got != a { t.Errorf("pointer incorrect: got %d want %d", got, a) } y.SetIterValue(i) if got := *y.Interface().(*int); got != b { t.Errorf("pointer incorrect: got %d want %d", got, b) } // Make sure we panic assigning from an unexported field. m = ValueOf(struct{ m map[string]int }{data}).Field(0) for iter := m.MapRange(); iter.Next(); { shouldPanic("using value obtained using unexported field", func() { k.SetIterKey(iter) }) shouldPanic("using value obtained using unexported field", func() { v.SetIterValue(iter) }) } } /* func TestMethodCallValueCodePtr(t *testing.T) { m := ValueOf(Point{}).Method(1) want := MethodValueCallCodePtr() if got := uintptr(m.UnsafePointer()); got != want { t.Errorf("methodValueCall code pointer mismatched, want: %v, got: %v", want, got) } if got := m.Pointer(); got != want { t.Errorf("methodValueCall code pointer mismatched, want: %v, got: %v", want, got) } } type A struct{} type B[T any] struct{} func TestIssue50208(t *testing.T) { want1 := "B[reflect_test.A]" if got := TypeOf(new(B[A])).Elem().Name(); got != want1 { t.Errorf("name of type parameter mismatched, want:%s, got:%s", want1, got) } want2 := "B[reflect_test.B[reflect_test.A]]" if got := TypeOf(new(B[B[A]])).Elem().Name(); got != want2 { t.Errorf("name of type parameter mismatched, want:%s, got:%s", want2, got) } } */ func TestNegativeKindString(t *testing.T) { x := -1 s := Kind(x).String() want := "kind-1" if s != want { t.Fatalf("Kind(-1).String() = %q, want %q", s, want) } } type ( namedBool bool namedBytes []byte ) /* func TestValue_Cap(t *testing.T) { a := &[3]int{1, 2, 3} v := ValueOf(a) if v.Cap() != cap(a) { t.Errorf("Cap = %d want %d", v.Cap(), cap(a)) } a = nil v = ValueOf(a) if v.Cap() != cap(a) { t.Errorf("Cap = %d want %d", v.Cap(), cap(a)) } getError := func(f func()) (errorStr string) { defer func() { e := recover() if str, ok := e.(string); ok { errorStr = str } }() f() return } e := getError(func() { var ptr *int ValueOf(ptr).Cap() }) wantStr := "reflect: call of reflect.Value.Cap on ptr to non-array Value" if e != wantStr { t.Errorf("error is %q, want %q", e, wantStr) } } func TestValue_Len(t *testing.T) { a := &[3]int{1, 2, 3} v := ValueOf(a) if v.Len() != len(a) { t.Errorf("Len = %d want %d", v.Len(), len(a)) } a = nil v = ValueOf(a) if v.Len() != len(a) { t.Errorf("Len = %d want %d", v.Len(), len(a)) } getError := func(f func()) (errorStr string) { defer func() { e := recover() if str, ok := e.(string); ok { errorStr = str } }() f() return } e := getError(func() { var ptr *int ValueOf(ptr).Len() }) wantStr := "reflect: call of reflect.Value.Len on ptr to non-array Value" if e != wantStr { t.Errorf("error is %q, want %q", e, wantStr) } } */ func TestValue_Comparable(t *testing.T) { var a int var s []int var i interface{} = a var iSlice interface{} = s var iArrayFalse interface{} = [2]interface{}{1, map[int]int{}} var iArrayTrue interface{} = [2]interface{}{1, struct{ I interface{} }{1}} var testcases = []struct { value Value comparable bool deref bool }{ { ValueOf(32), true, false, }, { ValueOf(int8(1)), true, false, }, { ValueOf(int16(1)), true, false, }, { ValueOf(int32(1)), true, false, }, { ValueOf(int64(1)), true, false, }, { ValueOf(uint8(1)), true, false, }, { ValueOf(uint16(1)), true, false, }, { ValueOf(uint32(1)), true, false, }, { ValueOf(uint64(1)), true, false, }, { ValueOf(float32(1)), true, false, }, { ValueOf(float64(1)), true, false, }, { ValueOf(complex(float32(1), float32(1))), true, false, }, { ValueOf(complex(float64(1), float64(1))), true, false, }, { ValueOf("abc"), true, false, }, { ValueOf(true), true, false, }, { ValueOf(map[int]int{}), false, false, }, { ValueOf([]int{}), false, false, }, { Value{}, false, false, }, { ValueOf(&a), true, false, }, { ValueOf(&s), true, false, }, { ValueOf(&i), true, true, }, { ValueOf(&iSlice), false, true, }, { ValueOf([2]int{}), true, false, }, { ValueOf([2]map[int]int{}), false, false, }, { ValueOf([0]func(){}), false, false, }, { ValueOf([2]struct{ I interface{} }{{1}, {1}}), true, false, }, { ValueOf([2]struct{ I interface{} }{{[]int{}}, {1}}), false, false, }, { ValueOf([2]interface{}{1, struct{ I int }{1}}), true, false, }, { ValueOf([2]interface{}{[1]interface{}{map[int]int{}}, struct{ I int }{1}}), false, false, }, { ValueOf(&iArrayFalse), false, true, }, { ValueOf(&iArrayTrue), true, true, }, } for _, cas := range testcases { v := cas.value if cas.deref { v = v.Elem() } got := v.Comparable() if got != cas.comparable { t.Errorf("%T.Comparable = %t, want %t", v, got, cas.comparable) } } } /* type ValueEqualTest struct { v, u any eq bool vDeref, uDeref bool } var equalI interface{} = 1 var equalSlice interface{} = []int{1} var nilInterface interface{} var mapInterface interface{} = map[int]int{} var valueEqualTests = []ValueEqualTest{ { Value{}, Value{}, true, false, false, }, { true, true, true, false, false, }, { 1, 1, true, false, false, }, { int8(1), int8(1), true, false, false, }, { int16(1), int16(1), true, false, false, }, { int32(1), int32(1), true, false, false, }, { int64(1), int64(1), true, false, false, }, { uint(1), uint(1), true, false, false, }, { uint8(1), uint8(1), true, false, false, }, { uint16(1), uint16(1), true, false, false, }, { uint32(1), uint32(1), true, false, false, }, { uint64(1), uint64(1), true, false, false, }, { float32(1), float32(1), true, false, false, }, { float64(1), float64(1), true, false, false, }, { complex(1, 1), complex(1, 1), true, false, false, }, { complex128(1 + 1i), complex128(1 + 1i), true, false, false, }, { func() {}, nil, false, false, false, }, { &equalI, 1, true, true, false, }, { (chan int)(nil), nil, false, false, false, }, { (chan int)(nil), (chan int)(nil), true, false, false, }, { &equalI, &equalI, true, false, false, }, { struct{ i int }{1}, struct{ i int }{1}, true, false, false, }, { struct{ i int }{1}, struct{ i int }{2}, false, false, false, }, { &nilInterface, &nilInterface, true, true, true, }, { 1, ValueOf(struct{ i int }{1}).Field(0), true, false, false, }, } func TestValue_Equal(t *testing.T) { for _, test := range valueEqualTests { var v, u Value if vv, ok := test.v.(Value); ok { v = vv } else { v = ValueOf(test.v) } if uu, ok := test.u.(Value); ok { u = uu } else { u = ValueOf(test.u) } if test.vDeref { v = v.Elem() } if test.uDeref { u = u.Elem() } if r := v.Equal(u); r != test.eq { t.Errorf("%s == %s got %t, want %t", v.Type(), u.Type(), r, test.eq) } } } func TestValue_EqualNonComparable(t *testing.T) { var invalid = Value{} // ValueOf(nil) var values = []Value{ // Value of slice is non-comparable. ValueOf([]int(nil)), ValueOf(([]int{})), // Value of map is non-comparable. ValueOf(map[int]int(nil)), ValueOf((map[int]int{})), // Value of func is non-comparable. ValueOf(((func())(nil))), ValueOf(func() {}), // Value of struct is non-comparable because of non-comparable elements. ValueOf((NonComparableStruct{})), // Value of array is non-comparable because of non-comparable elements. ValueOf([0]map[int]int{}), ValueOf([0]func(){}), ValueOf(([1]struct{ I interface{} }{{[]int{}}})), ValueOf(([1]interface{}{[1]interface{}{map[int]int{}}})), } for _, value := range values { // Panic when reflect.Value.Equal using two valid non-comparable values. shouldPanic("are not comparable", func() { value.Equal(value) }) // If one is non-comparable and the other is invalid, the expected result is always false. if r := value.Equal(invalid); r != false { t.Errorf("%s == invalid got %t, want false", value.Type(), r) } } } func TestInitFuncTypes(t *testing.T) { n := 100 var wg sync.WaitGroup wg.Add(n) for i := 0; i < n; i++ { go func() { defer wg.Done() ipT := TypeOf(net.IP{}) for i := 0; i < ipT.NumMethod(); i++ { _ = ipT.Method(i) } }() } wg.Wait() } */ ================================================ FILE: src/reflect/benchmark_test.go ================================================ // Copyright 2022 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package reflect_test type S struct { i1 int64 i2 int64 } ================================================ FILE: src/reflect/convert_test.go ================================================ package reflect_test import ( . "reflect" "testing" ) func TestTinyConvert(t *testing.T) { V := ValueOf var tests = []struct { have, want Value }{ {V(int8(1)), V(int8(1))}, {V(int8(2)), V(uint8(2))}, {V(uint8(3)), V(int8(3))}, {V(int8(4)), V(int16(4))}, {V(int16(5)), V(int8(5))}, {V(int8(6)), V(uint16(6))}, {V(uint16(7)), V(int8(7))}, {V(int8(8)), V(int32(8))}, {V(int32(9)), V(int8(9))}, {V(int8(10)), V(uint32(10))}, {V(uint32(11)), V(int8(11))}, {V(int8(12)), V(int64(12))}, {V(int64(13)), V(int8(13))}, {V(int8(14)), V(uint64(14))}, {V(uint64(15)), V(int8(15))}, {V(int8(16)), V(int(16))}, {V(int(17)), V(int8(17))}, {V(int8(18)), V(uint(18))}, {V(uint(19)), V(int8(19))}, {V(int8(20)), V(uintptr(20))}, {V(uintptr(21)), V(int8(21))}, {V(int8(22)), V(float32(22))}, {V(float32(23)), V(int8(23))}, {V(int8(24)), V(float64(24))}, {V(float64(25)), V(int8(25))}, {V(uint8(26)), V(uint8(26))}, {V(uint8(27)), V(int16(27))}, {V(int16(28)), V(uint8(28))}, {V(uint8(29)), V(uint16(29))}, {V(uint16(30)), V(uint8(30))}, {V(uint8(31)), V(int32(31))}, {V(int32(32)), V(uint8(32))}, {V(uint8(33)), V(uint32(33))}, {V(uint32(34)), V(uint8(34))}, {V(uint8(35)), V(int64(35))}, {V(int64(36)), V(uint8(36))}, {V(uint8(37)), V(uint64(37))}, {V(uint64(38)), V(uint8(38))}, {V(uint8(39)), V(int(39))}, {V(int(40)), V(uint8(40))}, {V(uint8(41)), V(uint(41))}, {V(uint(42)), V(uint8(42))}, {V(uint8(43)), V(uintptr(43))}, {V(uintptr(44)), V(uint8(44))}, {V(uint8(45)), V(float32(45))}, {V(float32(46)), V(uint8(46))}, {V(uint8(47)), V(float64(47))}, {V(float64(48)), V(uint8(48))}, {V(int16(49)), V(int16(49))}, {V(int16(50)), V(uint16(50))}, {V(uint16(51)), V(int16(51))}, {V(int16(52)), V(int32(52))}, {V(int32(53)), V(int16(53))}, {V(int16(54)), V(uint32(54))}, {V(uint32(55)), V(int16(55))}, {V(int16(56)), V(int64(56))}, {V(int64(57)), V(int16(57))}, {V(int16(58)), V(uint64(58))}, {V(uint64(59)), V(int16(59))}, {V(int16(60)), V(int(60))}, {V(int(61)), V(int16(61))}, {V(int16(62)), V(uint(62))}, {V(uint(63)), V(int16(63))}, {V(int16(64)), V(uintptr(64))}, {V(uintptr(65)), V(int16(65))}, {V(int16(66)), V(float32(66))}, {V(float32(67)), V(int16(67))}, {V(int16(68)), V(float64(68))}, {V(float64(69)), V(int16(69))}, {V(uint16(70)), V(uint16(70))}, {V(uint16(71)), V(int32(71))}, {V(int32(72)), V(uint16(72))}, {V(uint16(73)), V(uint32(73))}, {V(uint32(74)), V(uint16(74))}, {V(uint16(75)), V(int64(75))}, {V(int64(76)), V(uint16(76))}, {V(uint16(77)), V(uint64(77))}, {V(uint64(78)), V(uint16(78))}, {V(uint16(79)), V(int(79))}, {V(int(80)), V(uint16(80))}, {V(uint16(81)), V(uint(81))}, {V(uint(82)), V(uint16(82))}, {V(uint16(83)), V(uintptr(83))}, {V(uintptr(84)), V(uint16(84))}, {V(uint16(85)), V(float32(85))}, {V(float32(86)), V(uint16(86))}, {V(uint16(87)), V(float64(87))}, {V(float64(88)), V(uint16(88))}, {V(int32(89)), V(int32(89))}, {V(int32(90)), V(uint32(90))}, {V(uint32(91)), V(int32(91))}, {V(int32(92)), V(int64(92))}, {V(int64(93)), V(int32(93))}, {V(int32(94)), V(uint64(94))}, {V(uint64(95)), V(int32(95))}, {V(int32(96)), V(int(96))}, {V(int(97)), V(int32(97))}, {V(int32(98)), V(uint(98))}, {V(uint(99)), V(int32(99))}, {V(int32(100)), V(uintptr(100))}, {V(uintptr(101)), V(int32(101))}, {V(int32(102)), V(float32(102))}, {V(float32(103)), V(int32(103))}, {V(int32(104)), V(float64(104))}, {V(float64(105)), V(int32(105))}, {V(uint32(106)), V(uint32(106))}, {V(uint32(107)), V(int64(107))}, {V(int64(108)), V(uint32(108))}, {V(uint32(109)), V(uint64(109))}, {V(uint64(110)), V(uint32(110))}, {V(uint32(111)), V(int(111))}, {V(int(112)), V(uint32(112))}, {V(uint32(113)), V(uint(113))}, {V(uint(114)), V(uint32(114))}, {V(uint32(115)), V(uintptr(115))}, {V(uintptr(116)), V(uint32(116))}, {V(uint32(117)), V(float32(117))}, {V(float32(118)), V(uint32(118))}, {V(uint32(119)), V(float64(119))}, {V(float64(120)), V(uint32(120))}, {V(int64(121)), V(int64(121))}, {V(int64(122)), V(uint64(122))}, {V(uint64(123)), V(int64(123))}, {V(int64(124)), V(int(124))}, {V(int(125)), V(int64(125))}, {V(int64(126)), V(uint(126))}, {V(uint(127)), V(int64(127))}, {V(int64(128)), V(uintptr(128))}, {V(uintptr(129)), V(int64(129))}, {V(int64(130)), V(float32(130))}, {V(float32(131)), V(int64(131))}, {V(int64(132)), V(float64(132))}, {V(float64(133)), V(int64(133))}, {V(uint64(134)), V(uint64(134))}, {V(uint64(135)), V(int(135))}, {V(int(136)), V(uint64(136))}, {V(uint64(137)), V(uint(137))}, {V(uint(138)), V(uint64(138))}, {V(uint64(139)), V(uintptr(139))}, {V(uintptr(140)), V(uint64(140))}, {V(uint64(141)), V(float32(141))}, {V(float32(142)), V(uint64(142))}, {V(uint64(143)), V(float64(143))}, {V(float64(144)), V(uint64(144))}, {V(int(145)), V(int(145))}, {V(int(146)), V(uint(146))}, {V(uint(147)), V(int(147))}, {V(int(148)), V(uintptr(148))}, {V(uintptr(149)), V(int(149))}, {V(int(150)), V(float32(150))}, {V(float32(151)), V(int(151))}, {V(int(152)), V(float64(152))}, {V(float64(153)), V(int(153))}, {V(uint(154)), V(uint(154))}, {V(uint(155)), V(uintptr(155))}, {V(uintptr(156)), V(uint(156))}, {V(uint(157)), V(float32(157))}, {V(float32(158)), V(uint(158))}, {V(uint(159)), V(float64(159))}, {V(float64(160)), V(uint(160))}, {V(uintptr(161)), V(uintptr(161))}, {V(uintptr(162)), V(float32(162))}, {V(float32(163)), V(uintptr(163))}, {V(uintptr(164)), V(float64(164))}, {V(float64(165)), V(uintptr(165))}, {V(float32(166)), V(float32(166))}, {V(float32(167)), V(float64(167))}, {V(float64(168)), V(float32(168))}, {V(float64(169)), V(float64(169))}, } for _, tt := range tests { got := tt.have.Convert(tt.want.Type()) if !DeepEqual(got.Interface(), tt.want.Interface()) { t.Errorf("Failed to Convert() %T(%v) -> %T(%v), got %T(%v)", tt.have.Interface(), tt.have, tt.want.Interface(), tt.want, got.Interface(), got) } } } ================================================ FILE: src/reflect/deepequal.go ================================================ package reflect import "internal/reflectlite" func DeepEqual(x, y interface{}) bool { return reflectlite.DeepEqual(x, y) } ================================================ FILE: src/reflect/export_test.go ================================================ package reflect type OtherPkgFields struct { OtherExported int otherUnexported int } ================================================ FILE: src/reflect/internal/example1/example.go ================================================ // Copyright 2021 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package example1 type MyStruct struct { MyStructs []MyStruct MyStruct *MyStruct } ================================================ FILE: src/reflect/internal/example2/example.go ================================================ // Copyright 2021 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package example2 type MyStruct struct { MyStructs []MyStruct MyStruct *MyStruct } ================================================ FILE: src/reflect/intw.go ================================================ //go:build !avr package reflect // intw is an integer type, used in places where an int is typically required, // except architectures where the size of an int != word size. // See https://github.com/tinygo-org/tinygo/issues/1284. type intw = int ================================================ FILE: src/reflect/intw_avr.go ================================================ //go:build avr package reflect // intw is an integer type, used in places where an int is typically required, // except architectures where the size of an int != word size. // See https://github.com/tinygo-org/tinygo/issues/1284. type intw = uintptr ================================================ FILE: src/reflect/intw_test.go ================================================ //go:build !avr package reflect_test import ( "reflect" "testing" "unsafe" ) // Verify that SliceHeader is the same size as a slice. var _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(reflect.SliceHeader{})]byte{} // TestSliceHeaderIntegerSize verifies that SliceHeader.Len and Cap are type int on non-AVR platforms. // See https://github.com/tinygo-org/tinygo/issues/1284. func TestSliceHeaderIntegerSize(t *testing.T) { var h reflect.SliceHeader h.Len = int(0) h.Cap = int(0) } // Verify that StringHeader is the same size as a string. var _ [unsafe.Sizeof("hello")]byte = [unsafe.Sizeof(reflect.StringHeader{})]byte{} // TestStringHeaderIntegerSize verifies that StringHeader.Len and Cap are type int on non-AVR platforms. // See https://github.com/tinygo-org/tinygo/issues/1284. func TestStringHeaderIntegerSize(t *testing.T) { var h reflect.StringHeader h.Len = int(0) } ================================================ FILE: src/reflect/iter.go ================================================ //go:build go1.23 // Copyright 2024 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package reflect import ( "iter" ) func rangeNum[T int8 | int16 | int32 | int64 | int | uint8 | uint16 | uint32 | uint64 | uint | uintptr, N int64 | uint64](num N, t Type) iter.Seq[Value] { return func(yield func(v Value) bool) { convert := t.PkgPath() != "" // cannot use range T(v) because no core type. for i := T(0); i < T(num); i++ { tmp := ValueOf(i) // if the iteration value type is define by // type T built-in type. if convert { tmp = tmp.Convert(t) } if !yield(tmp) { return } } } } // Seq returns an iter.Seq[Value] that loops over the elements of v. // If v's kind is Func, it must be a function that has no results and // that takes a single argument of type func(T) bool for some type T. // If v's kind is Pointer, the pointer element type must have kind Array. // Otherwise v's kind must be Int, Int8, Int16, Int32, Int64, // Uint, Uint8, Uint16, Uint32, Uint64, Uintptr, // Array, Chan, Map, Slice, or String. func (v Value) Seq() iter.Seq[Value] { // TODO: canRangeFunc // if canRangeFunc(v.typ()) { // return func(yield func(Value) bool) { // rf := MakeFunc(v.Type().In(0), func(in []Value) []Value { // return []Value{ValueOf(yield(in[0]))} // }) // v.Call([]Value{rf}) // } // } switch v.Kind() { case Int: return rangeNum[int](v.Int(), v.Type()) case Int8: return rangeNum[int8](v.Int(), v.Type()) case Int16: return rangeNum[int16](v.Int(), v.Type()) case Int32: return rangeNum[int32](v.Int(), v.Type()) case Int64: return rangeNum[int64](v.Int(), v.Type()) case Uint: return rangeNum[uint](v.Uint(), v.Type()) case Uint8: return rangeNum[uint8](v.Uint(), v.Type()) case Uint16: return rangeNum[uint16](v.Uint(), v.Type()) case Uint32: return rangeNum[uint32](v.Uint(), v.Type()) case Uint64: return rangeNum[uint64](v.Uint(), v.Type()) case Uintptr: return rangeNum[uintptr](v.Uint(), v.Type()) case Pointer: if v.Elem().Kind() != Array { break } return func(yield func(Value) bool) { v = v.Elem() for i := 0; i < v.Len(); i++ { if !yield(ValueOf(i)) { return } } } case Array, Slice: return func(yield func(Value) bool) { for i := 0; i < v.Len(); i++ { if !yield(ValueOf(i)) { return } } } case String: return func(yield func(Value) bool) { for i := range v.String() { if !yield(ValueOf(i)) { return } } } case Map: return func(yield func(Value) bool) { i := v.MapRange() for i.Next() { if !yield(i.Key()) { return } } } case Chan: return func(yield func(Value) bool) { for value, ok := v.Recv(); ok; value, ok = v.Recv() { if !yield(value) { return } } } } panic("reflect: " + v.Type().String() + " cannot produce iter.Seq[Value]") } // Seq2 returns an iter.Seq2[Value, Value] that loops over the elements of v. // If v's kind is Func, it must be a function that has no results and // that takes a single argument of type func(K, V) bool for some type K, V. // If v's kind is Pointer, the pointer element type must have kind Array. // Otherwise v's kind must be Array, Map, Slice, or String. func (v Value) Seq2() iter.Seq2[Value, Value] { // TODO: canRangeFunc2 // if canRangeFunc2(v.typ()) { // return func(yield func(Value, Value) bool) { // rf := MakeFunc(v.Type().In(0), func(in []Value) []Value { // return []Value{ValueOf(yield(in[0], in[1]))} // }) // v.Call([]Value{rf}) // } // } switch v.Kind() { case Pointer: if v.Elem().Kind() != Array { break } return func(yield func(Value, Value) bool) { v = v.Elem() for i := 0; i < v.Len(); i++ { if !yield(ValueOf(i), v.Index(i)) { return } } } case Array, Slice: return func(yield func(Value, Value) bool) { for i := 0; i < v.Len(); i++ { if !yield(ValueOf(i), v.Index(i)) { return } } } case String: return func(yield func(Value, Value) bool) { for i, v := range v.String() { if !yield(ValueOf(i), ValueOf(v)) { return } } } case Map: return func(yield func(Value, Value) bool) { i := v.MapRange() for i.Next() { if !yield(i.Key(), i.Value()) { return } } } } panic("reflect: " + v.Type().String() + " cannot produce iter.Seq2[Value, Value]") } ================================================ FILE: src/reflect/iter_test.go ================================================ //go:build go1.23 // Copyright 2024 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package reflect_test import ( "iter" "maps" "reflect" . "reflect" "testing" ) type N int8 func TestValueSeq(t *testing.T) { m := map[string]int{ "1": 1, "2": 2, "3": 3, "4": 4, } c := make(chan int, 3) for i := range 3 { c <- i } close(c) tests := []struct { name string val Value check func(*testing.T, iter.Seq[Value]) }{ {"int", ValueOf(4), func(t *testing.T, s iter.Seq[Value]) { i := int64(0) for v := range s { if v.Int() != i { t.Fatalf("got %d, want %d", v.Int(), i) } i++ } if i != 4 { t.Fatalf("should loop four times") } }}, {"int8", ValueOf(int8(4)), func(t *testing.T, s iter.Seq[Value]) { i := int8(0) for v := range s { if v.Interface().(int8) != i { t.Fatalf("got %d, want %d", v.Int(), i) } i++ } if i != 4 { t.Fatalf("should loop four times") } }}, {"uint", ValueOf(uint64(4)), func(t *testing.T, s iter.Seq[Value]) { i := uint64(0) for v := range s { if v.Uint() != i { t.Fatalf("got %d, want %d", v.Uint(), i) } i++ } if i != 4 { t.Fatalf("should loop four times") } }}, {"uint8", ValueOf(uint8(4)), func(t *testing.T, s iter.Seq[Value]) { i := uint8(0) for v := range s { if v.Interface().(uint8) != i { t.Fatalf("got %d, want %d", v.Int(), i) } i++ } if i != 4 { t.Fatalf("should loop four times") } }}, {"*[4]int", ValueOf(&[4]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq[Value]) { i := int64(0) for v := range s { if v.Int() != i { t.Fatalf("got %d, want %d", v.Int(), i) } i++ } if i != 4 { t.Fatalf("should loop four times") } }}, {"[4]int", ValueOf([4]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq[Value]) { i := int64(0) for v := range s { if v.Int() != i { t.Fatalf("got %d, want %d", v.Int(), i) } i++ } if i != 4 { t.Fatalf("should loop four times") } }}, {"[]int", ValueOf([]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq[Value]) { i := int64(0) for v := range s { if v.Int() != i { t.Fatalf("got %d, want %d", v.Int(), i) } i++ } if i != 4 { t.Fatalf("should loop four times") } }}, {"string", ValueOf("12语言"), func(t *testing.T, s iter.Seq[Value]) { i := int64(0) indexes := []int64{0, 1, 2, 5} for v := range s { if v.Int() != indexes[i] { t.Fatalf("got %d, want %d", v.Int(), indexes[i]) } i++ } if i != 4 { t.Fatalf("should loop four times") } }}, {"map[string]int", ValueOf(m), func(t *testing.T, s iter.Seq[Value]) { copy := maps.Clone(m) for v := range s { if _, ok := copy[v.String()]; !ok { t.Fatalf("unexpected %v", v.Interface()) } delete(copy, v.String()) } if len(copy) != 0 { t.Fatalf("should loop four times") } }}, // {"chan int", ValueOf(c), func(t *testing.T, s iter.Seq[Value]) { // i := 0 // m := map[int64]bool{ // 0: false, // 1: false, // 2: false, // } // for v := range s { // if b, ok := m[v.Int()]; !ok || b { // t.Fatalf("unexpected %v", v.Interface()) // } // m[v.Int()] = true // i++ // } // if i != 3 { // t.Fatalf("should loop three times") // } // }}, // {"func", ValueOf(func(yield func(int) bool) { // for i := range 4 { // if !yield(i) { // return // } // } // }), func(t *testing.T, s iter.Seq[Value]) { // i := int64(0) // for v := range s { // if v.Int() != i { // t.Fatalf("got %d, want %d", v.Int(), i) // } // i++ // } // if i != 4 { // t.Fatalf("should loop four times") // } // }}, // {"method", ValueOf(methodIter{}).MethodByName("Seq"), func(t *testing.T, s iter.Seq[Value]) { // i := int64(0) // for v := range s { // if v.Int() != i { // t.Fatalf("got %d, want %d", v.Int(), i) // } // i++ // } // if i != 4 { // t.Fatalf("should loop four times") // } // }}, {"type N int8", ValueOf(N(4)), func(t *testing.T, s iter.Seq[Value]) { i := N(0) for v := range s { if v.Int() != int64(i) { t.Fatalf("got %d, want %d", v.Int(), i) } i++ if v.Type() != reflect.TypeOf(i) { j := ValueOf(i) t.Logf("ValueOf(j): %s", j.Type()) t.Fatalf("got %s, want %s", v.Type(), reflect.TypeOf(i)) } } if i != 4 { t.Fatalf("should loop four times") } }}, } for _, tc := range tests { seq := tc.val.Seq() tc.check(t, seq) } } func TestValueSeq2(t *testing.T) { m := map[string]int{ "1": 1, "2": 2, "3": 3, "4": 4, } tests := []struct { name string val Value check func(*testing.T, iter.Seq2[Value, Value]) }{ {"*[4]int", ValueOf(&[4]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq2[Value, Value]) { i := int64(0) for v1, v2 := range s { if v1.Int() != i { t.Fatalf("got %d, want %d", v1.Int(), i) } i++ if v2.Int() != i { t.Fatalf("got %d, want %d", v2.Int(), i) } } if i != 4 { t.Fatalf("should loop four times") } }}, {"[4]int", ValueOf([4]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq2[Value, Value]) { i := int64(0) for v1, v2 := range s { if v1.Int() != i { t.Fatalf("got %d, want %d", v1.Int(), i) } i++ if v2.Int() != i { t.Fatalf("got %d, want %d", v2.Int(), i) } } if i != 4 { t.Fatalf("should loop four times") } }}, {"[]int", ValueOf([]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq2[Value, Value]) { i := int64(0) for v1, v2 := range s { if v1.Int() != i { t.Fatalf("got %d, want %d", v1.Int(), i) } i++ if v2.Int() != i { t.Fatalf("got %d, want %d", v2.Int(), i) } } if i != 4 { t.Fatalf("should loop four times") } }}, {"string", ValueOf("12语言"), func(t *testing.T, s iter.Seq2[Value, Value]) { next, stop := iter.Pull2(s) defer stop() i := int64(0) for j, s := range "12语言" { v1, v2, ok := next() if !ok { t.Fatalf("should loop four times") } if v1.Int() != int64(j) { t.Fatalf("got %d, want %d", v1.Int(), j) } if v2.Interface() != s { t.Fatalf("got %v, want %v", v2.Interface(), s) } i++ } if i != 4 { t.Fatalf("should loop four times") } }}, {"map[string]int", ValueOf(m), func(t *testing.T, s iter.Seq2[Value, Value]) { copy := maps.Clone(m) for v1, v2 := range s { v, ok := copy[v1.String()] if !ok { t.Fatalf("unexpected %v", v1.String()) } if v != v2.Interface() { t.Fatalf("got %v, want %d", v2.Interface(), v) } delete(copy, v1.String()) } if len(copy) != 0 { t.Fatalf("should loop four times") } }}, // {"func", ValueOf(func(f func(int, int) bool) { // for i := range 4 { // f(i, i+1) // } // }), func(t *testing.T, s iter.Seq2[Value, Value]) { // i := int64(0) // for v1, v2 := range s { // if v1.Int() != i { // t.Fatalf("got %d, want %d", v1.Int(), i) // } // i++ // if v2.Int() != i { // t.Fatalf("got %d, want %d", v2.Int(), i) // } // } // if i != 4 { // t.Fatalf("should loop four times") // } // }}, // {"method", ValueOf(methodIter2{}).MethodByName("Seq2"), func(t *testing.T, s iter.Seq2[Value, Value]) { // i := int64(0) // for v1, v2 := range s { // if v1.Int() != i { // t.Fatalf("got %d, want %d", v1.Int(), i) // } // i++ // if v2.Int() != i { // t.Fatalf("got %d, want %d", v2.Int(), i) // } // } // if i != 4 { // t.Fatalf("should loop four times") // } // }}, {"[4]N", ValueOf([4]N{0, 1, 2, 3}), func(t *testing.T, s iter.Seq2[Value, Value]) { i := N(0) for v1, v2 := range s { if v1.Int() != int64(i) { t.Fatalf("got %d, want %d", v1.Int(), i) } if v2.Int() != int64(i) { t.Fatalf("got %d, want %d", v2.Int(), i) } i++ if v2.Type() != reflect.TypeOf(i) { t.Fatalf("got %s, want %s", v2.Type(), reflect.TypeOf(i)) } } if i != 4 { t.Fatalf("should loop four times") } }}, {"[]N", ValueOf([]N{1, 2, 3, 4}), func(t *testing.T, s iter.Seq2[Value, Value]) { i := N(0) for v1, v2 := range s { if v1.Int() != int64(i) { t.Fatalf("got %d, want %d", v1.Int(), i) } i++ if v2.Int() != int64(i) { t.Fatalf("got %d, want %d", v2.Int(), i) } if v2.Type() != reflect.TypeOf(i) { t.Fatalf("got %s, want %s", v2.Type(), reflect.TypeOf(i)) } } if i != 4 { t.Fatalf("should loop four times") } }}, } for _, tc := range tests { seq := tc.val.Seq2() tc.check(t, seq) } } // methodIter is a type from which we can derive a method // value that is an iter.Seq. type methodIter struct{} func (methodIter) Seq(yield func(int) bool) { for i := range 4 { if !yield(i) { return } } } // For Type.CanSeq test. func (methodIter) NonSeq(yield func(int)) {} // methodIter2 is a type from which we can derive a method // value that is an iter.Seq2. type methodIter2 struct{} func (methodIter2) Seq2(yield func(int, int) bool) { for i := range 4 { if !yield(i, i+1) { return } } } // For Type.CanSeq2 test. func (methodIter2) NonSeq2(yield func(int, int)) {} ================================================ FILE: src/reflect/makefunc.go ================================================ package reflect func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { panic("unimplemented: reflect.MakeFunc()") } ================================================ FILE: src/reflect/swapper.go ================================================ package reflect import "internal/reflectlite" func Swapper(slice interface{}) func(i, j int) { return reflectlite.Swapper(slice) } ================================================ FILE: src/reflect/tostring_test.go ================================================ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Formatting of reflection types and values for debugging. // Not defined as methods so they do not need to be linked into most binaries; // the functions are not used by the library itself, only in tests. package reflect_test import ( . "reflect" "strconv" ) // valueToString returns a textual representation of the reflection value val. // For debugging only. func valueToString(val Value) string { var str string if !val.IsValid() { return "" } typ := val.Type() switch val.Kind() { case Int, Int8, Int16, Int32, Int64: return strconv.FormatInt(val.Int(), 10) case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: return strconv.FormatUint(val.Uint(), 10) case Float32, Float64: return strconv.FormatFloat(val.Float(), 'g', -1, 64) case Complex64, Complex128: c := val.Complex() return strconv.FormatFloat(real(c), 'g', -1, 64) + "+" + strconv.FormatFloat(imag(c), 'g', -1, 64) + "i" case String: return val.String() case Bool: if val.Bool() { return "true" } else { return "false" } case Pointer: v := val str = typ.String() + "(" if v.IsNil() { str += "0" } else { str += "&" + valueToString(v.Elem()) } str += ")" return str case Array, Slice: v := val str += typ.String() str += "{" for i := 0; i < v.Len(); i++ { if i > 0 { str += ", " } str += valueToString(v.Index(i)) } str += "}" return str case Map: t := typ str = t.String() str += "{" str += "" str += "}" return str case Chan: str = typ.String() return str case Struct: t := typ v := val str += t.String() str += "{" for i, n := 0, v.NumField(); i < n; i++ { if i > 0 { str += ", " } str += valueToString(v.Field(i)) } str += "}" return str case Interface: return typ.String() + "(" + valueToString(val.Elem()) + ")" case Func: v := val return typ.String() + "(" + strconv.FormatUint(uint64(v.Pointer()), 10) + ")" default: panic("valueToString: can't print type " + typ.String()) } } ================================================ FILE: src/reflect/type.go ================================================ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Type information of an interface is stored as a pointer to a global in the // interface type (runtime._interface). This is called a type struct. // It always starts with a byte that contains both the type kind and a few // flags. In most cases it also contains a pointer to another type struct // (ptrTo), that is the pointer type of the current type (for example, type int // also has a pointer to the type *int). The exception is pointer types, to // avoid infinite recursion. // // The layouts specifically look like this: // - basic types (Bool..UnsafePointer): // meta uint8 // actually: kind + flags // ptrTo *typeStruct // - channels and slices (see elemType): // meta uint8 // nmethods uint16 (0) // ptrTo *typeStruct // elementType *typeStruct // the type that you get with .Elem() // - pointer types (see ptrType, this doesn't include chan, map, etc): // meta uint8 // nmethods uint16 // elementType *typeStruct // - array types (see arrayType) // meta uint8 // nmethods uint16 (0) // ptrTo *typeStruct // elem *typeStruct // element type of the array // arrayLen uintptr // length of the array (this is part of the type) // slicePtr *typeStruct // pointer to []T type // - map types (this is still missing the key and element types) // meta uint8 // nmethods uint16 (0) // ptrTo *typeStruct // elem *typeStruct // key *typeStruct // - struct types (see structType): // meta uint8 // nmethods uint16 // ptrTo *typeStruct // size uint32 // pkgpath *byte // package path; null terminated // numField uint16 // fields [...]structField // the remaining fields are all of type structField // - interface types (this is missing the interface methods): // meta uint8 // ptrTo *typeStruct // - signature types (this is missing input and output parameters): // meta uint8 // ptrTo *typeStruct // - named types // meta uint8 // nmethods uint16 // number of methods // ptrTo *typeStruct // elem *typeStruct // underlying type // pkgpath *byte // pkgpath; null terminated // name [1]byte // actual name; null terminated // // The type struct is essentially a union of all the above types. Which it is, // can be determined by looking at the meta byte. package reflect import ( "internal/reflectlite" "unsafe" ) type Kind = reflectlite.Kind const ( Invalid Kind = reflectlite.Invalid Bool Kind = reflectlite.Bool Int Kind = reflectlite.Int Int8 Kind = reflectlite.Int8 Int16 Kind = reflectlite.Int16 Int32 Kind = reflectlite.Int32 Int64 Kind = reflectlite.Int64 Uint Kind = reflectlite.Uint Uint8 Kind = reflectlite.Uint8 Uint16 Kind = reflectlite.Uint16 Uint32 Kind = reflectlite.Uint32 Uint64 Kind = reflectlite.Uint64 Uintptr Kind = reflectlite.Uintptr Float32 Kind = reflectlite.Float32 Float64 Kind = reflectlite.Float64 Complex64 Kind = reflectlite.Complex64 Complex128 Kind = reflectlite.Complex128 Array Kind = reflectlite.Array Chan Kind = reflectlite.Chan Func Kind = reflectlite.Func Interface Kind = reflectlite.Interface Map Kind = reflectlite.Map Pointer Kind = reflectlite.Pointer Slice Kind = reflectlite.Slice String Kind = reflectlite.String Struct Kind = reflectlite.Struct UnsafePointer Kind = reflectlite.UnsafePointer ) const Ptr = reflectlite.Ptr type ChanDir = reflectlite.ChanDir const ( RecvDir = reflectlite.RecvDir SendDir = reflectlite.SendDir BothDir = reflectlite.BothDir ) // Method represents a single method. type Method struct { // Name is the method name. Name string // PkgPath is the package path that qualifies a lower case (unexported) // method name. It is empty for upper case (exported) method names. // The combination of PkgPath and Name uniquely identifies a method // in a method set. // See https://golang.org/ref/spec#Uniqueness_of_identifiers PkgPath string Type Type // method type Func Value // func with receiver as first argument Index int // index for Type.Method } // IsExported reports whether the method is exported. func (m Method) IsExported() bool { return m.PkgPath == "" } // The following Type type has been copied almost entirely from // https://github.com/golang/go/blob/go1.15/src/reflect/type.go#L27-L212. // Some methods have been commented out as they haven't yet been implemented. // Type is the representation of a Go type. // // Not all methods apply to all kinds of types. Restrictions, // if any, are noted in the documentation for each method. // Use the Kind method to find out the kind of type before // calling kind-specific methods. Calling a method // inappropriate to the kind of type causes a run-time panic. // // Type values are comparable, such as with the == operator, // so they can be used as map keys. // Two Type values are equal if they represent identical types. type Type interface { // Methods applicable to all types. // Align returns the alignment in bytes of a value of // this type when allocated in memory. Align() int // FieldAlign returns the alignment in bytes of a value of // this type when used as a field in a struct. FieldAlign() int // Method returns the i'th method in the type's method set. // It panics if i is not in the range [0, NumMethod()). // // For a non-interface type T or *T, the returned Method's Type and Func // fields describe a function whose first argument is the receiver. // // For an interface type, the returned Method's Type field gives the // method signature, without a receiver, and the Func field is nil. // // Only exported methods are accessible and they are sorted in // lexicographic order. Method(int) Method // MethodByName returns the method with that name in the type's // method set and a boolean indicating if the method was found. // // For a non-interface type T or *T, the returned Method's Type and Func // fields describe a function whose first argument is the receiver. // // For an interface type, the returned Method's Type field gives the // method signature, without a receiver, and the Func field is nil. MethodByName(string) (Method, bool) // NumMethod returns the number of exported methods in the type's method set. NumMethod() int // Name returns the type's name within its package for a defined type. // For other (non-defined) types it returns the empty string. Name() string // PkgPath returns a defined type's package path, that is, the import path // that uniquely identifies the package, such as "encoding/base64". // If the type was predeclared (string, error) or not defined (*T, struct{}, // []int, or A where A is an alias for a non-defined type), the package path // will be the empty string. PkgPath() string // Size returns the number of bytes needed to store // a value of the given type; it is analogous to unsafe.Sizeof. Size() uintptr // String returns a string representation of the type. // The string representation may use shortened package names // (e.g., base64 instead of "encoding/base64") and is not // guaranteed to be unique among types. To test for type identity, // compare the Types directly. String() string // Kind returns the specific kind of this type. Kind() Kind // Implements reports whether the type implements the interface type u. Implements(u Type) bool // AssignableTo reports whether a value of the type is assignable to type u. AssignableTo(u Type) bool // ConvertibleTo reports whether a value of the type is convertible to type u. ConvertibleTo(u Type) bool // Comparable reports whether values of this type are comparable. Comparable() bool // Methods applicable only to some types, depending on Kind. // The methods allowed for each kind are: // // Int*, Uint*, Float*, Complex*: Bits // Array: Elem, Len // Chan: ChanDir, Elem // Func: In, NumIn, Out, NumOut, IsVariadic. // Map: Key, Elem // Pointer: Elem // Slice: Elem // Struct: Field, FieldByIndex, FieldByName, FieldByNameFunc, NumField // Bits returns the size of the type in bits. // It panics if the type's Kind is not one of the // sized or unsized Int, Uint, Float, or Complex kinds. Bits() int // ChanDir returns a channel type's direction. // It panics if the type's Kind is not Chan. ChanDir() ChanDir // IsVariadic reports whether a function type's final input parameter // is a "..." parameter. If so, t.In(t.NumIn() - 1) returns the parameter's // implicit actual type []T. // // For concreteness, if t represents func(x int, y ... float64), then // // t.NumIn() == 2 // t.In(0) is the reflect.Type for "int" // t.In(1) is the reflect.Type for "[]float64" // t.IsVariadic() == true // // IsVariadic panics if the type's Kind is not Func. IsVariadic() bool // Elem returns a type's element type. // It panics if the type's Kind is not Array, Chan, Map, Pointer, or Slice. Elem() Type // Field returns a struct type's i'th field. // It panics if the type's Kind is not Struct. // It panics if i is not in the range [0, NumField()). Field(i int) StructField // FieldByIndex returns the nested field corresponding // to the index sequence. It is equivalent to calling Field // successively for each index i. // It panics if the type's Kind is not Struct. FieldByIndex(index []int) StructField // FieldByName returns the struct field with the given name // and a boolean indicating if the field was found. FieldByName(name string) (StructField, bool) // FieldByNameFunc returns the struct field with a name // that satisfies the match function and a boolean indicating if // the field was found. // // FieldByNameFunc considers the fields in the struct itself // and then the fields in any embedded structs, in breadth first order, // stopping at the shallowest nesting depth containing one or more // fields satisfying the match function. If multiple fields at that depth // satisfy the match function, they cancel each other // and FieldByNameFunc returns no match. // This behavior mirrors Go's handling of name lookup in // structs containing embedded fields. FieldByNameFunc(match func(string) bool) (StructField, bool) // In returns the type of a function type's i'th input parameter. // It panics if the type's Kind is not Func. // It panics if i is not in the range [0, NumIn()). In(i int) Type // Key returns a map type's key type. // It panics if the type's Kind is not Map. Key() Type // Len returns an array type's length. // It panics if the type's Kind is not Array. Len() int // NumField returns a struct type's field count. // It panics if the type's Kind is not Struct. NumField() int // NumIn returns a function type's input parameter count. // It panics if the type's Kind is not Func. NumIn() int // NumOut returns a function type's output parameter count. // It panics if the type's Kind is not Func. NumOut() int // Out returns the type of a function type's i'th output parameter. // It panics if the type's Kind is not Func. // It panics if i is not in the range [0, NumOut()). Out(i int) Type // OverflowComplex reports whether the complex128 x cannot be represented by type t. // It panics if t's Kind is not Complex64 or Complex128. OverflowComplex(x complex128) bool // OverflowFloat reports whether the float64 x cannot be represented by type t. // It panics if t's Kind is not Float32 or Float64. OverflowFloat(x float64) bool // OverflowInt reports whether the int64 x cannot be represented by type t. // It panics if t's Kind is not Int, Int8, Int16, Int32, or Int64. OverflowInt(x int64) bool // OverflowUint reports whether the uint64 x cannot be represented by type t. // It panics if t's Kind is not Uint, Uintptr, Uint8, Uint16, Uint32, or Uint64. OverflowUint(x uint64) bool // CanSeq reports whether a [Value] with this type can be iterated over using [Value.Seq]. CanSeq() bool // CanSeq2 reports whether a [Value] with this type can be iterated over using [Value.Seq2]. CanSeq2() bool } type rawType struct { reflectlite.RawType } func toType(t reflectlite.Type) Type { if t == nil { return nil } return (*rawType)(unsafe.Pointer(t.(*reflectlite.RawType))) } func toRawType(t Type) *reflectlite.RawType { return (*reflectlite.RawType)(unsafe.Pointer(t.(*rawType))) } func TypeOf(i interface{}) Type { return toType(reflectlite.TypeOf(i)) } func PtrTo(t Type) Type { return PointerTo(t) } func PointerTo(t Type) Type { return toType(reflectlite.PointerTo(toRawType(t))) } func (t *rawType) AssignableTo(u Type) bool { return t.RawType.AssignableTo(&(u.(*rawType).RawType)) } func (t *rawType) CanSeq() bool { switch t.Kind() { case Int8, Int16, Int32, Int64, Int, Uint8, Uint16, Uint32, Uint64, Uint, Uintptr, Array, Slice, Chan, String, Map: return true case Func: // TODO: implement canRangeFunc // return canRangeFunc(t) panic("unimplemented: (reflect.Type).CanSeq() for functions") case Pointer: return t.Elem().Kind() == Array } return false } func (t *rawType) CanSeq2() bool { switch t.Kind() { case Array, Slice, String, Map: return true case Func: // TODO: implement canRangeFunc2 // return canRangeFunc2(t) panic("unimplemented: (reflect.Type).CanSeq2() for functions") case Pointer: return t.Elem().Kind() == Array } return false } func (t *rawType) ConvertibleTo(u Type) bool { panic("unimplemented: (reflect.Type).ConvertibleTo()") } func (t *rawType) Elem() Type { return toType(t.RawType.Elem()) } func (t *rawType) Field(i int) StructField { f := t.RawType.Field(i) return toStructField(f) } func (t *rawType) FieldByIndex(index []int) StructField { f := t.RawType.FieldByIndex(index) return toStructField(f) } func (t *rawType) FieldByName(name string) (StructField, bool) { f, ok := t.RawType.FieldByName(name) return toStructField(f), ok } func (t *rawType) FieldByNameFunc(match func(string) bool) (StructField, bool) { f, ok := t.RawType.FieldByNameFunc(match) return toStructField(f), ok } func (t *rawType) Implements(u Type) bool { return t.RawType.Implements(&(u.(*rawType).RawType)) } func (t *rawType) In(i int) Type { panic("unimplemented: (reflect.Type).In()") } func (t *rawType) IsVariadic() bool { panic("unimplemented: (reflect.Type).IsVariadic()") } func (t *rawType) Key() Type { return toType(t.RawType.Key()) } func (t *rawType) Method(i int) Method { panic("unimplemented: (reflect.Type).Method()") } func (t *rawType) MethodByName(name string) (Method, bool) { panic("unimplemented: (reflect.Type).MethodByName()") } func (t *rawType) NumIn() int { panic("unimplemented: (reflect.Type).NumIn()") } func (t *rawType) NumOut() int { panic("unimplemented: (reflect.Type).NumOut()") } func (t *rawType) Out(i int) Type { panic("unimplemented: (reflect.Type).Out()") } // A StructField describes a single field in a struct. // This must be kept in sync with [reflectlite.StructField]. type StructField struct { // Name indicates the field name. Name string // PkgPath is the package path where the struct containing this field is // declared for unexported fields, or the empty string for exported fields. PkgPath string Type Type Tag StructTag // field tag string Offset uintptr Index []int // index sequence for Type.FieldByIndex Anonymous bool } func toStructField(f reflectlite.StructField) StructField { return StructField{ Name: f.Name, PkgPath: f.PkgPath, Type: toType(f.Type), Tag: f.Tag, Offset: f.Offset, Index: f.Index, Anonymous: f.Anonymous, } } // IsExported reports whether the field is exported. func (f StructField) IsExported() bool { return f.PkgPath == "" } type StructTag = reflectlite.StructTag func TypeFor[T any]() Type { return toType(reflectlite.TypeFor[T]()) } func SliceOf(t Type) Type { return toType(reflectlite.SliceOf(toRawType(t))) } func ArrayOf(n int, t Type) Type { return toType(reflectlite.ArrayOf(n, toRawType(t))) } func StructOf([]StructField) Type { return toType(reflectlite.StructOf([]reflectlite.StructField{})) } func MapOf(key, value Type) Type { return toType(reflectlite.MapOf(toRawType(key), toRawType(value))) } func FuncOf(in, out []Type, variadic bool) Type { rawIn := make([]reflectlite.Type, len(in)) for i, t := range in { rawIn[i] = toRawType(t) } rawOut := make([]reflectlite.Type, len(out)) for i, t := range out { rawOut[i] = toRawType(t) } return toType(reflectlite.FuncOf(rawIn, rawOut, variadic)) } func ChanOf(dir ChanDir, t Type) Type { panic("unimplemented: reflect.ChanOf") } ================================================ FILE: src/reflect/type_test.go ================================================ // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package reflect_test import ( "reflect" "testing" ) func TestTypeFor(t *testing.T) { type ( mystring string myiface interface{} ) testcases := []struct { wantFrom any got reflect.Type }{ {new(int), reflect.TypeFor[int]()}, {new(int64), reflect.TypeFor[int64]()}, {new(string), reflect.TypeFor[string]()}, {new(mystring), reflect.TypeFor[mystring]()}, {new(any), reflect.TypeFor[any]()}, {new(myiface), reflect.TypeFor[myiface]()}, } for _, tc := range testcases { want := reflect.ValueOf(tc.wantFrom).Elem().Type() if want != tc.got { t.Errorf("unexpected reflect.Type: got %v; want %v", tc.got, want) } } } ================================================ FILE: src/reflect/value.go ================================================ package reflect import ( "internal/reflectlite" "unsafe" ) type Value struct { reflectlite.Value } func Indirect(v Value) Value { return Value{reflectlite.Indirect(v.Value)} } func ValueOf(i interface{}) Value { return Value{reflectlite.ValueOf(i)} } func (v Value) Type() Type { return toType(v.Value.Type()) } func (v Value) Addr() Value { return Value{v.Value.Addr()} } func (v Value) Slice(i, j int) Value { return Value{v.Value.Slice(i, j)} } func (v Value) Slice3(i, j, k int) Value { return Value{v.Value.Slice3(i, j, k)} } func (v Value) Elem() Value { return Value{v.Value.Elem()} } // Field returns the value of the i'th field of this struct. func (v Value) Field(i int) Value { return Value{v.Value.Field(i)} } func (v Value) Index(i int) Value { return Value{v.Value.Index(i)} } func (v Value) MapKeys() []Value { keys := v.Value.MapKeys() return *(*[]Value)(unsafe.Pointer(&keys)) } func (v Value) MapIndex(key Value) Value { return Value{v.Value.MapIndex(key.Value)} } func (v Value) MapRange() *MapIter { return (*MapIter)(v.Value.MapRange()) } type MapIter reflectlite.MapIter func (it *MapIter) Key() Value { return Value{((*reflectlite.MapIter)(it)).Key()} } func (v Value) SetIterKey(iter *MapIter) { v.Value.SetIterKey((*reflectlite.MapIter)(iter)) } func (it *MapIter) Value() Value { return Value{((*reflectlite.MapIter)(it)).Value()} } func (v Value) SetIterValue(iter *MapIter) { v.Value.SetIterValue((*reflectlite.MapIter)(iter)) } func (it *MapIter) Next() bool { return ((*reflectlite.MapIter)(it)).Next() } func (it *MapIter) Reset(v Value) { (*reflectlite.MapIter)(it).Reset(v.Value) } func (v Value) Set(x Value) { v.Value.Set(x.Value) } func (v Value) CanConvert(t Type) bool { return v.Value.CanConvert(toRawType(t)) } func (v Value) Convert(t Type) Value { return Value{v.Value.Convert(toRawType(t))} } func MakeSlice(typ Type, len, cap int) Value { return Value{reflectlite.MakeSlice(toRawType(typ), len, cap)} } func Zero(typ Type) Value { return Value{reflectlite.Zero(toRawType(typ))} } // New is the reflect equivalent of the new(T) keyword, returning a pointer to a // new value of the given type. func New(typ Type) Value { return Value{reflectlite.New(toRawType(typ))} } type ValueError = reflectlite.ValueError // Copy copies the contents of src into dst until either // dst has been filled or src has been exhausted. func Copy(dst, src Value) int { return reflectlite.Copy(dst.Value, src.Value) } // Append appends the values x to a slice s and returns the resulting slice. // As in Go, each x's value must be assignable to the slice's element type. func Append(v Value, x ...Value) Value { y := *(*[]reflectlite.Value)(unsafe.Pointer(&x)) return Value{reflectlite.Append(v.Value, y...)} } // AppendSlice appends a slice t to a slice s and returns the resulting slice. // The slices s and t must have the same element type. func AppendSlice(s, t Value) Value { return Value{reflectlite.AppendSlice(s.Value, t.Value)} } func (v Value) SetMapIndex(key, elem Value) { v.Value.SetMapIndex(key.Value, elem.Value) } // FieldByIndex returns the nested field corresponding to index. func (v Value) FieldByIndex(index []int) Value { return Value{v.Value.FieldByIndex(index)} } // FieldByIndexErr returns the nested field corresponding to index. func (v Value) FieldByIndexErr(index []int) (Value, error) { out, err := v.Value.FieldByIndexErr(index) return Value{out}, err } func (v Value) FieldByName(name string) Value { return Value{v.Value.FieldByName(name)} } func (v Value) FieldByNameFunc(match func(string) bool) Value { return Value{v.Value.FieldByNameFunc(match)} } type SelectDir int const ( _ SelectDir = iota SelectSend // case Chan <- Send SelectRecv // case <-Chan: SelectDefault // default ) type SelectCase struct { Dir SelectDir // direction of case Chan Value // channel to use (for send or receive) Send Value // value to send (for send) } func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool) { panic("unimplemented: reflect.Select") } func (v Value) Send(x Value) { panic("unimplemented: reflect.Value.Send()") } func (v Value) TrySend(x Value) bool { panic("unimplemented: reflect.Value.TrySend()") } func (v Value) Close() { panic("unimplemented: reflect.Value.Close()") } // MakeMap creates a new map with the specified type. func MakeMap(typ Type) Value { return Value{reflectlite.MakeMap(toRawType(typ))} } // MakeMapWithSize creates a new map with the specified type and initial space // for approximately n elements. func MakeMapWithSize(typ Type, n int) Value { return Value{reflectlite.MakeMapWithSize(toRawType(typ), n)} } func (v Value) Call(in []Value) []Value { panic("unimplemented: (reflect.Value).Call()") } func (v Value) CallSlice(in []Value) []Value { panic("unimplemented: (reflect.Value).CallSlice()") } func (v Value) Equal(u Value) bool { return v.Value.Equal(u.Value) } func (v Value) Method(i int) Value { panic("unimplemented: (reflect.Value).Method()") } func (v Value) MethodByName(name string) Value { panic("unimplemented: (reflect.Value).MethodByName()") } func (v Value) Recv() (x Value, ok bool) { panic("unimplemented: (reflect.Value).Recv()") } func (v Value) TryRecv() (x Value, ok bool) { panic("unimplemented: (reflect.Value).TryRecv()") } func NewAt(typ Type, p unsafe.Pointer) Value { panic("unimplemented: reflect.New()") } // Deprecated: Use unsafe.Slice or unsafe.SliceData instead. type SliceHeader struct { Data uintptr Len intw Cap intw } // Deprecated: Use unsafe.String or unsafe.StringData instead. type StringHeader struct { Data uintptr Len intw } // Verify SliceHeader and StringHeader sizes. // See https://github.com/tinygo-org/tinygo/pull/4156 // and https://github.com/tinygo-org/tinygo/issues/1284. var ( _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(SliceHeader{})]byte{} _ [unsafe.Sizeof("")]byte = [unsafe.Sizeof(StringHeader{})]byte{} ) ================================================ FILE: src/reflect/value_test.go ================================================ package reflect_test import ( "bytes" "encoding/base64" . "reflect" "slices" "sort" "strings" "testing" ) func TestTinyIndirectPointers(t *testing.T) { var m = map[string]int{} m["x"] = 1 var a = &m if ValueOf(a).Elem().Len() != 1 { t.Errorf("bad map length via reflect") } var b struct { Decoded *[3]byte } v1 := New(TypeOf(b.Decoded).Elem()) var bb [3]byte bb[0] = 0xaa v1.Elem().Set(ValueOf(bb)) if v1.Elem().Index(0).Uint() != 0xaa { t.Errorf("bad indirect array index via reflect") } } func TestTinyMap(t *testing.T) { m := make(map[string]int) mtyp := TypeOf(m) if got, want := mtyp.Key().Kind().String(), "string"; got != want { t.Errorf("m.Type().Key().String()=%q, want %q", got, want) } if got, want := mtyp.Elem().Kind().String(), "int"; got != want { t.Errorf("m.Elem().String()=%q, want %q", got, want) } m["foo"] = 2 mref := ValueOf(m) two := mref.MapIndex(ValueOf("foo")) if got, want := two.Interface().(int), 2; got != want { t.Errorf("MapIndex(`foo`)=%v, want %v", got, want) } m["bar"] = 3 m["baz"] = 4 m["qux"] = 5 it := mref.MapRange() var gotKeys []string for it.Next() { k := it.Key() v := it.Value() kstr := k.Interface().(string) vint := v.Interface().(int) gotKeys = append(gotKeys, kstr) if m[kstr] != vint { t.Errorf("m[%v]=%v, want %v", kstr, vint, m[kstr]) } } var wantKeys []string for k := range m { wantKeys = append(wantKeys, k) } sort.Strings(gotKeys) sort.Strings(wantKeys) if !equal(gotKeys, wantKeys) { t.Errorf("MapRange return unexpected keys: got %v, want %v", gotKeys, wantKeys) } refMapKeys := mref.MapKeys() gotKeys = gotKeys[:0] for _, v := range refMapKeys { gotKeys = append(gotKeys, v.Interface().(string)) } sort.Strings(gotKeys) if !equal(gotKeys, wantKeys) { t.Errorf("MapKeys return unexpected keys: got %v, want %v", gotKeys, wantKeys) } mref.SetMapIndex(ValueOf("bar"), Value{}) if _, ok := m["bar"]; ok { t.Errorf("SetMapIndex failed to delete `bar`") } mref.SetMapIndex(ValueOf("baz"), ValueOf(6)) if got, want := m["baz"], 6; got != want { t.Errorf("SetMapIndex(bar, 6) got %v, want %v", got, want) } m2ref := MakeMap(mref.Type()) m2ref.SetMapIndex(ValueOf("foo"), ValueOf(2)) m2 := m2ref.Interface().(map[string]int) if m2["foo"] != 2 { t.Errorf("MakeMap failed to create map") } type stringint struct { s string i int } simap := make(map[stringint]int) refsimap := MakeMap(TypeOf(simap)) refsimap.SetMapIndex(ValueOf(stringint{"hello", 4}), ValueOf(6)) six := refsimap.MapIndex(ValueOf(stringint{"hello", 4})) if six.Interface().(int) != 6 { t.Errorf("m[hello, 4]=%v, want 6", six) } keys := refsimap.MapKeys() if len(keys) != 1 { t.Errorf("refsimap: MapKeys()=%v, want 1", len(keys)) } if keys[0].Type() != TypeOf(stringint{}) { t.Errorf("keys[0] has wrong type: %v", keys[0].Type().String()) } sikey := keys[0].Interface().(stringint) if sikey != (stringint{"hello", 4}) { t.Errorf("sikey has unexpected value: %#v", sikey) } // make sure we can look up interface keys with reflection type unmarshalerText struct { A, B string } ut := make(map[unmarshalerText]bool) refut := ValueOf(ut) // put in a key with the compiler ut[unmarshalerText{"x", "y"}] = true // make sure we can get it out v2 := refut.MapIndex(ValueOf(unmarshalerText{"x", "y"})) if !v2.IsValid() || !v2.Bool() { t.Errorf("Failed to look up map struct key with reflection") } // put in a key with reflection refut.SetMapIndex(ValueOf(unmarshalerText{"y", "z"}), ValueOf(true)) // make sure we can get it out with the compiler if !ut[unmarshalerText{"y", "z"}] { t.Errorf("Failed to look up up reflect-set map key with compiler") } utKeys := refut.MapKeys() // make sure keys extracted via reflection have the correct type if _, ok := utKeys[0].Interface().(unmarshalerText); !ok { t.Errorf("Map keys via MapKeys() have wrong type: %v", utKeys[0].Type().String()) } // and via iteration utIter := refut.MapRange() utIter.Next() utIterKey := utIter.Key() if _, ok := utIterKey.Interface().(unmarshalerText); !ok { t.Errorf("Map keys via MapIter() have wrong type: %v", utIterKey.Type().String()) } { m := map[any]any{1: 2} rv := ValueOf(m) iter := rv.MapRange() iter.Next() k := iter.Key() if k.Kind().String() != "interface" { t.Errorf("map[any]any MapRange has wrong key type: %v", k.Kind().String()) } keys := rv.MapKeys() if k := keys[0]; k.Kind().String() != "interface" { t.Errorf("map[any]any MapRange has wrong key type: %v", k.Kind().String()) } } } // For an interface type, it returns the number of exported and unexported methods. type counter interface { count() int } type count struct { i int } func (c *count) count() int { return c.i } func TestMapInterfaceKeys(t *testing.T) { m := make(map[interface{}]int) for i := 0; i < 20; i++ { c := &count{i} m[c] = i } for k, v := range m { if got := m[k]; got != v { t.Error("lookup failure got", got, "want", v) } } refm := ValueOf(m) keys := refm.MapKeys() if len(keys) != 20 { t.Error("failed to look up 20 keys") } for _, k := range keys { c := k.Interface().(*count) e := refm.MapIndex(k) if e.Interface().(int) != c.i { t.Error("reflect lookup failure:", c.i) } } it := refm.MapRange() var totalKeys int for it.Next() { totalKeys++ k := it.Key() v := it.Value().Interface().(int) c := k.Interface().(*count) if v != c.i || refm.MapIndex(k).Interface().(int) != c.i { t.Error("reflect iter lookup failure:", c.i) } } if totalKeys != 20 { t.Errorf("failed to get 20 keys") } } func TestMapInterfaceElem(t *testing.T) { m := make(map[string]interface{}) refm := ValueOf(m) four := ValueOf(4) hello := ValueOf("hello") refm.SetMapIndex(hello, four) if v := refm.MapIndex(hello).Interface().(int); v != 4 { t.Errorf("failed to get value assigned to interface via MapIndex") } if v := m["hello"].(int); v != 4 { t.Errorf("failed to get value assigned to interface via direct lookup") } } func TestTinySlice(t *testing.T) { s := []int{0, 10, 20} refs := ValueOf(s) for i := 3; i < 10; i++ { refs = Append(refs, ValueOf(i*10)) } s = refs.Interface().([]int) for i := 0; i < 10; i++ { if s[i] != i*10 { t.Errorf("s[%d]=%d, want %d", i, s[i], i*10) } } s28 := s[2:8] s28ref := refs.Slice(2, 8) if len(s28) != s28ref.Len() || cap(s28) != s28ref.Cap() { t.Errorf("Slice: len(s28)=%d s28ref.Len()=%d cap(s28)=%d s28ref.Cap()=%d\n", len(s28), s28ref.Len(), cap(s28), s28ref.Cap()) } for i, got := range s28 { want := int(s28ref.Index(i).Int()) if got != want { t.Errorf("s28[%d]=%d, want %d", i, got, want) } } s268 := s[2:6:8] s268ref := refs.Slice3(2, 6, 8) if len(s268) != s268ref.Len() || cap(s268) != s268ref.Cap() { t.Errorf("Slice3: len(s268)=%d s268ref.Len()=%d cap(s268)=%d s268ref.Cap()=%d\n", len(s268), s268ref.Len(), cap(s268), s268ref.Cap()) } for i, got := range s268 { want := int(s268ref.Index(i).Int()) if got != want { t.Errorf("s268[%d]=%d, want %d", i, got, want) } } // should be equivalent to s28 now, except for the capacity which doesn't change s268ref = ValueOf(&s268).Elem() s268ref.SetLen(6) if len(s28) != s268ref.Len() || cap(s268) != s268ref.Cap() { t.Errorf("SetLen: len(s268)=%d s268ref.Len()=%d cap(s268)=%d s268ref.Cap()=%d\n", len(s28), s268ref.Len(), cap(s268), s268ref.Cap()) } for i, got := range s28 { want := int(s268ref.Index(i).Int()) if got != want { t.Errorf("s28[%d]=%d, want %d", i, got, want) } } refs = MakeSlice(TypeOf(s), 5, 10) s = refs.Interface().([]int) if len(s) != refs.Len() || cap(s) != refs.Cap() { t.Errorf("len(s)=%v refs.Len()=%v cap(s)=%v refs.Cap()=%v", len(s), refs.Len(), cap(s), refs.Cap()) } } func TestTinyBytes(t *testing.T) { s := []byte("abcde") refs := ValueOf(s) s2 := refs.Bytes() if !equal(s, s2) { t.Errorf("Failed to get Bytes(): %v != %v", s, s2) } Copy(refs, ValueOf("12345")) if string(s) != "12345" { t.Errorf("Copy()=%q, want `12345`", string(s)) } // test small arrays that fit in a pointer a := [3]byte{10, 20, 30} v := ValueOf(&a) vslice := v.Elem().Bytes() if len(vslice) != 3 || cap(vslice) != 3 { t.Errorf("len(vslice)=%v, cap(vslice)=%v", len(vslice), cap(vslice)) } for i, got := range vslice { if want := (byte(i) + 1) * 10; got != want { t.Errorf("vslice[%d]=%d, want %d", i, got, want) } } } func TestTinyNamedTypes(t *testing.T) { type namedString string named := namedString("foo") if got, want := TypeOf(named).Name(), "namedString"; got != want { t.Errorf("TypeOf.Name()=%v, want %v", got, want) } if got, want := TypeOf(named).String(), "reflect_test.namedString"; got != want { t.Errorf("TypeOf.String()=%v, want %v", got, want) } errorType := TypeOf((*error)(nil)).Elem() if s := errorType.String(); s != "error" { t.Errorf("error type = %v, want error", s) } m := make(map[[4]uint16]string) if got, want := TypeOf(m).String(), "map[[4]uint16]string"; got != want { t.Errorf("Type.String()=%v, want %v", got, want) } s := struct { a int8 b int8 c int8 d int8 e int8 f int32 }{} if got, want := TypeOf(s).String(), "struct { a int8; b int8; c int8; d int8; e int8; f int32 }"; got != want { t.Errorf("Type.String()=%v, want %v", got, want) } if got, want := ValueOf(m).String(), ""; got != want { t.Errorf("Value.String()=%v, want %v", got, want) } if got, want := TypeOf(base64.Encoding{}).String(), "base64.Encoding"; got != want { t.Errorf("Type.String(base64.Encoding{})=%v, want %v", got, want) } type Repository struct { RoleName *string `json:"role_name,omitempty"` } var repo *Repository v := ValueOf(&repo).Elem() n := New(v.Type().Elem()) v.Set(n) } func TestTinyStruct(t *testing.T) { type barStruct struct { QuxString string BazInt int } type foobar struct { Foo string `foo:"struct tag"` Bar barStruct } var fb foobar fb.Bar.QuxString = "qux" reffb := TypeOf(fb) q := reffb.FieldByIndex([]int{1, 0}) if want := "QuxString"; q.Name != want { t.Errorf("FieldByIndex=%v, want %v", q.Name, want) } var ok bool q, ok = reffb.FieldByName("Foo") if q.Name != "Foo" || !ok { t.Errorf("FieldByName(Foo)=%v,%v, want Foo, true", q.Name, ok) } if got, want := q.Tag, `foo:"struct tag"`; string(got) != want { t.Errorf("StrucTag for Foo=%v, want %v", got, want) } q, ok = reffb.FieldByNameFunc(func(s string) bool { return strings.ToLower(s) == "bar" }) if q.Name != "Bar" || !ok { t.Errorf("FieldByNameFunc(bar)=%v,%v, want Bar, true", q.Name, ok) } q, ok = reffb.FieldByName("Snorble") if q.Name != "" || ok { t.Errorf("FieldByName(Snorble)=%v,%v, want ``, false", q.Name, ok) } q, ok = reffb.FieldByNameFunc(func(s string) bool { return strings.ToLower(s) == "snorble" }) if q.Name != "" || ok { t.Errorf("FieldByName(snorble)=%v,%v, want ``, false", q.Name, ok) } } func TestTinyZero(t *testing.T) { s := "hello, world" sptr := &s v := ValueOf(&sptr).Elem() v.Set(Zero(v.Type())) sptr = v.Interface().(*string) if sptr != nil { t.Errorf("failed to set a nil string pointer") } sl := []int{1, 2, 3} v = ValueOf(&sl).Elem() v.Set(Zero(v.Type())) sl = v.Interface().([]int) if sl != nil { t.Errorf("failed to set a nil slice") } } func addrDecode(body interface{}) { vbody := ValueOf(body) ptr := vbody.Elem() pptr := ptr.Addr() addrSetInt(pptr.Interface()) } func addrSetInt(intf interface{}) { ptr := intf.(*uint64) *ptr = 112358 } func TestTinyAddr(t *testing.T) { var n uint64 addrDecode(&n) if n != 112358 { t.Errorf("Failed to set t=112358, got %v", n) } v := ValueOf(&n) if got, want := v.Elem().Addr().CanAddr(), false; got != want { t.Errorf("Elem.Addr.CanAddr=%v, want %v", got, want) } } func TestTinyNilType(t *testing.T) { var a any typ := TypeOf(a) if typ != nil { t.Errorf("Type of any{nil} is not nil") } } func TestTinySetBytes(t *testing.T) { var b []byte refb := ValueOf(&b).Elem() s := []byte("hello") refb.SetBytes(s) s[0] = 'b' refbSlice := refb.Interface().([]byte) if len(refbSlice) != len(s) || b[0] != s[0] || refbSlice[0] != s[0] { t.Errorf("SetBytes(): reflection slice mismatch") } } type methodStruct struct { i int } func (m methodStruct) ValueMethod1() int { return m.i } func (m methodStruct) valueMethod2() int { return m.i } func (m *methodStruct) PointerMethod1() int { return m.i } func (m *methodStruct) PointerMethod2() int { return m.i } func (m *methodStruct) pointerMethod3() int { return m.i } func TestTinyNumMethods(t *testing.T) { refptrt := TypeOf(&methodStruct{}) if got, want := refptrt.NumMethod(), 1+2; got != want { t.Errorf("Pointer Methods=%v, want %v", got, want) } reft := refptrt.Elem() if got, want := reft.NumMethod(), 1; got != want { t.Errorf("Value Methods=%v, want %v", got, want) } } func TestAssignableTo(t *testing.T) { var a any refa := ValueOf(&a).Elem() refa.Set(ValueOf(4)) if got, want := refa.Interface().(int), 4; got != want { t.Errorf("AssignableTo / Set failed, got %v, want %v", got, want) } b := []byte{0x01, 0x02} refb := ValueOf(&b).Elem() refb.Set(ValueOf([]byte{0x02, 0x03})) if got, want := refb.Interface().([]byte), []byte{0x02, 0x03}; !bytes.Equal(got, want) { t.Errorf("AssignableTo / Set failed, got %v, want %v", got, want) } type bstr []byte c := bstr{0x01, 0x02} refc := ValueOf(&c).Elem() refc.Set(ValueOf([]byte{0x02, 0x03})) if got, want := refb.Interface().([]byte), []byte{0x02, 0x03}; !bytes.Equal(got, want) { t.Errorf("AssignableTo / Set failed, got %v, want %v", got, want) } d := []byte{0x01, 0x02} refd := ValueOf(&d).Elem() refd.Set(ValueOf(bstr{0x02, 0x03})) if got, want := refb.Interface().([]byte), []byte{0x02, 0x03}; !bytes.Equal(got, want) { t.Errorf("AssignableTo / Set failed, got %v, want %v", got, want) } } func TestConvert(t *testing.T) { v := ValueOf(int64(3)) c := v.Convert(TypeOf(byte(0))) if c.Type().Kind() != Uint8 || c.Uint() != 3 { t.Errorf("Convert(uint64 -> byte failed: kind=%v, value=%d", c.Type().Kind().String(), c.Uint()) } v = ValueOf("hello") c = v.Convert(TypeOf([]byte(""))) if c.Type().Kind() != Slice || c.Type().Elem().Kind() != Uint8 && c.Len() != 5 && string(c.Bytes()) != "hello" { t.Errorf("Convert(string -> []byte") } type namedString string c = v.Convert(TypeOf(namedString(""))) if c.Type().Kind() != String || c.Type().Name() != "namedString" { t.Errorf("Convert(string -> namedString") } } func TestConvertSliceToArrayOrArrayPointer(t *testing.T) { s := make([]byte, 2, 4) // a0 := [0]byte(s) // a1 := [1]byte(s[1:]) // a1[0] == s[1] // a2 := [2]byte(s) // a2[0] == s[0] // a4 := [4]byte(s) // panics: len([4]byte) > len(s) v := ValueOf(s).Convert(TypeFor[[0]byte]()) if v.Kind() != Array || v.Type().Len() != 0 { t.Error("Convert([]byte -> [0]byte)") } v = ValueOf(s[1:]).Convert(TypeFor[[1]byte]()) if v.Kind() != Array || v.Type().Len() != 1 { t.Error("Convert([]byte -> [1]byte)") } v = ValueOf(s).Convert(TypeFor[[2]byte]()) if v.Kind() != Array || v.Type().Len() != 2 { t.Error("Convert([]byte -> [2]byte)") } if ValueOf(s).CanConvert(TypeFor[[4]byte]()) { t.Error("Converting a slice with len smaller than array to array should fail") } // s0 := (*[0]byte)(s) // s0 != nil // s1 := (*[1]byte)(s[1:]) // &s1[0] == &s[1] // s2 := (*[2]byte)(s) // &s2[0] == &s[0] // s4 := (*[4]byte)(s) // panics: len([4]byte) > len(s) v = ValueOf(s).Convert(TypeFor[*[0]byte]()) if v.Kind() != Pointer || v.Elem().Kind() != Array || v.Elem().Type().Len() != 0 { t.Error("Convert([]byte -> *[0]byte)") } v = ValueOf(s[1:]).Convert(TypeFor[*[1]byte]()) if v.Kind() != Pointer || v.Elem().Kind() != Array || v.Elem().Type().Len() != 1 { t.Error("Convert([]byte -> *[1]byte)") } v = ValueOf(s).Convert(TypeFor[*[2]byte]()) if v.Kind() != Pointer || v.Elem().Kind() != Array || v.Elem().Type().Len() != 2 { t.Error("Convert([]byte -> *[2]byte)") } if ValueOf(s).CanConvert(TypeFor[*[4]byte]()) { t.Error("Converting a slice with len smaller than array to array pointer should fail") } // Test converting slices with backing arrays <= and >64bits slice64 := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} array64 := ValueOf(slice64).Convert(TypeFor[[8]byte]()).Interface().([8]byte) if !bytes.Equal(slice64, array64[:]) { t.Errorf("converted array %x does not match backing array of slice %x", array64, slice64) } slice72 := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09} array72 := ValueOf(slice72).Convert(TypeFor[[9]byte]()).Interface().([9]byte) if !bytes.Equal(slice72, array72[:]) { t.Errorf("converted array %x does not match backing array of slice %x", array72, slice72) } } func TestConvertToEmptyInterface(t *testing.T) { anyType := TypeFor[interface{}]() v := ValueOf(false).Convert(anyType) if v.Kind() != Interface || v.NumMethod() > 0 { t.Error("Convert(bool -> interface{})") } _ = v.Interface().(interface{}).(bool) v = ValueOf(int64(3)).Convert(anyType) if v.Kind() != Interface || v.NumMethod() > 0 { t.Error("Convert(int64 -> interface{})") } _ = v.Interface().(interface{}).(int64) v = ValueOf(struct{}{}).Convert(anyType) if v.Kind() != Interface || v.NumMethod() > 0 { t.Error("Convert(struct -> interface{})") } _ = v.Interface().(interface{}).(struct{}) v = ValueOf([]struct{}{}).Convert(anyType) if v.Kind() != Interface || v.NumMethod() > 0 { t.Error("Convert(slice -> interface{})") } _ = v.Interface().(interface{}).([]struct{}) v = ValueOf(map[string]string{"A": "B"}).Convert(anyType) if v.Kind() != Interface || v.NumMethod() > 0 { t.Error("Convert(map -> interface{})") } _ = v.Interface().(interface{}).(map[string]string) } func TestClearSlice(t *testing.T) { type stringSlice []string for _, test := range []struct { slice any expect any }{ { slice: []bool{true, false, true}, expect: []bool{false, false, false}, }, { slice: []byte{0x00, 0x01, 0x02, 0x03}, expect: []byte{0x00, 0x00, 0x00, 0x00}, }, { slice: [][]int{[]int{2, 1}, []int{3}, []int{}}, expect: [][]int{nil, nil, nil}, }, { slice: []stringSlice{stringSlice{"hello", "world"}, stringSlice{}, stringSlice{"goodbye"}}, expect: []stringSlice{nil, nil, nil}, }, } { v := ValueOf(test.slice) expectLen, expectCap := v.Len(), v.Cap() v.Clear() if len := v.Len(); len != expectLen { t.Errorf("Clear(slice) altered len, got %d, expected %d", len, expectLen) } if cap := v.Cap(); cap != expectCap { t.Errorf("Clear(slice) altered cap, got %d, expected %d", cap, expectCap) } if !DeepEqual(test.slice, test.expect) { t.Errorf("Clear(slice) got %v, expected %v", test.slice, test.expect) } } } func TestClearMap(t *testing.T) { for _, test := range []struct { m any expect any }{ { m: map[int]bool{1: true, 2: false, 3: true}, expect: map[int]bool{}, }, { m: map[string][]byte{"hello": []byte("world")}, expect: map[string][]byte{}, }, } { v := ValueOf(test.m) v.Clear() if len := v.Len(); len != 0 { t.Errorf("Clear(map) should set len to 0, got %d", len) } if !DeepEqual(test.m, test.expect) { t.Errorf("Clear(map) got %v, expected %v", test.m, test.expect) } } } func TestCopyArrayToSlice(t *testing.T) { // Test copying array <=64 bits and >64bits // See issue #4554 a1 := [1]int64{1} s1 := make([]int64, 1) a2 := [2]int64{1, 2} s2 := make([]int64, 2) a8 := [8]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} s8 := make([]byte, 8) a9 := [9]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09} s9 := make([]byte, 9) Copy(ValueOf(s1), ValueOf(a1)) if !slices.Equal(s1, a1[:]) { t.Errorf("copied slice %x does not match input array %x", s1, a1[:]) } Copy(ValueOf(s2), ValueOf(a2)) if !slices.Equal(s2, a2[:]) { t.Errorf("copied slice %x does not match input array %x", s2, a2[:]) } Copy(ValueOf(s8), ValueOf(a8)) if !bytes.Equal(s8, a8[:]) { t.Errorf("copied slice %x does not match input array %x", s8, a8[:]) } Copy(ValueOf(s9), ValueOf(a9)) if !bytes.Equal(s9, a9[:]) { t.Errorf("copied slice %x does not match input array %x", s9, a9[:]) } } func TestIssue4040(t *testing.T) { var value interface{} = uint16(0) // get the pointer to the interface value inPtr := ValueOf(&value) // dereference to get the actual value (an interface) inElem := inPtr.Elem() // create a new value of the same concrete type uint16Type := TypeOf(uint16(0)) newUint16Value := New(uint16Type).Elem() newUint16Value.Set(ValueOf(uint16(13))) // set the new value to the interface inElem.Set(newUint16Value) if value.(uint16) != 13 { t.Errorf("Failed to set interface value from uint16") } } func equal[T comparable](a, b []T) bool { if len(a) != len(b) { return false } for i, aa := range a { if b[i] != aa { return false } } return true } ================================================ FILE: src/reflect/visiblefields.go ================================================ package reflect import ( "internal/reflectlite" "unsafe" ) func VisibleFields(t Type) []StructField { fields := reflectlite.VisibleFields(toRawType(t)) return *(*[]StructField)(unsafe.Pointer(&fields)) } ================================================ FILE: src/reflect/visiblefields_test.go ================================================ // Copyright 2021 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package reflect_test import ( . "reflect" "strings" "testing" ) type structField struct { name string index []int } var fieldsTests = []struct { testName string val any expect []structField }{{ testName: "SimpleStruct", val: struct { A int B string C bool }{}, expect: []structField{{ name: "A", index: []int{0}, }, { name: "B", index: []int{1}, }, { name: "C", index: []int{2}, }}, }, { testName: "NonEmbeddedStructMember", val: struct { A struct { X int } }{}, expect: []structField{{ name: "A", index: []int{0}, }}, }, { testName: "EmbeddedExportedStruct", val: struct { SFG }{}, expect: []structField{{ name: "SFG", index: []int{0}, }, { name: "F", index: []int{0, 0}, }, { name: "G", index: []int{0, 1}, }}, }, { testName: "EmbeddedUnexportedStruct", val: struct { sFG }{}, expect: []structField{{ name: "sFG", index: []int{0}, }, { name: "F", index: []int{0, 0}, }, { name: "G", index: []int{0, 1}, }}, }, { testName: "TwoEmbeddedStructsWithCancelingMembers", val: struct { SFG SF }{}, expect: []structField{{ name: "SFG", index: []int{0}, }, { name: "G", index: []int{0, 1}, }, { name: "SF", index: []int{1}, }}, }, { testName: "EmbeddedStructsWithSameFieldsAtDifferentDepths", val: struct { SFGH3 SG1 SFG2 SF2 L int }{}, expect: []structField{{ name: "SFGH3", index: []int{0}, }, { name: "SFGH2", index: []int{0, 0}, }, { name: "SFGH1", index: []int{0, 0, 0}, }, { name: "SFGH", index: []int{0, 0, 0, 0}, }, { name: "H", index: []int{0, 0, 0, 0, 2}, }, { name: "SG1", index: []int{1}, }, { name: "SG", index: []int{1, 0}, }, { name: "G", index: []int{1, 0, 0}, }, { name: "SFG2", index: []int{2}, }, { name: "SFG1", index: []int{2, 0}, }, { name: "SFG", index: []int{2, 0, 0}, }, { name: "SF2", index: []int{3}, }, { name: "SF1", index: []int{3, 0}, }, { name: "SF", index: []int{3, 0, 0}, }, { name: "L", index: []int{4}, }}, }, { testName: "EmbeddedPointerStruct", val: struct { *SF }{}, expect: []structField{{ name: "SF", index: []int{0}, }, { name: "F", index: []int{0, 0}, }}, }, { testName: "EmbeddedNotAPointer", val: struct { M }{}, expect: []structField{{ name: "M", index: []int{0}, }}, }, { testName: "RecursiveEmbedding", val: Rec1{}, expect: []structField{{ name: "Rec2", index: []int{0}, }, { name: "F", index: []int{0, 0}, }, { name: "Rec1", index: []int{0, 1}, }}, }, { testName: "RecursiveEmbedding2", val: Rec2{}, expect: []structField{{ name: "F", index: []int{0}, }, { name: "Rec1", index: []int{1}, }, { name: "Rec2", index: []int{1, 0}, }}, }, { testName: "RecursiveEmbedding3", val: RS3{}, expect: []structField{{ name: "RS2", index: []int{0}, }, { name: "RS1", index: []int{1}, }, { name: "i", index: []int{1, 0}, }}, }} type SFG struct { F int G int } type SFG1 struct { SFG } type SFG2 struct { SFG1 } type SFGH struct { F int G int H int } type SFGH1 struct { SFGH } type SFGH2 struct { SFGH1 } type SFGH3 struct { SFGH2 } type SF struct { F int } type SF1 struct { SF } type SF2 struct { SF1 } type SG struct { G int } type SG1 struct { SG } type sFG struct { F int G int } type RS1 struct { i int } type RS2 struct { RS1 } type RS3 struct { RS2 RS1 } type M map[string]any type Rec1 struct { *Rec2 } type Rec2 struct { F string *Rec1 } func TestFields(t *testing.T) { for _, test := range fieldsTests { test := test t.Run(test.testName, func(t *testing.T) { typ := TypeOf(test.val) fields := VisibleFields(typ) if got, want := len(fields), len(test.expect); got != want { t.Fatalf("unexpected field count; got %d want %d", got, want) } for j, field := range fields { expect := test.expect[j] t.Logf("field %d: %s", j, expect.name) gotField := typ.FieldByIndex(field.Index) // Unfortunately, FieldByIndex does not return // a field with the same index that we passed in, // so we set it to the expected value so that // it can be compared later with the result of FieldByName. gotField.Index = field.Index expectField := typ.FieldByIndex(expect.index) // ditto. expectField.Index = expect.index if !DeepEqual(gotField, expectField) { t.Fatalf("unexpected field result\ngot %#v\nwant %#v", gotField, expectField) } // Sanity check that we can actually access the field by the // expected name. gotField1, ok := typ.FieldByName(expect.name) if !ok { t.Fatalf("field %q not accessible by name", expect.name) } if !DeepEqual(gotField1, expectField) { t.Fatalf("unexpected FieldByName result; got %#v want %#v", gotField1, expectField) } } }) } } // Must not panic with nil embedded pointer. func TestFieldByIndexErr(t *testing.T) { // TODO(dgryski): FieldByIndexErr not implemented yet -- skip return type A struct { S string } type B struct { *A } v := ValueOf(B{}) _, err := v.FieldByIndexErr([]int{0, 0}) if err == nil { t.Fatal("expected error") } if !strings.Contains(err.Error(), "embedded struct field A") { t.Fatal(err) } } ================================================ FILE: src/runtime/algorithm.go ================================================ package runtime // This file implements various core algorithms used in the runtime package and // standard library. import ( "unsafe" ) // This function is needed by math/rand since Go 1.20. // See: https://github.com/golang/go/issues/54880 // //go:linkname rand_fastrand64 math/rand.fastrand64 func rand_fastrand64() uint64 { return fastrand64() } // This function is used by hash/maphash. // This function isn't required anymore since Go 1.22, so should be removed once // that becomes the minimum requirement. func fastrand() uint32 { xorshift32State = xorshift32(xorshift32State) return xorshift32State } func initRand() { r, _ := hardwareRand() xorshift64State = uint64(r | 1) // protect against 0 xorshift32State = uint32(xorshift64State) } var xorshift32State uint32 = 1 func xorshift32(x uint32) uint32 { // Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs". // Improved sequence based on // http://www.iro.umontreal.ca/~lecuyer/myftp/papers/xorshift.pdf x ^= x << 7 x ^= x >> 1 x ^= x << 9 return x } // This function is used by hash/maphash. // This function isn't required anymore since Go 1.22, so should be removed once // that becomes the minimum requirement. func fastrand64() uint64 { xorshift64State = xorshiftMult64(xorshift64State) return xorshift64State } var xorshift64State uint64 = 1 // 64-bit xorshift multiply rng from http://vigna.di.unimi.it/ftp/papers/xorshift.pdf func xorshiftMult64(x uint64) uint64 { x ^= x >> 12 // a x ^= x << 25 // b x ^= x >> 27 // c return x * 2685821657736338717 } // This function is used by hash/maphash. func memhash(p unsafe.Pointer, seed, s uintptr) uintptr { if unsafe.Sizeof(uintptr(0)) > 4 { return uintptr(hash64(p, s, seed)) } return uintptr(hash32(p, s, seed)) } // Function that's called from various packages starting with Go 1.22. func rand() uint64 { // Return a random number from hardware, falling back to software if // unavailable. n, ok := hardwareRand() if !ok { // Fallback to static random number. // Not great, but we can't do much better than this. n = fastrand64() } return n } ================================================ FILE: src/runtime/arch-has-returnaddr.go ================================================ //go:build !(avr || tinygo.wasm) package runtime import "unsafe" const hasReturnAddr = true //export llvm.returnaddress func returnAddress(level uint32) unsafe.Pointer ================================================ FILE: src/runtime/arch-no-returnaddr.go ================================================ //go:build avr || tinygo.wasm package runtime import "unsafe" const hasReturnAddr = false func returnAddress(level uint32) unsafe.Pointer { return nil } ================================================ FILE: src/runtime/arch_386.go ================================================ package runtime const GOARCH = "386" // The bitness of the CPU (e.g. 8, 32, 64). const TargetBits = 32 const deferExtraRegs = 0 const callInstSize = 5 // "call someFunction" is 5 bytes const ( linux_MAP_ANONYMOUS = 0x20 linux_SIGBUS = 7 linux_SIGILL = 4 linux_SIGSEGV = 11 ) // Align on word boundary. func align(ptr uintptr) uintptr { return (ptr + 15) &^ 15 } func getCurrentStackPointer() uintptr { return uintptr(stacksave()) } ================================================ FILE: src/runtime/arch_amd64.go ================================================ package runtime const GOARCH = "amd64" // The bitness of the CPU (e.g. 8, 32, 64). const TargetBits = 64 const deferExtraRegs = 0 const callInstSize = 5 // "call someFunction" is 5 bytes const ( linux_MAP_ANONYMOUS = 0x20 linux_SIGBUS = 7 linux_SIGILL = 4 linux_SIGSEGV = 11 ) // Align a pointer. // Note that some amd64 instructions (like movaps) expect 16-byte aligned // memory, thus the result must be 16-byte aligned. func align(ptr uintptr) uintptr { return (ptr + 15) &^ 15 } func getCurrentStackPointer() uintptr { return uintptr(stacksave()) } ================================================ FILE: src/runtime/arch_arm.go ================================================ //go:build (arm && !baremetal && !tinygo.wasm) || (arm && arm7tdmi) package runtime const GOARCH = "arm" // The bitness of the CPU (e.g. 8, 32, 64). const TargetBits = 32 const deferExtraRegs = 0 const callInstSize = 4 // "bl someFunction" is 4 bytes const ( linux_MAP_ANONYMOUS = 0x20 linux_SIGBUS = 7 linux_SIGILL = 4 linux_SIGSEGV = 11 ) // Align on the maximum alignment for this platform (double). func align(ptr uintptr) uintptr { return (ptr + 7) &^ 7 } func getCurrentStackPointer() uintptr { return uintptr(stacksave()) } ================================================ FILE: src/runtime/arch_arm64.go ================================================ package runtime const GOARCH = "arm64" // The bitness of the CPU (e.g. 8, 32, 64). const TargetBits = 64 const deferExtraRegs = 0 const callInstSize = 4 // "bl someFunction" is 4 bytes const ( linux_MAP_ANONYMOUS = 0x20 linux_SIGBUS = 7 linux_SIGILL = 4 linux_SIGSEGV = 11 ) // Align on word boundary. func align(ptr uintptr) uintptr { return (ptr + 15) &^ 15 } func getCurrentStackPointer() uintptr { return uintptr(stacksave()) } ================================================ FILE: src/runtime/arch_avr.go ================================================ //go:build avr package runtime import "runtime/interrupt" const GOARCH = "arm" // avr pretends to be arm // The bitness of the CPU (e.g. 8, 32, 64). const TargetBits = 8 const deferExtraRegs = 1 // the frame pointer (Y register) also needs to be stored const callInstSize = 2 // "call" is 4 bytes, "rcall" is 2 bytes // Align on a word boundary. func align(ptr uintptr) uintptr { // No alignment necessary on the AVR. return ptr } func getCurrentStackPointer() uintptr { return uintptr(stacksave()) } // The safest thing to do here would just be to disable interrupts for // procPin/procUnpin. Note that a global variable is safe in this case, as any // access to procPinnedMask will happen with interrupts disabled. var procPinnedMask interrupt.State //go:linkname procPin sync/atomic.runtime_procPin func procPin() { procPinnedMask = interrupt.Disable() } //go:linkname procUnpin sync/atomic.runtime_procUnpin func procUnpin() { interrupt.Restore(procPinnedMask) } // The following functions are workarounds for things missing in compiler-rt. // They will likely need special assembly implementations. // They are treated specially: they're added to @llvm.compiler.used so that the // linker won't eliminate them. //export __mulsi3 func __mulsi3(a, b uint32) uint32 { var r uint32 for a != 0 { if a&1 != 0 { r += b } a >>= 1 b <<= 1 } return r } //export __divsi3 func __divsi3(a, b int32) int32 //export __udivsi3 func __udivsi3(a, b uint32) uint32 //export __divmodsi4 func __divmodsi4(a, b int32) uint64 { d := __divsi3(a, b) rem := a - (d * b) return uint64(uint32(d)) | uint64(uint32(rem))<<32 } //export __udivmodsi4 func __udivmodsi4(a, b uint32) uint64 { d := __udivsi3(a, b) rem := a - (d * b) return uint64(d) | uint64(rem)<<32 } ================================================ FILE: src/runtime/arch_cortexm.go ================================================ //go:build cortexm package runtime import ( "device/arm" ) const GOARCH = "arm" // The bitness of the CPU (e.g. 8, 32, 64). const TargetBits = 32 const deferExtraRegs = 0 const callInstSize = 4 // "bl someFunction" is 4 bytes // Align on word boundary. func align(ptr uintptr) uintptr { return (ptr + 7) &^ 7 } func getCurrentStackPointer() uintptr { return uintptr(stacksave()) } // The safest thing to do here would just be to disable interrupts for // procPin/procUnpin. Note that a global variable is safe in this case, as any // access to procPinnedMask will happen with interrupts disabled. var procPinnedMask uintptr //go:linkname procPin sync/atomic.runtime_procPin func procPin() { procPinnedMask = arm.DisableInterrupts() } //go:linkname procUnpin sync/atomic.runtime_procUnpin func procUnpin() { arm.EnableInterrupts(procPinnedMask) } ================================================ FILE: src/runtime/arch_mips.go ================================================ package runtime const GOARCH = "mips" // The bitness of the CPU (e.g. 8, 32, 64). const TargetBits = 32 const deferExtraRegs = 0 const callInstSize = 8 // "jal someFunc" is 4 bytes, plus a MIPS delay slot const ( linux_MAP_ANONYMOUS = 0x800 linux_SIGBUS = 10 linux_SIGILL = 4 linux_SIGSEGV = 11 ) // It appears that MIPS has a maximum alignment of 8 bytes. func align(ptr uintptr) uintptr { return (ptr + 7) &^ 7 } func getCurrentStackPointer() uintptr { return uintptr(stacksave()) } ================================================ FILE: src/runtime/arch_mipsle.go ================================================ package runtime const GOARCH = "mipsle" // The bitness of the CPU (e.g. 8, 32, 64). const TargetBits = 32 const deferExtraRegs = 0 const callInstSize = 8 // "jal someFunc" is 4 bytes, plus a MIPS delay slot const ( linux_MAP_ANONYMOUS = 0x800 linux_SIGBUS = 10 linux_SIGILL = 4 linux_SIGSEGV = 11 ) // It appears that MIPS has a maximum alignment of 8 bytes. func align(ptr uintptr) uintptr { return (ptr + 7) &^ 7 } func getCurrentStackPointer() uintptr { return uintptr(stacksave()) } ================================================ FILE: src/runtime/arch_tinygoriscv.go ================================================ //go:build tinygo.riscv package runtime import "device/riscv" const deferExtraRegs = 0 const callInstSize = 4 // 8 without relaxation, maybe 4 with relaxation // RISC-V has a maximum alignment of 16 bytes (both for RV32 and for RV64). // Source: https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf func align(ptr uintptr) uintptr { return (ptr + 15) &^ 15 } func getCurrentStackPointer() uintptr { return uintptr(stacksave()) } // The safest thing to do here would just be to disable interrupts for // procPin/procUnpin. Note that a global variable is safe in this case, as any // access to procPinnedMask will happen with interrupts disabled. var procPinnedMask uintptr //go:linkname procPin sync/atomic.runtime_procPin func procPin() { procPinnedMask = riscv.DisableInterrupts() } //go:linkname procUnpin sync/atomic.runtime_procUnpin func procUnpin() { riscv.EnableInterrupts(procPinnedMask) } func waitForEvents() { mask := riscv.DisableInterrupts() if runqueue := schedulerRunQueue(); runqueue == nil || !runqueue.Empty() { riscv.Asm("wfi") } riscv.EnableInterrupts(mask) } ================================================ FILE: src/runtime/arch_tinygoriscv32.go ================================================ //go:build tinygo.riscv32 package runtime const GOARCH = "arm" // riscv pretends to be arm // The bitness of the CPU (e.g. 8, 32, 64). const TargetBits = 32 ================================================ FILE: src/runtime/arch_tinygoriscv64.go ================================================ //go:build tinygo.riscv64 package runtime const GOARCH = "arm64" // riscv pretends to be arm // The bitness of the CPU (e.g. 8, 32, 64). const TargetBits = 64 ================================================ FILE: src/runtime/arch_tinygowasm.go ================================================ //go:build tinygo.wasm package runtime import ( "unsafe" ) const GOARCH = "wasm" // The bitness of the CPU (e.g. 8, 32, 64). const TargetBits = 32 const deferExtraRegs = 0 const callInstSize = 1 // unknown and irrelevant (llvm.returnaddress doesn't work), so make something up //go:extern __heap_base var heapStartSymbol [0]byte //go:extern __global_base var globalsStartSymbol [0]byte const ( // wasmMemoryIndex is always zero until the multi-memory feature is used. // // See https://github.com/WebAssembly/multi-memory wasmMemoryIndex = 0 // wasmPageSize is the size of a page in WebAssembly's 32-bit memory. This // is also its only unit of change. // // See https://www.w3.org/TR/wasm-core-1/#page-size wasmPageSize = 64 * 1024 ) // wasm_memory_size invokes the "memory.size" instruction, which returns the // current size to the memory at the given index (always wasmMemoryIndex), in // pages. // //export llvm.wasm.memory.size.i32 func wasm_memory_size(index int32) int32 // wasm_memory_grow invokes the "memory.grow" instruction, which attempts to // increase the size of the memory at the given index (always wasmMemoryIndex), // by the delta (in pages). This returns the previous size on success of -1 on // failure. // //export llvm.wasm.memory.grow.i32 func wasm_memory_grow(index int32, delta int32) int32 var ( // heapStart is the current memory offset which starts the heap. The heap // extends from this offset until heapEnd (exclusive). heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) // heapEnd is the current memory length in bytes. heapEnd = uintptr(wasm_memory_size(wasmMemoryIndex) * wasmPageSize) globalsStart = uintptr(unsafe.Pointer(&globalsStartSymbol)) globalsEnd = uintptr(unsafe.Pointer(&heapStartSymbol)) stackTop = uintptr(unsafe.Pointer(&globalsStartSymbol)) ) func align(ptr uintptr) uintptr { // Align to 16, which is the alignment of max_align_t: // https://godbolt.org/z/dYqTsWrGq const heapAlign = 16 return (ptr + heapAlign - 1) &^ (heapAlign - 1) } //export tinygo_getCurrentStackPointer func getCurrentStackPointer() uintptr // savedStackPointer is used to save the system stack pointer while in a goroutine. var savedStackPointer uintptr // saveStackPointer is called by internal/task.state.Resume() to save the stack pointer before entering a goroutine from the system stack. func saveStackPointer() { savedStackPointer = getCurrentStackPointer() } // growHeap tries to grow the heap size. It returns true if it succeeds, false // otherwise. func growHeap() bool { // Grow memory by the available size, which means the heap size is doubled. memorySize := wasm_memory_size(wasmMemoryIndex) result := wasm_memory_grow(wasmMemoryIndex, memorySize) if result == -1 { // Grow failed. return false } setHeapEnd(uintptr(wasm_memory_size(wasmMemoryIndex) * wasmPageSize)) // Heap has grown successfully. return true } ================================================ FILE: src/runtime/arch_tinygowasm_malloc.go ================================================ //go:build tinygo.wasm && !(custommalloc || wasm_unknown || gc.boehm) package runtime import "unsafe" // The below functions override the default allocator of wasi-libc. This ensures // code linked from other languages can allocate memory without colliding with // our GC allocations. // Map of allocations, where the key is the allocated pointer and the value is // the size of the allocation. // TODO: make this a map[unsafe.Pointer]uintptr, since that results in slightly // smaller binaries. But for that to work, unsafe.Pointer needs to be seen as a // binary key (which it is not at the moment). // See https://github.com/tinygo-org/tinygo/pull/4898 for details. var allocs = make(map[*byte]uintptr) //export malloc func libc_malloc(size uintptr) unsafe.Pointer { if size == 0 { return nil } ptr := alloc(size, nil) allocs[(*byte)(ptr)] = size return ptr } //export free func libc_free(ptr unsafe.Pointer) { if ptr == nil { return } if _, ok := allocs[(*byte)(ptr)]; ok { delete(allocs, (*byte)(ptr)) } else { panic("free: invalid pointer") } } //export calloc func libc_calloc(nmemb, size uintptr) unsafe.Pointer { // No difference between calloc and malloc. return libc_malloc(nmemb * size) } //export realloc func libc_realloc(oldPtr unsafe.Pointer, size uintptr) unsafe.Pointer { if size == 0 { libc_free(oldPtr) return nil } // It's hard to optimize this to expand the current buffer with our GC, but // it is theoretically possible. For now, just always allocate fresh. // TODO: we could skip this if the new allocation is smaller than the old. ptr := alloc(size, nil) if oldPtr != nil { if oldSize, ok := allocs[(*byte)(oldPtr)]; ok { oldBuf := unsafe.Slice((*byte)(oldPtr), oldSize) newBuf := unsafe.Slice((*byte)(ptr), size) copy(newBuf, oldBuf) delete(allocs, (*byte)(oldPtr)) } else { panic("realloc: invalid pointer") } } allocs[(*byte)(ptr)] = size return ptr } ================================================ FILE: src/runtime/arch_xtensa.go ================================================ //go:build xtensa package runtime const GOARCH = "arm" // xtensa pretends to be arm // The bitness of the CPU (e.g. 8, 32, 64). const TargetBits = 32 const deferExtraRegs = 0 const callInstSize = 3 // "callx0 someFunction" (and similar) is 3 bytes // The largest alignment according to the Xtensa ABI is 8 (long long, double). func align(ptr uintptr) uintptr { return (ptr + 7) &^ 7 } func getCurrentStackPointer() uintptr { return uintptr(stacksave()) } ================================================ FILE: src/runtime/asm_386.S ================================================ #ifdef _WIN32 .global _tinygo_scanCurrentStack _tinygo_scanCurrentStack: #else .section .text.tinygo_scanCurrentStack .global tinygo_scanCurrentStack .type tinygo_scanCurrentStack, %function tinygo_scanCurrentStack: #endif // Sources: // * https://stackoverflow.com/questions/18024672/what-registers-are-preserved-through-a-linux-x86-64-function-call // * https://godbolt.org/z/q7e8dn // Save callee-saved registers. pushl %ebx pushl %esi pushl %edi pushl %ebp // Scan the stack. subl $8, %esp // adjust the stack before the call to maintain 16-byte alignment pushl %esp #ifdef _WIN32 calll _tinygo_scanstack #else calll tinygo_scanstack #endif // Restore the stack pointer. Registers do not need to be restored as they // were only pushed to be discoverable by the GC. addl $28, %esp retl #ifdef _WIN32 .global _tinygo_longjmp _tinygo_longjmp: #else .section .text.tinygo_longjmp .global tinygo_longjmp tinygo_longjmp: #endif // Note: the code we jump to assumes eax is set to a non-zero value if we // jump from here. movl 4(%esp), %eax movl 0(%eax), %esp // jumpSP movl 4(%eax), %eax // jumpPC (stash in volatile register) jmpl *%eax ================================================ FILE: src/runtime/asm_amd64.S ================================================ #ifdef __ELF__ .section .text.tinygo_scanCurrentStack .global tinygo_scanCurrentStack tinygo_scanCurrentStack: #else // Darwin .global _tinygo_scanCurrentStack _tinygo_scanCurrentStack: #endif // Save callee-saved registers. pushq %rbx pushq %rbp pushq %r12 pushq %r13 pushq %r14 pushq %r15 // Scan the stack. subq $8, %rsp // adjust the stack before the call to maintain 16-byte alignment movq %rsp, %rdi #ifdef __ELF__ callq tinygo_scanstack #else callq _tinygo_scanstack // Darwin #endif // Restore the stack pointer. Registers do not need to be restored as they // were only pushed to be discoverable by the GC. addq $56, %rsp retq #ifdef __ELF__ .section .text.tinygo_longjmp .global tinygo_longjmp tinygo_longjmp: #else // Darwin .global _tinygo_longjmp _tinygo_longjmp: #endif // Note: the code we jump to assumes rax is set to a non-zero value if we // jump from here, so we use rax as the temporary value for jumpPC. movq 0(%rdi), %rsp // jumpSP movq 8(%rdi), %rax // jumpPC jmpq *%rax #ifdef __MACH__ // Darwin // allow these symbols to stripped as dead code .subsections_via_symbols #endif ================================================ FILE: src/runtime/asm_amd64_windows.S ================================================ .section .text.tinygo_scanCurrentStack,"ax" .global tinygo_scanCurrentStack tinygo_scanCurrentStack: // Save callee-saved registers. pushq %rbx pushq %rbp pushq %rdi pushq %rsi pushq %r12 pushq %r13 pushq %r14 pushq %r15 // Scan the stack. subq $8, %rsp // adjust the stack before the call to maintain 16-byte alignment movq %rsp, %rcx // pass the stack pointer as the first parameter callq tinygo_scanstack // Restore the stack pointer. Registers do not need to be restored as they // were only pushed to be discoverable by the GC. addq $72, %rsp retq .section .text.tinygo_longjmp,"ax" .global tinygo_longjmp tinygo_longjmp: // Note: the code we jump to assumes rax is set to a non-zero value if we // jump from here, so we use rax as the temporary value for jumpPC. movq 0(%rcx), %rsp // jumpSP movq 8(%rcx), %rax // jumpPC jmpq *%rax ================================================ FILE: src/runtime/asm_arm.S ================================================ // Only generate .debug_frame, don't generate .eh_frame. .cfi_sections .debug_frame .section .text.tinygo_scanCurrentStack .global tinygo_scanCurrentStack .type tinygo_scanCurrentStack, %function tinygo_scanCurrentStack: .cfi_startproc // Save callee-saved registers onto the stack. #if defined(__thumb2__) push {r4-r11, lr} .cfi_def_cfa_offset 9*4 #else mov r0, r8 mov r1, r9 mov r2, r10 mov r3, r11 push {r0-r3, lr} .cfi_def_cfa_offset 5*4 push {r4-r7} .cfi_def_cfa_offset 4*4 #endif // Scan the stack. mov r0, sp bl tinygo_scanstack // Restore stack state and return. add sp, #32 .cfi_def_cfa_offset 1*4 pop {pc} .cfi_endproc .size tinygo_scanCurrentStack, .-tinygo_scanCurrentStack .section .text.tinygo_longjmp .global tinygo_longjmp .type tinygo_longjmp, %function tinygo_longjmp: .cfi_startproc // Note: the code we jump to assumes r0 is set to a non-zero value if we // jump from here (which is conveniently already the case). ldm r0, {r0, r1} mov sp, r0 // jumpSP mov pc, r1 // jumpPC .cfi_endproc .size tinygo_longjmp, .-tinygo_longjmp ================================================ FILE: src/runtime/asm_arm64.S ================================================ #ifdef __MACH__ .global _tinygo_scanCurrentStack _tinygo_scanCurrentStack: #else .global tinygo_scanCurrentStack tinygo_scanCurrentStack: #endif // Sources: // * https://developer.arm.com/architectures/learn-the-architecture/armv8-a-instruction-set-architecture/procedure-call-standard // * https://godbolt.org/z/qrvrEh // Save callee-saved registers. stp x29, x30, [sp, #-160]! stp x28, x27, [sp, #16] stp x26, x25, [sp, #32] stp x24, x23, [sp, #48] stp x22, x21, [sp, #64] stp x20, x19, [sp, #80] stp d8, d9, [sp, #96] stp d10, d11, [sp, #112] stp d12, d13, [sp, #128] stp d14, d15, [sp, #144] // Scan the stack. mov x0, sp #ifdef __MACH__ bl _tinygo_scanstack #else bl tinygo_scanstack #endif // Restore stack state and return. ldp x29, x30, [sp], #160 ret #ifdef __MACH__ .global _tinygo_longjmp _tinygo_longjmp: #else .global tinygo_longjmp tinygo_longjmp: #endif // Note: the code we jump to assumes x0 is set to a non-zero value if we // jump from here (which is conveniently already the case). ldp x1, x2, [x0] // jumpSP, jumpPC mov sp, x1 br x2 ================================================ FILE: src/runtime/asm_avr.S ================================================ .section .text.tinygo_scanCurrentStack .global tinygo_scanCurrentStack .type tinygo_scanCurrentStack, %function tinygo_scanCurrentStack: // Save callee-saved registers. push r29 // Y push r28 // Y push r17 push r16 push r15 push r14 push r13 push r12 push r11 push r10 push r9 push r8 push r7 push r6 push r5 push r4 push r3 push r2 // Scan the stack. in r24, 0x3d; SPL in r25, 0x3e; SPH #if __AVR_ARCH__ == 2 || __AVR_ARCH__ == 25 rcall tinygo_scanstack #else call tinygo_scanstack #endif // Restore callee-saved registers. pop r2 pop r3 pop r4 pop r5 pop r6 pop r7 pop r8 pop r9 pop r10 pop r11 pop r12 pop r13 pop r14 pop r15 pop r16 pop r17 pop r28 // Y pop r29 // Y .section .text.tinygo_longjmp .global tinygo_longjmp tinygo_longjmp: ; Move the *DeferFrame pointer to the X register. mov r26, r24 mov r27, r25 ; Load the stack pointer ld r24, X+ ld r25, X+ ; Switch to the given stack pointer. in r0, 0x3f ; SREG cli ; disable interrupts out 0x3d, r24 ; SPL out 0x3f, r0 ; re-enable interrupts (with one instruction delay) out 0x3e, r25 ; SPH ; Load the new PC ld r30, X+ ld r31, X+ ; Load the new Y register ld r28, X+ ld r29, X+ ; Mark that we returned from a longjmp. ldi r24, 1 ; Jump to the PC (stored in the Z register) icall ================================================ FILE: src/runtime/asm_mipsx.S ================================================ // Do not reorder instructions to insert a branch delay slot. // We know what we're doing, and will manually fill the branch delay slot. .set noreorder .section .text.tinygo_scanCurrentStack .global tinygo_scanCurrentStack .type tinygo_scanCurrentStack, %function tinygo_scanCurrentStack: // Push callee-saved registers onto the stack. addiu $sp, $sp, -40 sw $ra, 36($sp) sw $s8, 32($sp) sw $s7, 28($sp) sw $s6, 24($sp) sw $s5, 20($sp) sw $s4, 16($sp) sw $s3, 12($sp) sw $s2, 8($sp) sw $s1, 4($sp) sw $s0, ($sp) // Scan the stack. jal tinygo_scanstack move $a0, $sp // in the branch delay slot // Restore return address. lw $ra, 36($sp) // Restore stack state. addiu $sp, $sp, 40 // Return to the caller. jalr $ra nop .section .text.tinygo_longjmp .global tinygo_longjmp tinygo_longjmp: // Note: the code we jump to assumes a0 is non-zero, which is already the // case because that's the defer frame pointer. lw $sp, 0($a0) // jumpSP lw $a1, 4($a0) // jumpPC jr $a1 nop ================================================ FILE: src/runtime/asm_riscv.S ================================================ #if __riscv_xlen==64 #define REGSIZE 8 #define SREG sd #define LREG ld #else #define REGSIZE 4 #define SREG sw #define LREG lw #endif .section .text.tinygo_scanCurrentStack .global tinygo_scanCurrentStack .type tinygo_scanCurrentStack, %function tinygo_scanCurrentStack: // Push callee-saved registers onto the stack. addi sp, sp, -13*REGSIZE SREG ra, 0*REGSIZE(sp) SREG s11, 1*REGSIZE(sp) SREG s10, 2*REGSIZE(sp) SREG s9, 3*REGSIZE(sp) SREG s8, 4*REGSIZE(sp) SREG s7, 5*REGSIZE(sp) SREG s6, 6*REGSIZE(sp) SREG s5, 7*REGSIZE(sp) SREG s4, 8*REGSIZE(sp) SREG s3, 9*REGSIZE(sp) SREG s2, 10*REGSIZE(sp) SREG s1, 11*REGSIZE(sp) SREG s0, 12*REGSIZE(sp) // Scan the stack. mv a0, sp call tinygo_scanstack // Restore return address. LREG ra, 0(sp) // Restore stack state. addi sp, sp, 13*REGSIZE // Return to the caller. ret .section .text.tinygo_longjmp .global tinygo_longjmp tinygo_longjmp: // Note: the code we jump to assumes a0 is non-zero, which is already the // case because that's the defer frame pointer. lw sp, 0(a0) // jumpSP lw a1, REGSIZE(a0) // jumpPC jr a1 .section .text.tinygo_checkpointJump .global tinygo_checkpointJump tinygo_checkpointJump: // Note: the code we jump to assumes a0 is non-zero, which is already the // case because that's the stack pointer. mv sp, a0 // jumpSP csrw mepc, a1 // update MEPC value, so we resume there after the mret mret // jump to jumpPC ================================================ FILE: src/runtime/asm_tinygowasm.S ================================================ .globaltype __stack_pointer, i32 .global tinygo_getCurrentStackPointer .hidden tinygo_getCurrentStackPointer .type tinygo_getCurrentStackPointer,@function tinygo_getCurrentStackPointer: // func getCurrentStackPointer() uintptr .functype tinygo_getCurrentStackPointer() -> (i32) global.get __stack_pointer return end_function ================================================ FILE: src/runtime/atomics_critical.go ================================================ //go:build baremetal && !tinygo.wasm // Automatically generated file. DO NOT EDIT. // This file implements standins for non-native atomics using critical sections. package runtime import ( _ "unsafe" ) // Documentation: // * https://llvm.org/docs/Atomics.html // * https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html // // Some atomic operations are emitted inline while others are emitted as libcalls. // How many are emitted as libcalls depends on the MCU arch and core variant. // 16-bit atomics. //export __atomic_load_2 func __atomic_load_2(ptr *uint16, ordering uintptr) uint16 { // The LLVM docs for this say that there is a val argument after the pointer. // That is a typo, and the GCC docs omit it. mask := lockAtomics() val := *ptr unlockAtomics(mask) return val } //export __atomic_store_2 func __atomic_store_2(ptr *uint16, val uint16, ordering uintptr) { mask := lockAtomics() *ptr = val unlockAtomics(mask) } //go:inline func doAtomicCAS16(ptr *uint16, expected, desired uint16) uint16 { mask := lockAtomics() old := *ptr if old == expected { *ptr = desired } unlockAtomics(mask) return old } //export __sync_val_compare_and_swap_2 func __sync_val_compare_and_swap_2(ptr *uint16, expected, desired uint16) uint16 { return doAtomicCAS16(ptr, expected, desired) } //export __atomic_compare_exchange_2 func __atomic_compare_exchange_2(ptr, expected *uint16, desired uint16, successOrder, failureOrder uintptr) bool { exp := *expected old := doAtomicCAS16(ptr, exp, desired) return old == exp } //go:inline func doAtomicSwap16(ptr *uint16, new uint16) uint16 { mask := lockAtomics() old := *ptr *ptr = new unlockAtomics(mask) return old } //export __sync_lock_test_and_set_2 func __sync_lock_test_and_set_2(ptr *uint16, new uint16) uint16 { return doAtomicSwap16(ptr, new) } //export __atomic_exchange_2 func __atomic_exchange_2(ptr *uint16, new uint16, ordering uintptr) uint16 { return doAtomicSwap16(ptr, new) } //go:inline func doAtomicAdd16(ptr *uint16, value uint16) (old, new uint16) { mask := lockAtomics() old = *ptr new = old + value *ptr = new unlockAtomics(mask) return old, new } //export __atomic_fetch_add_2 func __atomic_fetch_add_2(ptr *uint16, value uint16, ordering uintptr) uint16 { old, _ := doAtomicAdd16(ptr, value) return old } //export __sync_fetch_and_add_2 func __sync_fetch_and_add_2(ptr *uint16, value uint16) uint16 { old, _ := doAtomicAdd16(ptr, value) return old } //export __atomic_add_fetch_2 func __atomic_add_fetch_2(ptr *uint16, value uint16, ordering uintptr) uint16 { _, new := doAtomicAdd16(ptr, value) return new } // 32-bit atomics. //export __atomic_load_4 func __atomic_load_4(ptr *uint32, ordering uintptr) uint32 { // The LLVM docs for this say that there is a val argument after the pointer. // That is a typo, and the GCC docs omit it. mask := lockAtomics() val := *ptr unlockAtomics(mask) return val } //export __atomic_store_4 func __atomic_store_4(ptr *uint32, val uint32, ordering uintptr) { mask := lockAtomics() *ptr = val unlockAtomics(mask) } //go:inline func doAtomicCAS32(ptr *uint32, expected, desired uint32) uint32 { mask := lockAtomics() old := *ptr if old == expected { *ptr = desired } unlockAtomics(mask) return old } //export __sync_val_compare_and_swap_4 func __sync_val_compare_and_swap_4(ptr *uint32, expected, desired uint32) uint32 { return doAtomicCAS32(ptr, expected, desired) } //export __atomic_compare_exchange_4 func __atomic_compare_exchange_4(ptr, expected *uint32, desired uint32, successOrder, failureOrder uintptr) bool { exp := *expected old := doAtomicCAS32(ptr, exp, desired) return old == exp } //go:inline func doAtomicSwap32(ptr *uint32, new uint32) uint32 { mask := lockAtomics() old := *ptr *ptr = new unlockAtomics(mask) return old } //export __sync_lock_test_and_set_4 func __sync_lock_test_and_set_4(ptr *uint32, new uint32) uint32 { return doAtomicSwap32(ptr, new) } //export __atomic_exchange_4 func __atomic_exchange_4(ptr *uint32, new uint32, ordering uintptr) uint32 { return doAtomicSwap32(ptr, new) } //go:inline func doAtomicAdd32(ptr *uint32, value uint32) (old, new uint32) { mask := lockAtomics() old = *ptr new = old + value *ptr = new unlockAtomics(mask) return old, new } //export __atomic_fetch_add_4 func __atomic_fetch_add_4(ptr *uint32, value uint32, ordering uintptr) uint32 { old, _ := doAtomicAdd32(ptr, value) return old } //export __sync_fetch_and_add_4 func __sync_fetch_and_add_4(ptr *uint32, value uint32) uint32 { old, _ := doAtomicAdd32(ptr, value) return old } //export __atomic_add_fetch_4 func __atomic_add_fetch_4(ptr *uint32, value uint32, ordering uintptr) uint32 { _, new := doAtomicAdd32(ptr, value) return new } // 64-bit atomics. //export __atomic_load_8 func __atomic_load_8(ptr *uint64, ordering uintptr) uint64 { // The LLVM docs for this say that there is a val argument after the pointer. // That is a typo, and the GCC docs omit it. mask := lockAtomics() val := *ptr unlockAtomics(mask) return val } //export __atomic_store_8 func __atomic_store_8(ptr *uint64, val uint64, ordering uintptr) { mask := lockAtomics() *ptr = val unlockAtomics(mask) } //go:inline func doAtomicCAS64(ptr *uint64, expected, desired uint64) uint64 { mask := lockAtomics() old := *ptr if old == expected { *ptr = desired } unlockAtomics(mask) return old } //export __sync_val_compare_and_swap_8 func __sync_val_compare_and_swap_8(ptr *uint64, expected, desired uint64) uint64 { return doAtomicCAS64(ptr, expected, desired) } //export __atomic_compare_exchange_8 func __atomic_compare_exchange_8(ptr, expected *uint64, desired uint64, successOrder, failureOrder uintptr) bool { exp := *expected old := doAtomicCAS64(ptr, exp, desired) return old == exp } //go:inline func doAtomicSwap64(ptr *uint64, new uint64) uint64 { mask := lockAtomics() old := *ptr *ptr = new unlockAtomics(mask) return old } //export __sync_lock_test_and_set_8 func __sync_lock_test_and_set_8(ptr *uint64, new uint64) uint64 { return doAtomicSwap64(ptr, new) } //export __atomic_exchange_8 func __atomic_exchange_8(ptr *uint64, new uint64, ordering uintptr) uint64 { return doAtomicSwap64(ptr, new) } //go:inline func doAtomicAdd64(ptr *uint64, value uint64) (old, new uint64) { mask := lockAtomics() old = *ptr new = old + value *ptr = new unlockAtomics(mask) return old, new } //export __atomic_fetch_add_8 func __atomic_fetch_add_8(ptr *uint64, value uint64, ordering uintptr) uint64 { old, _ := doAtomicAdd64(ptr, value) return old } //export __sync_fetch_and_add_8 func __sync_fetch_and_add_8(ptr *uint64, value uint64) uint64 { old, _ := doAtomicAdd64(ptr, value) return old } //export __atomic_add_fetch_8 func __atomic_add_fetch_8(ptr *uint64, value uint64, ordering uintptr) uint64 { _, new := doAtomicAdd64(ptr, value) return new } ================================================ FILE: src/runtime/baremetal.go ================================================ //go:build baremetal package runtime import ( "unsafe" ) //go:extern _heap_start var heapStartSymbol [0]byte //go:extern _heap_end var heapEndSymbol [0]byte //go:extern _globals_start var globalsStartSymbol [0]byte //go:extern _globals_end var globalsEndSymbol [0]byte //go:extern _stack_top var stackTopSymbol [0]byte var ( heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) heapEnd = uintptr(unsafe.Pointer(&heapEndSymbol)) globalsStart = uintptr(unsafe.Pointer(&globalsStartSymbol)) globalsEnd = uintptr(unsafe.Pointer(&globalsEndSymbol)) stackTop = uintptr(unsafe.Pointer(&stackTopSymbol)) ) // growHeap tries to grow the heap size. It returns true if it succeeds, false // otherwise. func growHeap() bool { // On baremetal, there is no way the heap can be grown. return false } //export malloc func libc_malloc(size uintptr) unsafe.Pointer { // Note: this zeroes the returned buffer which is not necessary. // The same goes for bytealg.MakeNoZero. return alloc(size, nil) } //export calloc func libc_calloc(nmemb, size uintptr) unsafe.Pointer { // No difference between calloc and malloc. return libc_malloc(nmemb * size) } //export free func libc_free(ptr unsafe.Pointer) { free(ptr) } //export runtime_putchar func runtime_putchar(c byte) { putchar(c) } //go:linkname syscall_Exit syscall.Exit func syscall_Exit(code int) { exit(code) } const baremetal = true // timeOffset is how long the monotonic clock started after the Unix epoch. It // should be a positive integer under normal operation or zero when it has not // been set. var timeOffset int64 //go:linkname now time.now func now() (sec int64, nsec int32, mono int64) { mono = nanotime() sec = (mono + timeOffset) / (1000 * 1000 * 1000) nsec = int32((mono + timeOffset) - sec*(1000*1000*1000)) return } // AdjustTimeOffset adds the given offset to the built-in time offset. A // positive value adds to the time (skipping some time), a negative value moves // the clock into the past. func AdjustTimeOffset(offset int64) { // TODO: do this atomically? timeOffset += offset } // Picolibc is not configured to define its own errno value, instead it calls // __errno_location. // TODO: a global works well enough for now (same as errno on Linux with // -scheduler=tasks), but this should ideally be a thread-local variable stored // in task.Task. // Especially when we add multicore support for microcontrollers. var errno int32 //export __errno_location func libc_errno_location() *int32 { return &errno } ================================================ FILE: src/runtime/build_asserts.go ================================================ //go:build runtime_asserts package runtime // enable assertions for the garbage collector const gcAsserts = true // enable asserts for the scheduler const schedulerAsserts = true ================================================ FILE: src/runtime/build_noasserts.go ================================================ //go:build !runtime_asserts package runtime // disable assertions for the garbage collector const gcAsserts = false // disable assertions for the scheduler const schedulerAsserts = false ================================================ FILE: src/runtime/cgo/cgo.go ================================================ package cgo // dummy ================================================ FILE: src/runtime/chan.go ================================================ package runtime // This file implements the 'chan' type and send/receive/select operations. // // Every channel has a list of senders and a list of receivers, and possibly a // queue. There is no 'channel state', the state is inferred from the available // senders/receivers and values in the buffer. // // - A sender will first try to send the value to a waiting receiver if there is // one, but only if there is nothing in the queue (to keep the values flowing // in the correct order). If it can't, it will add the value in the queue and // possibly wait as a sender if there's no space available. // - A receiver will first try to read a value from the queue, but if there is // none it will try to read from a sender in the list. It will block if it // can't proceed. // // State is kept in various ways: // // - The sender value is stored in the sender 'channelOp', which is really a // queue entry. This works for both senders and select operations: a select // operation has a separate value to send for each case. // - The receiver value is stored inside Task.Ptr. This works for receivers, and // importantly also works for select which has a single buffer for every // receive operation. // - The `Task.Data` value stores how the channel operation proceeded. For // normal send/receive operations, it starts at chanOperationWaiting and then // is changed to chanOperationOk or chanOperationClosed depending on whether // the send/receive proceeded normally or because it was closed. For a select // operation, it also stores the 'case' index in the upper bits (zero for // non-select operations) so that the select operation knows which case did // proceed. // The value is at the same time also a way that goroutines can be the first // (and only) goroutine to 'take' a channel operation using an atomic CAS // operation to change it from 'waiting' to any other value. This is important // for the select statement because multiple goroutines could try to let // different channels in the select statement proceed at the same time. By // using Task.Data, only a single channel operation in the select statement // can proceed. // - It is possible for the channel queues to contain already-processed senders // or receivers. This can happen when the select statement managed to proceed // but the goroutine doing the select has not yet cleaned up the stale queue // entries before returning. This should therefore only happen for a short // period. import ( "internal/task" "runtime/interrupt" "unsafe" ) // The runtime implementation of the Go 'chan' type. type channel struct { closed bool selectLocked bool elementSize uintptr bufCap uintptr // 'cap' bufLen uintptr // 'len' bufHead uintptr bufTail uintptr senders chanQueue receivers chanQueue lock task.PMutex buf unsafe.Pointer } const ( chanOperationWaiting = 0b00 // waiting for a send/receive operation to continue chanOperationOk = 0b01 // successfully sent or received (not closed) chanOperationClosed = 0b10 // channel was closed, the value has been zeroed chanOperationMask = 0b11 ) type chanQueue struct { first *channelOp } // Pus the next channel operation to the queue. All appropriate fields must have // been initialized already. // This function must be called with interrupts disabled and the channel lock // held. func (q *chanQueue) push(node *channelOp) { node.next = q.first q.first = node } // Pop the next waiting channel from the queue. Channels that are no longer // waiting (for example, when they're part of a select operation) will be // skipped. // This function must be called with interrupts disabled. func (q *chanQueue) pop(chanOp uint32) *channelOp { for { if q.first == nil { return nil } // Pop next from the queue. popped := q.first q.first = q.first.next // The new value for the 'data' field will be a combination of the // channel operation and the select index. (The select index is 0 for // non-select channel operations). newDataValue := chanOp | popped.index<<2 // Try to be the first to proceed with this goroutine. swapped := popped.task.DataAtomicUint32().CompareAndSwap(0, newDataValue) if swapped { return popped } } } // Remove the given to-be-removed node from the queue if it is part of the // queue. If there are multiple, only one will be removed. // This function must be called with interrupts disabled and the channel lock // held. func (q *chanQueue) remove(remove *channelOp) { n := &q.first for *n != nil { if *n == remove { *n = (*n).next return } n = &((*n).next) } } type channelOp struct { next *channelOp task *task.Task index uint32 // select index, 0 for non-select operation value unsafe.Pointer // if this is a sender, this is the value to send } type chanSelectState struct { ch *channel value unsafe.Pointer } func chanMake(elementSize uintptr, bufSize uintptr) *channel { return &channel{ elementSize: elementSize, bufCap: bufSize, buf: alloc(elementSize*bufSize, nil), } } // Return the number of entries in this chan, called from the len builtin. // A nil chan is defined as having length 0. func chanLen(c *channel) int { if c == nil { return 0 } return int(c.bufLen) } // Return the capacity of this chan, called from the cap builtin. // A nil chan is defined as having capacity 0. func chanCap(c *channel) int { if c == nil { return 0 } return int(c.bufCap) } // Push the value to the channel buffer array, for a send operation. // This function may only be called when interrupts are disabled, the channel is // locked and it is known there is space available in the buffer. func (ch *channel) bufferPush(value unsafe.Pointer) { elemAddr := unsafe.Add(ch.buf, ch.bufHead*ch.elementSize) ch.bufLen++ ch.bufHead++ if ch.bufHead == ch.bufCap { ch.bufHead = 0 } memcpy(elemAddr, value, ch.elementSize) } // Pop a value from the channel buffer and store it in the 'value' pointer, for // a receive operation. // This function may only be called when interrupts are disabled, the channel is // locked and it is known there is at least one value available in the buffer. func (ch *channel) bufferPop(value unsafe.Pointer) { elemAddr := unsafe.Add(ch.buf, ch.bufTail*ch.elementSize) ch.bufLen-- ch.bufTail++ if ch.bufTail == ch.bufCap { ch.bufTail = 0 } memcpy(value, elemAddr, ch.elementSize) // Zero the value to allow the GC to collect it. memzero(elemAddr, ch.elementSize) } // Try to proceed with this send operation without blocking, and return whether // the send succeeded. Interrupts must be disabled and the lock must be held // when calling this function. func (ch *channel) trySend(value unsafe.Pointer) bool { // To make sure we send values in the correct order, we can only send // directly to a receiver when there are no values in the buffer. // Do not allow sending on a closed channel. if ch.closed { // Note: we cannot currently recover from this panic. // There's some state in the select statement especially that would be // corrupted if we allowed recovering from this panic. runtimePanic("send on closed channel") } // There is no value in the buffer and we have a receiver available. Copy // the value directly into the receiver. if ch.bufLen == 0 { if receiver := ch.receivers.pop(chanOperationOk); receiver != nil { memcpy(receiver.task.Ptr, value, ch.elementSize) scheduleTask(receiver.task) return true } } // If there is space in the buffer (if this is a buffered channel), we can // store the value in the buffer and continue. if ch.bufLen < ch.bufCap { ch.bufferPush(value) return true } return false } func chanSend(ch *channel, value unsafe.Pointer, op *channelOp) { if ch == nil { // A nil channel blocks forever. Do not schedule this goroutine again. deadlock() } mask := interrupt.Disable() ch.lock.Lock() // See whether we can proceed immediately, and if so, return early. if ch.trySend(value) { ch.lock.Unlock() interrupt.Restore(mask) return } // Can't proceed. Add us to the list of senders and wait until we're awoken. t := task.Current() t.SetDataUint32(chanOperationWaiting) op.task = t op.index = 0 op.value = value ch.senders.push(op) ch.lock.Unlock() interrupt.Restore(mask) // Wait until this goroutine is resumed. // It might be resumed after Unlock() and before Pause(). In that case, // because we use semaphores, the Pause() will continue immediately. task.Pause() // Check whether the sent happened normally (not because the channel was // closed while sending). if t.DataUint32() == chanOperationClosed { // Oops, this channel was closed while sending! runtimePanic("send on closed channel") } } // Try to proceed with this receive operation without blocking, and return // whether the receive operation succeeded. Interrupts must be disabled and the // lock must be held when calling this function. func (ch *channel) tryRecv(value unsafe.Pointer) (received, ok bool) { // To make sure we keep the values in the channel in the correct order, we // first have to read values from the buffer before we can look at the // senders. // If there is a value available in the buffer, we can pull it out and // proceed immediately. if ch.bufLen > 0 { ch.bufferPop(value) // Check for the next sender available and push it to the buffer. if sender := ch.senders.pop(chanOperationOk); sender != nil { ch.bufferPush(sender.value) scheduleTask(sender.task) } return true, true } if ch.closed { // Channel is closed, so proceed immediately. memzero(value, ch.elementSize) return true, false } // If there is a sender, we can proceed with the channel operation // immediately. if sender := ch.senders.pop(chanOperationOk); sender != nil { memcpy(value, sender.value, ch.elementSize) scheduleTask(sender.task) return true, true } return false, false } func chanRecv(ch *channel, value unsafe.Pointer, op *channelOp) bool { if ch == nil { // A nil channel blocks forever. Do not schedule this goroutine again. deadlock() } mask := interrupt.Disable() ch.lock.Lock() if received, ok := ch.tryRecv(value); received { ch.lock.Unlock() interrupt.Restore(mask) return ok } // We can't proceed, so we add ourselves to the list of receivers and wait // until we're awoken. t := task.Current() t.Ptr = value t.SetDataUint32(chanOperationWaiting) op.task = t op.index = 0 ch.receivers.push(op) ch.lock.Unlock() interrupt.Restore(mask) // Wait until the goroutine is resumed. task.Pause() // Return whether the receive happened from a closed channel. return t.DataUint32() != chanOperationClosed } // chanClose closes the given channel. If this channel has a receiver or is // empty, it closes the channel. Else, it panics. func chanClose(ch *channel) { if ch == nil { // Not allowed by the language spec. runtimePanic("close of nil channel") } mask := interrupt.Disable() ch.lock.Lock() if ch.closed { // Not allowed by the language spec. ch.lock.Unlock() interrupt.Restore(mask) runtimePanic("close of closed channel") } // Proceed all receiving operations that are blocked. for { receiver := ch.receivers.pop(chanOperationClosed) if receiver == nil { // Processed all receivers. break } // Zero the value that the receiver is getting. memzero(receiver.task.Ptr, ch.elementSize) // Wake up the receiving goroutine. scheduleTask(receiver.task) } // Let all senders panic. for { sender := ch.senders.pop(chanOperationClosed) if sender == nil { break // processed all senders } // Wake up the sender. scheduleTask(sender.task) } ch.closed = true ch.lock.Unlock() interrupt.Restore(mask) } // We currently use a global select lock to avoid deadlocks while locking each // individual channel in the select. Without this global lock, two select // operations that have a different order of the same channels could end up in a // deadlock. This global lock is inefficient if there are many select operations // happening in parallel, but gets the job done. // // If this becomes a performance issue, we can see how the Go runtime does this. // I think it does this by sorting all states by channel address and then // locking them in that order to avoid this deadlock. var chanSelectLock task.PMutex // Lock all channels (taking care to skip duplicate channels). func lockAllStates(states []chanSelectState) { if !hasParallelism { return } for _, state := range states { if state.ch != nil && !state.ch.selectLocked { state.ch.lock.Lock() state.ch.selectLocked = true } } } // Unlock all channels (taking care to skip duplicate channels). func unlockAllStates(states []chanSelectState) { if !hasParallelism { return } for _, state := range states { if state.ch != nil && state.ch.selectLocked { state.ch.lock.Unlock() state.ch.selectLocked = false } } } // chanSelect implements blocking or non-blocking select operations. // The 'ops' slice must be set if (and only if) this is a blocking select. func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelOp) (uint32, bool) { mask := interrupt.Disable() // Lock everything. chanSelectLock.Lock() lockAllStates(states) const selectNoIndex = ^uint32(0) selectIndex := selectNoIndex selectOk := true // Iterate over each state, and see if it can proceed. // TODO: start from a random index. for i, state := range states { if state.ch == nil { // A nil channel blocks forever, so it won't take part of the select // operation. continue } if state.value == nil { // chan receive if received, ok := state.ch.tryRecv(recvbuf); received { selectIndex = uint32(i) selectOk = ok break } } else { // chan send if state.ch.trySend(state.value) { selectIndex = uint32(i) break } } } // If this select can immediately proceed, or is a non-blocking select, // return early. blocking := len(ops) != 0 if selectIndex != selectNoIndex || !blocking { unlockAllStates(states) chanSelectLock.Unlock() interrupt.Restore(mask) return selectIndex, selectOk } // The select is blocking and no channel operation can proceed, so things // become more complicated. // We add ourselves as a sender/receiver to every channel, and wait for the // first one to complete. Only one will successfully complete, because // senders and receivers use a compare-and-exchange atomic operation on // t.Data so that only one will be able to "take" this select operation. t := task.Current() t.Ptr = recvbuf t.SetDataUint32(chanOperationWaiting) for i, state := range states { if state.ch == nil { continue } op := &ops[i] op.task = t op.index = uint32(i) if state.value == nil { // chan receive state.ch.receivers.push(op) } else { // chan send op.value = state.value state.ch.senders.push(op) } } // Now we wait until one of the send/receive operations can proceed. unlockAllStates(states) chanSelectLock.Unlock() interrupt.Restore(mask) task.Pause() // Resumed, so one channel operation must have progressed. // Make sure all channel ops are removed from the senders/receivers // queue before we return and the memory of them becomes invalid. chanSelectLock.Lock() lockAllStates(states) for i, state := range states { if state.ch == nil { continue } op := &ops[i] mask := interrupt.Disable() if state.value == nil { state.ch.receivers.remove(op) } else { state.ch.senders.remove(op) } interrupt.Restore(mask) } unlockAllStates(states) chanSelectLock.Unlock() // Pull the return values out of t.Data (which contains two bitfields). selectIndex = t.DataUint32() >> 2 selectOk = t.DataUint32()&chanOperationMask != chanOperationClosed return selectIndex, selectOk } ================================================ FILE: src/runtime/complex.go ================================================ // Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package runtime // inf2one returns a signed 1 if f is an infinity and a signed 0 otherwise. // The sign of the result is the sign of f. func inf2one(f float64) float64 { g := 0.0 if isInf(f) { g = 1.0 } return copysign(g, f) } func complex64div(n complex64, m complex64) complex64 { return complex64(complex128div(complex128(n), complex128(m))) } func complex128div(n complex128, m complex128) complex128 { var e, f float64 // complex(e, f) = n/m // Algorithm for robust complex division as described in // Robert L. Smith: Algorithm 116: Complex division. Commun. ACM 5(8): 435 (1962). if abs(real(m)) >= abs(imag(m)) { ratio := imag(m) / real(m) denom := real(m) + ratio*imag(m) e = (real(n) + imag(n)*ratio) / denom f = (imag(n) - real(n)*ratio) / denom } else { ratio := real(m) / imag(m) denom := imag(m) + ratio*real(m) e = (real(n)*ratio + imag(n)) / denom f = (imag(n)*ratio - real(n)) / denom } if isNaN(e) && isNaN(f) { // Correct final result to infinities and zeros if applicable. // Matches C99: ISO/IEC 9899:1999 - G.5.1 Multiplicative operators. a, b := real(n), imag(n) c, d := real(m), imag(m) switch { case m == 0 && (!isNaN(a) || !isNaN(b)): e = copysign(inf, c) * a f = copysign(inf, c) * b case (isInf(a) || isInf(b)) && isFinite(c) && isFinite(d): a = inf2one(a) b = inf2one(b) e = inf * (a*c + b*d) f = inf * (b*c - a*d) case (isInf(c) || isInf(d)) && isFinite(a) && isFinite(b): c = inf2one(c) d = inf2one(d) e = 0 * (a*c + b*d) f = 0 * (b*c - a*d) } } return complex(e, f) } ================================================ FILE: src/runtime/coro.go ================================================ package runtime // A naive implementation of coroutines that supports // package iter. type coro struct { f func(*coro) ch chan struct{} } //go:linkname newcoro func newcoro(f func(*coro)) *coro { c := &coro{ ch: make(chan struct{}), f: f, } go func() { defer close(c.ch) <-c.ch f(c) }() return c } //go:linkname coroswitch func coroswitch(c *coro) { c.ch <- struct{}{} <-c.ch } ================================================ FILE: src/runtime/debug/debug.go ================================================ // Portions copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package debug is a very partially implemented package to allow compilation. package debug import ( "fmt" "runtime" "strconv" "strings" ) // SetMaxStack sets the maximum amount of memory that can be used by a single // goroutine stack. // // Not implemented. func SetMaxStack(n int) int { return n } // PrintStack prints to standard error the stack trace returned by runtime.Stack. // // Not implemented. func PrintStack() {} // Stack returns a formatted stack trace of the goroutine that calls it. // // Not implemented. func Stack() []byte { return nil } // ReadBuildInfo returns the build information embedded // in the running binary. The information is available only // in binaries built with module support. // // Not implemented. func ReadBuildInfo() (info *BuildInfo, ok bool) { return &BuildInfo{GoVersion: runtime.Compiler + runtime.Version()}, true } // BuildInfo represents the build information read from // the running binary. type BuildInfo struct { GoVersion string // version of the Go toolchain that built the binary, e.g. "go1.19.2" Path string // The main package path Main Module // The module containing the main package Deps []*Module // Module dependencies Settings []BuildSetting } type BuildSetting struct { // Key and Value describe the build setting. // Key must not contain an equals sign, space, tab, or newline. // Value must not contain newlines ('\n'). Key, Value string } // Module represents a module. type Module struct { Path string // module path Version string // module version Sum string // checksum Replace *Module // replaced by this module } // Not implemented. func SetGCPercent(n int) int { return n } // Start of stolen from big go. TODO: import/reuse without copy pasta. // quoteKey reports whether key is required to be quoted. func quoteKey(key string) bool { return len(key) == 0 || strings.ContainsAny(key, "= \t\r\n\"`") } // quoteValue reports whether value is required to be quoted. func quoteValue(value string) bool { return strings.ContainsAny(value, " \t\r\n\"`") } func (bi *BuildInfo) String() string { buf := new(strings.Builder) if bi.GoVersion != "" { fmt.Fprintf(buf, "go\t%s\n", bi.GoVersion) } if bi.Path != "" { fmt.Fprintf(buf, "path\t%s\n", bi.Path) } var formatMod func(string, Module) formatMod = func(word string, m Module) { buf.WriteString(word) buf.WriteByte('\t') buf.WriteString(m.Path) buf.WriteByte('\t') buf.WriteString(m.Version) if m.Replace == nil { buf.WriteByte('\t') buf.WriteString(m.Sum) } else { buf.WriteByte('\n') formatMod("=>", *m.Replace) } buf.WriteByte('\n') } if bi.Main != (Module{}) { formatMod("mod", bi.Main) } for _, dep := range bi.Deps { formatMod("dep", *dep) } for _, s := range bi.Settings { key := s.Key if quoteKey(key) { key = strconv.Quote(key) } value := s.Value if quoteValue(value) { value = strconv.Quote(value) } fmt.Fprintf(buf, "build\t%s=%s\n", key, value) } return buf.String() } ================================================ FILE: src/runtime/debug/garbage.go ================================================ // Package debug is a very partially implemented package to allow compilation. package debug import ( "time" ) type GCStats struct { LastGC time.Time NumGC int64 PauseTotal time.Duration Pause []time.Duration PauseEnd []time.Time PauseQuantiles []time.Duration } func ReadGCStats(stats *GCStats) { } func FreeOSMemory() { } func SetMaxThreads(threads int) int { return threads } func SetPanicOnFault(enabled bool) bool { return enabled } func WriteHeapDump(fd uintptr) func SetTraceback(level string) func SetMemoryLimit(limit int64) int64 { return limit } ================================================ FILE: src/runtime/debug.go ================================================ package runtime // Stub for NumCgoCall, does not return the real value func NumCgoCall() int { return 0 } // Stub for NumGoroutine, does not return the real value func NumGoroutine() int { return 1 } // Stub for Breakpoint, does not do anything. func Breakpoint() { panic("Breakpoint not supported") } ================================================ FILE: src/runtime/defer.go ================================================ package runtime // Some helper types for the defer statement. // See compiler/defer.go for details. type _defer struct { callback uintptr // callback number next *_defer } ================================================ FILE: src/runtime/dynamic_arm64.go ================================================ package runtime import ( "unsafe" ) const debugLoader = false const ( rAARCH64_RELATIVE = 1027 dtNULL = 0 /* Terminating entry. */ dtRELA = 7 /* Address of ElfNN_Rela relocations. */ dtRELASZ = 8 /* Total size of ElfNN_Rela relocations. */ ) /* ELF64 relocations that need an addend field. */ type rela64 struct { Off uint64 /* Location to be relocated. */ Info uint64 /* Relocation type and symbol index. */ Addend int64 /* Addend. */ } // ELF64 Dynamic structure. The ".dynamic" section contains an array of them. type dyn64 struct { Tag int64 /* Entry type. */ Val uint64 /* Integer/address value */ } //export __dynamic_loader func dynamicLoader(base uintptr, dyn *dyn64) { var rela *rela64 relasz := uint64(0) if debugLoader { println("ASLR Base: ", base) } for dyn.Tag != dtNULL { switch dyn.Tag { case dtRELA: rela = (*rela64)(unsafe.Pointer(base + uintptr(dyn.Val))) case dtRELASZ: relasz = uint64(dyn.Val) / uint64(unsafe.Sizeof(rela64{})) } ptr := unsafe.Pointer(dyn) ptr = unsafe.Add(ptr, unsafe.Sizeof(dyn64{})) dyn = (*dyn64)(ptr) } if rela == nil { runtimePanic("bad reloc") } if debugLoader { println("Sections to load: ", relasz) } for relasz > 0 && rela != nil { switch rela.Info { case rAARCH64_RELATIVE: if debugLoader { println("relocating ", uintptr(rela.Addend), " to ", base+uintptr(rela.Addend)) } ptr := (*uint64)(unsafe.Pointer(base + uintptr(rela.Off))) *ptr = uint64(base + uintptr(rela.Addend)) default: if debugLoader { println("unknown section to load:", rela.Info) } } rptr := unsafe.Pointer(rela) rptr = unsafe.Add(rptr, unsafe.Sizeof(rela64{})) rela = (*rela64)(rptr) relasz-- } } ================================================ FILE: src/runtime/env.go ================================================ //go:build linux || darwin || windows || wasip1 package runtime // Update the C environment if cgo is loaded. // Called from Go 1.20 and above. // //go:linkname syscallSetenv syscall.runtimeSetenv func syscallSetenv(key, value string) { keydata := cstring(key) valdata := cstring(value) setenv(&keydata[0], &valdata[0]) if key == "GODEBUG" && godebugUpdate != nil { // Starting with Go 1.20, we need to call a callback (set by // internal/godebug) to notify the GODEBUG environment variable has // changed. This is necessary to get archive/zip to pass tests. godebugUpdate(key, value) } } // Update the C environment if cgo is loaded. // Called from Go 1.20 and above. // //go:linkname syscallUnsetenv syscall.runtimeUnsetenv func syscallUnsetenv(key string) { keydata := cstring(key) unsetenv(&keydata[0]) } // Compatibility with Go 1.19 and below. // //go:linkname syscall_setenv_c syscall.setenv_c func syscall_setenv_c(key string, val string) { syscallSetenv(key, val) } // Compatibility with Go 1.19 and below. // //go:linkname syscall_unsetenv_c syscall.unsetenv_c func syscall_unsetenv_c(key string) { syscallUnsetenv(key) } // cstring converts a Go string to a C string. // borrowed from syscall func cstring(s string) []byte { data := make([]byte, len(s)+1) copy(data, s) // final byte should be zero from the initial allocation return data } ================================================ FILE: src/runtime/env_unix.go ================================================ //go:build linux || darwin || wasip1 package runtime // int setenv(const char *name, const char *val, int replace); // //export setenv func libc_setenv(name *byte, val *byte, replace int32) int32 // int unsetenv(const char *name); // //export unsetenv func libc_unsetenv(name *byte) int32 func setenv(key, val *byte) { // ignore any errors libc_setenv(key, val, 1) } func unsetenv(key *byte) { // ignore any errors libc_unsetenv(key) } ================================================ FILE: src/runtime/env_windows.go ================================================ //go:build windows package runtime // Set environment variable in Windows: // // BOOL SetEnvironmentVariableA( // [in] LPCSTR lpName, // [in, optional] LPCSTR lpValue // ); // //export SetEnvironmentVariableA func _SetEnvironmentVariableA(key, val *byte) bool func setenv(key, val *byte) { // ignore any errors _SetEnvironmentVariableA(key, val) } func unsetenv(key *byte) { // ignore any errors _SetEnvironmentVariableA(key, nil) } ================================================ FILE: src/runtime/error.go ================================================ package runtime // The Error interface identifies a run time error. type Error interface { error RuntimeError() } ================================================ FILE: src/runtime/extern.go ================================================ package runtime func Callers(skip int, pc []uintptr) int { return 0 } // buildVersion is the Tinygo tree's version string at build time. // // This is set by the linker. var buildVersion string // Version returns the Tinygo tree's version string. // It is the same as goenv.Version(). func Version() string { return buildVersion } ================================================ FILE: src/runtime/float.go ================================================ // Copyright 2017 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package runtime import "unsafe" var inf = float64frombits(0x7FF0000000000000) // isNaN reports whether f is an IEEE 754 “not-a-number” value. func isNaN(f float64) (is bool) { // IEEE 754 says that only NaNs satisfy f != f. return f != f } // isFinite reports whether f is neither NaN nor an infinity. func isFinite(f float64) bool { return !isNaN(f - f) } // isInf reports whether f is an infinity. func isInf(f float64) bool { return !isNaN(f) && !isFinite(f) } // Abs returns the absolute value of x. // // Special cases are: // // Abs(±Inf) = +Inf // Abs(NaN) = NaN func abs(x float64) float64 { const sign = 1 << 63 return float64frombits(float64bits(x) &^ sign) } // copysign returns a value with the magnitude // of x and the sign of y. func copysign(x, y float64) float64 { const sign = 1 << 63 return float64frombits(float64bits(x)&^sign | float64bits(y)&sign) } // Float64bits returns the IEEE 754 binary representation of f. func float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) } // Float64frombits returns the floating point number corresponding // the IEEE 754 binary representation b. func float64frombits(b uint64) float64 { return *(*float64)(unsafe.Pointer(&b)) } ================================================ FILE: src/runtime/gc_blocks.go ================================================ //go:build gc.conservative || gc.precise package runtime // This memory manager is a textbook mark/sweep implementation, heavily inspired // by the MicroPython garbage collector. // // The memory manager internally uses blocks of 4 pointers big (see // bytesPerBlock). Every allocation first rounds up to this size to align every // block. It will first try to find a chain of blocks that is big enough to // satisfy the allocation. If it finds one, it marks the first one as the "head" // and the following ones (if any) as the "tail" (see below). If it cannot find // any free space, it will perform a garbage collection cycle and try again. If // it still cannot find any free space, it gives up. // // Every block has some metadata, which is stored at the end of the heap. // The four states are "free", "head", "tail", and "mark". During normal // operation, there are no marked blocks. Every allocated object starts with a // "head" and is followed by "tail" blocks. The reason for this distinction is // that this way, the start and end of every object can be found easily. // // Metadata is stored in a special area at the end of the heap, in the area // metadataStart..heapEnd. The actual blocks are stored in // heapStart..metadataStart. // // More information: // https://aykevl.nl/2020/09/gc-tinygo // https://github.com/micropython/micropython/wiki/Memory-Manager // https://github.com/micropython/micropython/blob/master/py/gc.c // "The Garbage Collection Handbook" by Richard Jones, Antony Hosking, Eliot // Moss. import ( "internal/task" "runtime/interrupt" "unsafe" ) const gcDebug = false const needsStaticHeap = true // Some globals + constants for the entire GC. const ( wordsPerBlock = 4 // number of pointers in an allocated block bytesPerBlock = wordsPerBlock * unsafe.Sizeof(heapStart) stateBits = 2 // how many bits a block state takes (see blockState type) blocksPerStateByte = 8 / stateBits ) var ( metadataStart unsafe.Pointer // pointer to the start of the heap metadata scanList *objHeader // scanList is a singly linked list of heap objects that have been marked but not scanned freeRanges *freeRange // freeRanges is a linked list of free block ranges endBlock gcBlock // the block just past the end of the available space gcTotalAlloc uint64 // total number of bytes allocated gcMallocs uint64 // total number of allocations gcLock task.PMutex // lock to avoid race conditions on multicore systems ) // zeroSizedAlloc is just a sentinel that gets returned when allocating 0 bytes. var zeroSizedAlloc uint8 // Provide some abstraction over heap blocks. // blockState stores the four states in which a block can be. // It holds 1 bit in each nibble. // When stored into a state byte, each bit in a nibble corresponds to a different block. // For blocks A-D, a state byte would be laid out as 0bDCBA_DCBA. type blockState uint8 const ( blockStateLow blockState = 1 blockStateHigh blockState = 1 << blocksPerStateByte blockStateFree blockState = 0 blockStateHead blockState = blockStateLow blockStateTail blockState = blockStateHigh blockStateMark blockState = blockStateLow | blockStateHigh blockStateMask blockState = blockStateLow | blockStateHigh ) // blockStateEach is a mask that can be used to extract a nibble from the block state. const blockStateEach = 1<= uintptr(metadataStart)) { runtimePanic("gc: trying to get block from invalid address") } return gcBlock((addr - heapStart) / bytesPerBlock) } // Return a pointer to the start of the allocated object. func (b gcBlock) pointer() unsafe.Pointer { return unsafe.Pointer(b.address()) } // Return the address of the start of the allocated object. func (b gcBlock) address() uintptr { addr := heapStart + uintptr(b)*bytesPerBlock if gcAsserts && addr > uintptr(metadataStart) { runtimePanic("gc: block pointing inside metadata") } return addr } // findHead returns the head (first block) of an object, assuming the block // points to an allocated object. It returns the same block if this block // already points to the head. func (b gcBlock) findHead() gcBlock { for { // Optimization: check whether the current block state byte (which // contains the state of multiple blocks) is composed entirely of tail // blocks. If so, we can skip back to the last block in the previous // state byte. // This optimization speeds up findHead for pointers that point into a // large allocation. stateByte := b.stateByte() if stateByte == blockStateByteAllTails { b -= (b % blocksPerStateByte) + 1 continue } // Check whether we've found a non-tail block, which means we found the // head. state := b.stateFromByte(stateByte) if state != blockStateTail { break } b-- } if gcAsserts { if b.state() != blockStateHead && b.state() != blockStateMark { runtimePanic("gc: found tail without head") } } return b } // findNext returns the first block just past the end of the tail. This may or // may not be the head of an object. func (b gcBlock) findNext() gcBlock { if b.state() == blockStateHead || b.state() == blockStateMark { b++ } for b.address() < uintptr(metadataStart) && b.state() == blockStateTail { b++ } return b } func (b gcBlock) stateByte() byte { return *(*uint8)(unsafe.Add(metadataStart, b/blocksPerStateByte)) } // Return the block state given a state byte. The state byte must have been // obtained using b.stateByte(), otherwise the result is incorrect. func (b gcBlock) stateFromByte(stateByte byte) blockState { return blockState(stateByte>>(b%blocksPerStateByte)) & blockStateMask } // State returns the current block state. func (b gcBlock) state() blockState { return b.stateFromByte(b.stateByte()) } // setState sets the current block to the given state, which must contain more // bits than the current state. Allowed transitions: from free to any state and // from head to mark. func (b gcBlock) setState(newState blockState) { stateBytePtr := (*uint8)(unsafe.Add(metadataStart, b/blocksPerStateByte)) *stateBytePtr |= uint8(newState << (b % blocksPerStateByte)) if gcAsserts && b.state() != newState { runtimePanic("gc: setState() was not successful") } } // objHeader is a structure prepended to every heap object to hold metadata. type objHeader struct { // next is the next object to scan after this. next *objHeader // layout holds the layout bitmap used to find pointers in the object. layout gcLayout } // freeRange is a node on the outer list of range lengths. // The free ranges are structured as two nested singly-linked lists: // - The outer level (freeRange) has one entry for each unique range length. // - The inner level (freeRangeMore) has one entry for each additional range of the same length. // This two-level structure ensures that insertion/removal times are proportional to the requested length. type freeRange struct { // len is the length of this free range. len uintptr // nextLen is the next longer free range. nextLen *freeRange // nextWithLen is the next free range with this length. nextWithLen *freeRangeMore } // freeRangeMore is a node on the inner list of equal-length ranges. type freeRangeMore struct { next *freeRangeMore } // insertFreeRange inserts a range of len blocks starting at ptr into the free list. func insertFreeRange(ptr unsafe.Pointer, len uintptr) { if gcAsserts && len == 0 { runtimePanic("gc: insert 0-length free range") } // Find the insertion point by length. // Skip until the next range is at least the target length. insDst := &freeRanges for *insDst != nil && (*insDst).len < len { insDst = &(*insDst).nextLen } // Create the new free range. next := *insDst if next != nil && next.len == len { // Insert into the list with this length. newRange := (*freeRangeMore)(ptr) newRange.next = next.nextWithLen next.nextWithLen = newRange } else { // Insert into the list of lengths. newRange := (*freeRange)(ptr) *newRange = freeRange{ len: len, nextLen: next, nextWithLen: nil, } *insDst = newRange } } // popFreeRange removes a range of len blocks from the freeRanges list. // It returns nil if there are no sufficiently long ranges. func popFreeRange(len uintptr) unsafe.Pointer { if gcAsserts && len == 0 { runtimePanic("gc: pop 0-length free range") } // Find the removal point by length. // Skip until the next range is at least the target length. remDst := &freeRanges for *remDst != nil && (*remDst).len < len { remDst = &(*remDst).nextLen } rangeWithLength := *remDst if rangeWithLength == nil { // No ranges are long enough. return nil } removedLen := rangeWithLength.len // Remove the range. var ptr unsafe.Pointer if nextWithLen := rangeWithLength.nextWithLen; nextWithLen != nil { // Remove from the list with this length. rangeWithLength.nextWithLen = nextWithLen.next ptr = unsafe.Pointer(nextWithLen) } else { // Remove from the list of lengths. *remDst = rangeWithLength.nextLen ptr = unsafe.Pointer(rangeWithLength) } if removedLen > len { // Insert the leftover range. insertFreeRange(unsafe.Add(ptr, len*bytesPerBlock), removedLen-len) } return ptr } func isOnHeap(ptr uintptr) bool { return ptr >= heapStart && ptr < uintptr(metadataStart) } // Initialize the memory allocator. // No memory may be allocated before this is called. That means the runtime and // any packages the runtime depends upon may not allocate memory during package // initialization. func initHeap() { calculateHeapAddresses() // Set all block states to 'free'. metadataSize := heapEnd - uintptr(metadataStart) memzero(unsafe.Pointer(metadataStart), metadataSize) // Rebuild the free ranges list. buildFreeRanges() } // setHeapEnd is called to expand the heap. The heap can only grow, not shrink. // Also, the heap should grow substantially each time otherwise growing the heap // will be expensive. func setHeapEnd(newHeapEnd uintptr) { if gcAsserts && newHeapEnd <= heapEnd { runtimePanic("gc: setHeapEnd didn't grow the heap") } // Save some old variables we need later. oldMetadataStart := metadataStart oldMetadataSize := heapEnd - uintptr(metadataStart) // Increase the heap. After setting the new heapEnd, calculateHeapAddresses // will update metadataStart and the memcpy will copy the metadata to the // new location. // The new metadata will be bigger than the old metadata, but a simple // memcpy is fine as it only copies the old metadata and the new memory will // have been zero initialized. heapEnd = newHeapEnd calculateHeapAddresses() memcpy(metadataStart, oldMetadataStart, oldMetadataSize) // Note: the memcpy above assumes the heap grows enough so that the new // metadata does not overlap the old metadata. If that isn't true, memmove // should be used to avoid corruption. // This assert checks whether that's true. if gcAsserts && uintptr(metadataStart) < uintptr(oldMetadataStart)+oldMetadataSize { runtimePanic("gc: heap did not grow enough at once") } // Rebuild the free ranges list. buildFreeRanges() } // calculateHeapAddresses initializes variables such as metadataStart and // numBlock based on heapStart and heapEnd. // // This function can be called again when the heap size increases. The caller is // responsible for copying the metadata to the new location. func calculateHeapAddresses() { totalSize := heapEnd - heapStart // Allocate some memory to keep 2 bits of information about every block. metadataSize := (totalSize + blocksPerStateByte*bytesPerBlock) / (1 + blocksPerStateByte*bytesPerBlock) metadataStart = unsafe.Pointer(heapEnd - metadataSize) // Use the rest of the available memory as heap. numBlocks := (uintptr(metadataStart) - heapStart) / bytesPerBlock endBlock = gcBlock(numBlocks) if gcDebug { println("heapStart: ", heapStart) println("heapEnd: ", heapEnd) println("total size: ", totalSize) println("metadata size: ", metadataSize) println("metadataStart: ", metadataStart) println("# of blocks: ", numBlocks) println("# of block states:", metadataSize*blocksPerStateByte) } if gcAsserts && metadataSize*blocksPerStateByte < numBlocks { // sanity check runtimePanic("gc: metadata array is too small") } } // alloc tries to find some free space on the heap, possibly doing a garbage // collection cycle if needed. If no space is free, it panics. // //go:noinline func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { if size == 0 { return unsafe.Pointer(&zeroSizedAlloc) } if interrupt.In() { runtimePanicAt(returnAddress(0), "heap alloc in interrupt") } // Round the size up to a multiple of blocks, adding space for the header. rawSize := size size += align(unsafe.Sizeof(objHeader{})) size += bytesPerBlock - 1 if size < rawSize { // The size overflowed. runtimePanicAt(returnAddress(0), "out of memory") } neededBlocks := size / bytesPerBlock size = neededBlocks * bytesPerBlock // Make sure there are no concurrent allocations. The heap is not currently // designed for concurrent alloc/GC. gcLock.Lock() // Update the total allocation counters. gcTotalAlloc += uint64(rawSize) gcMallocs++ // Acquire a range of free blocks. var ranGC bool var grewHeap bool var pointer unsafe.Pointer for { pointer = popFreeRange(neededBlocks) if pointer != nil { break } if !ranGC { // Run the collector and try again. freeBytes := runGC() ranGC = true heapSize := uintptr(metadataStart) - heapStart if freeBytes < heapSize/3 { // Ensure there is at least 33% headroom. // This percentage was arbitrarily chosen, and may need to // be tuned in the future. growHeap() } continue } if gcDebug && !grewHeap { println("grow heap for request:", uint(neededBlocks)) dumpFreeRangeCounts() } if growHeap() { grewHeap = true continue } // Unfortunately the heap could not be increased. This // happens on baremetal systems for example (where all // available RAM has already been dedicated to the heap). runtimePanicAt(returnAddress(0), "out of memory") } // Set the backing blocks as being allocated. block := blockFromAddr(uintptr(pointer)) block.setState(blockStateHead) for i := block + 1; i != block+gcBlock(neededBlocks); i++ { i.setState(blockStateTail) } // Create the object header. header := (*objHeader)(pointer) header.layout = parseGCLayout(layout) // We've claimed this allocation, now we can unlock the heap. gcLock.Unlock() // Return a pointer to this allocation. add := align(unsafe.Sizeof(objHeader{})) pointer = unsafe.Add(pointer, add) size -= add memzero(pointer, size) return pointer } func realloc(ptr unsafe.Pointer, size uintptr) unsafe.Pointer { if ptr == nil { return alloc(size, nil) } ptrAddress := uintptr(ptr) endOfTailAddress := blockFromAddr(ptrAddress).findNext().address() // this might be a few bytes longer than the original size of // ptr, because we align to full blocks of size bytesPerBlock oldSize := endOfTailAddress - ptrAddress if size <= oldSize { return ptr } newAlloc := alloc(size, nil) memcpy(newAlloc, ptr, oldSize) free(ptr) return newAlloc } func free(ptr unsafe.Pointer) { // TODO: free blocks on request, when the compiler knows they're unused. } // GC performs a garbage collection cycle. func GC() { gcLock.Lock() runGC() gcLock.Unlock() } // runGC performs a garbage collection cycle. It is the internal implementation // of the runtime.GC() function. The difference is that it returns the number of // free bytes in the heap after the GC is finished. func runGC() (freeBytes uintptr) { if gcDebug { println("running collection cycle...") } // Mark phase: mark all reachable objects, recursively. gcMarkReachable() if baremetal && hasScheduler { // Channel operations in interrupts may move task pointers around while we are marking. // Therefore we need to scan the runqueue separately. var markedTaskQueue task.Queue runqueueScan: runqueue := schedulerRunQueue() for !runqueue.Empty() { // Pop the next task off of the runqueue. t := runqueue.Pop() // Mark the task if it has not already been marked. markRoot(uintptr(unsafe.Pointer(runqueue)), uintptr(unsafe.Pointer(t))) // Push the task onto our temporary queue. markedTaskQueue.Push(t) } finishMark() // Restore the runqueue. i := interrupt.Disable() if !runqueue.Empty() { // Something new came in while finishing the mark. interrupt.Restore(i) goto runqueueScan } *runqueue = markedTaskQueue interrupt.Restore(i) } else { finishMark() } // If we're using threads, resume all other threads before starting the // sweep. gcResumeWorld() // Sweep phase: free all non-marked objects and unmark marked objects for // the next collection cycle. sweep() // Rebuild the free ranges list. freeBytes = buildFreeRanges() // Show how much has been sweeped, for debugging. if gcDebug { dumpHeap() } return } // markRoots reads all pointers from start to end (exclusive) and if they look // like a heap pointer and are unmarked, marks them and scans that object as // well (recursively). The starting address must be valid and aligned. func markRoots(start, end uintptr) { if gcDebug { println("mark from", start, "to", end, int(end-start)) } if gcAsserts { if start >= end { runtimePanic("gc: unexpected range to mark") } if start%unsafe.Alignof(start) != 0 { runtimePanic("gc: unaligned start pointer") } } // Scan the range conservatively. scanConservative(start, end-start) } // scanConservative scans all possible pointer locations in a range and marks referenced heap allocations. // The starting address must be valid and pointer-aligned. func scanConservative(addr, len uintptr) { for len >= unsafe.Sizeof(addr) { root := *(*uintptr)(unsafe.Pointer(addr)) markRoot(addr, root) addr += unsafe.Alignof(addr) len -= unsafe.Alignof(addr) } } func markCurrentGoroutineStack(sp uintptr) { // This could be optimized by only marking the stack area that's currently // in use. markRoot(0, sp) } // finishMark finishes the marking process by scanning all heap objects on scanList. func finishMark() { for { // Remove an object from the scan list. obj := scanList if obj == nil { return } scanList = obj.next // Check if the object may contain pointers. if obj.layout.pointerFree() { // This object doesn't contain any pointers. // This is a fast path for objects like make([]int, 4096). // It skips the length calculation. continue } // Compute the scan bounds. objAddr := uintptr(unsafe.Pointer(obj)) start := objAddr + align(unsafe.Sizeof(objHeader{})) end := blockFromAddr(objAddr).findNext().address() // Scan the object. obj.layout.scan(start, end-start) } } // mark a GC root at the address addr. func markRoot(addr, root uintptr) { // Find the heap block corresponding to the root. if !isOnHeap(root) { // This is not a heap pointer. return } block := blockFromAddr(root) // Find the head of the corresponding object. if block.state() == blockStateFree { // The to-be-marked object doesn't actually exist. // This could either be a dangling pointer (oops!) but most likely // just a false positive. return } head := block.findHead() // Mark the object. if head.state() == blockStateMark { // This object is already marked. return } if gcDebug { println("found unmarked pointer", root, "at address", addr) } head.setState(blockStateMark) // Add the object to the scan list. header := (*objHeader)(head.pointer()) header.next = scanList scanList = header } // Sweep goes through all memory and frees unmarked memory. func sweep() { metadataEnd := unsafe.Add(metadataStart, (endBlock+(blocksPerStateByte-1))/blocksPerStateByte) var carry byte for meta := metadataStart; meta != metadataEnd; meta = unsafe.Add(meta, 1) { // Fetch the state byte. stateBytePtr := (*byte)(unsafe.Pointer(meta)) stateByte := *stateBytePtr // Separate blocks by type. // Split the nibbles. // Each nibble is a mask of blocks. high := stateByte >> blocksPerStateByte low := stateByte & blockStateEach // Marked heads are in both nibbles. markedHeads := low & high // Unmarked heads are in the low nibble but not the high nibble. unmarkedHeads := low &^ high // Tails are in the high nibble but not the low nibble. tails := high &^ low // Clear all tail runs after unmarked (freed) heads. // // Adding 1 to the start of a bit run will clear the run and set the next bit: // (2^k - 1) + 1 = 2^k // e.g. 0b0011 + 1 = 0b0100 // Bitwise-and with the original mask to clear the newly set bit. // e.g. (0b0011 + 1) & 0b0011 = 0b0100 & 0b0011 = 0b0000 // This will not clear bits after the run because the gap stops the carry: // e.g. (0b1011 + 1) & 0b1011 = 0b1100 & 0b1011 = 0b1000 // This can clear multiple runs in a single addition: // e.g. (0b1101 + 0b0101) & 0b1101 = 0b10010 & 0b1101 = 0b0000 // // In order to find tail run starts after unmarked heads we could use tails & (unmarkedHeads << 1). // It is possible omit the bitwise-and because the clear still works if the next block is not a tail. // A head is not a tail, so corresponding missing tail bit will stop the carry from a previous tail run. // As such it will set the next bit which will be cleared back away later. // e.g. HHTH: (0b0010 + (0b1101 << 1)) & 0b0010 = 0b11100 & 0b0010 = 0b0000 // // Treat the whole heap as a single pair of integer masks. // This is accomplished for addition by carrying the overflow to the next state byte. // The unmarkedHeads << 1 is equivalent to unmarkedHeads + unmarkedHeads, so it can be merged with the sum. // This does not require any special work for the bitwise-and because it operates bitwise. tailClear := tails + (unmarkedHeads << 1) + carry carry = tailClear >> blocksPerStateByte tails &= tailClear // Construct the new state byte. *stateBytePtr = markedHeads | (tails << blocksPerStateByte) } } // buildFreeRanges rebuilds the freeRanges list. // This must be called after a GC sweep or heap grow. // It returns how many bytes are free in the heap. func buildFreeRanges() uintptr { freeRanges = nil block := endBlock var totalBlocks uintptr for { // Skip backwards over occupied blocks. for block > 0 && (block-1).state() != blockStateFree { block-- } if block == 0 { break } // Find the start of the free range. end := block for block > 0 && (block-1).state() == blockStateFree { block-- } // Insert the free range. len := uintptr(end - block) totalBlocks += len insertFreeRange(block.pointer(), len) } if gcDebug { println("free ranges after rebuild:") dumpFreeRangeCounts() } return totalBlocks * bytesPerBlock } func dumpFreeRangeCounts() { for rangeWithLength := freeRanges; rangeWithLength != nil; rangeWithLength = rangeWithLength.nextLen { totalRanges := uintptr(1) for nextWithLen := rangeWithLength.nextWithLen; nextWithLen != nil; nextWithLen = nextWithLen.next { totalRanges++ } println("-", uint(rangeWithLength.len), "x", uint(totalRanges)) } } // dumpHeap can be used for debugging purposes. It dumps the state of each heap // block to standard output. func dumpHeap() { println("heap:") for block := gcBlock(0); block < endBlock; block++ { switch block.state() { case blockStateHead: print("*") case blockStateTail: print("-") case blockStateMark: print("#") default: // free print("·") } if block%64 == 63 || block+1 == endBlock { println() } } } // ReadMemStats populates m with memory statistics. // // The returned memory statistics are up to date as of the // call to ReadMemStats. This would not do GC implicitly for you. func ReadMemStats(m *MemStats) { gcLock.Lock() // Calculate the raw size of the heap. heapEnd := heapEnd heapStart := heapStart m.Sys = uint64(heapEnd - heapStart) m.HeapSys = uint64(uintptr(metadataStart) - heapStart) metadataStart := metadataStart // TODO: should GCSys include objHeaders? m.GCSys = uint64(heapEnd - uintptr(metadataStart)) m.HeapReleased = 0 // always 0, we don't currently release memory back to the OS. // Count live heads and tails. var liveHeads, liveTails uintptr endBlock := endBlock metadataEnd := unsafe.Add(metadataStart, (endBlock+(blocksPerStateByte-1))/blocksPerStateByte) for meta := metadataStart; meta != metadataEnd; meta = unsafe.Add(meta, 1) { // Since we are outside of a GC, nothing is marked. // A bit in the low nibble implies a head. // A bit in the high nibble implies a tail. stateByte := *(*byte)(unsafe.Pointer(meta)) liveHeads += uintptr(count4LUT[stateByte&blockStateEach]) liveTails += uintptr(count4LUT[stateByte>>blocksPerStateByte]) } // Add heads and tails to count live blocks. liveBlocks := liveHeads + liveTails liveBytes := uint64(liveBlocks * bytesPerBlock) m.HeapInuse = liveBytes m.HeapAlloc = liveBytes m.Alloc = liveBytes // Subtract live blocks from total blocks to count free blocks. freeBlocks := uintptr(endBlock) - liveBlocks m.HeapIdle = uint64(freeBlocks * bytesPerBlock) // Record the number of allocated objects. gcMallocs := gcMallocs m.Mallocs = gcMallocs // Subtract live objects from allocated objects to count freed objects. m.Frees = gcMallocs - uint64(liveHeads) // Record the total allocated bytes. m.TotalAlloc = gcTotalAlloc gcLock.Unlock() } // count4LUT is a lookup table used to count set bits in a 4-bit mask. // TODO: replace with popcnt when available var count4LUT = [16]uint8{ 0b0000: 0, 0b0001: 1, 0b0010: 1, 0b0011: 2, 0b0100: 1, 0b0101: 2, 0b0110: 2, 0b0111: 3, 0b1000: 1, 0b1001: 2, 0b1010: 2, 0b1011: 3, 0b1100: 2, 0b1101: 3, 0b1110: 3, 0b1111: 4, } func SetFinalizer(obj interface{}, finalizer interface{}) { // Unimplemented. } ================================================ FILE: src/runtime/gc_boehm.c ================================================ //go:build none // This file is included in the build on systems that support the Boehm GC, // despite the //go:build line above. #include typedef void (* GC_push_other_roots_proc)(void); void GC_set_push_other_roots(GC_push_other_roots_proc); typedef void(* GC_warn_proc)(const char *msg, uintptr_t arg); void GC_set_warn_proc(GC_warn_proc p); void tinygo_runtime_bdwgc_callback(void); static void callback(void) { tinygo_runtime_bdwgc_callback(); } static void warn_proc(const char *msg, uintptr_t arg) { } void tinygo_runtime_bdwgc_init(void) { GC_set_push_other_roots(callback); #if defined(__wasm__) // There are a lot of warnings on WebAssembly in the form: // // GC Warning: Repeated allocation of very large block (appr. size 68 KiB): // May lead to memory leak and poor performance // // The usual advice is to use something like GC_malloc_ignore_off_page but // unfortunately for most allocations that's not allowed: Go allocations can // legitimately hold pointers further than one page in the allocation. So // instead we just disable the warning. GC_set_warn_proc(warn_proc); #endif } ================================================ FILE: src/runtime/gc_boehm.go ================================================ //go:build gc.boehm // This is the Boehm-Demers-Weiser conservative garbage collector, integrated // into TinyGo. // // Note that we use a special way of dealing with threads: // * All calls to the bdwgc library are serialized using locks. // * When the bdwgc library wants to push GC roots, all other threads that are // running are stopped. // * After returning from a bdwgc library call, the caller checks whether // other threads were stopped (meaning a GC cycle happened) and resumes the // world. // This is not exactly the most efficient way to do this. We can likely speed // things up by using bdwgc-native wrappers for starting/stopping threads (and // also to resume the world while sweeping). Also, thread local allocation might // help. But we don't do any of these right now, it is left as a possible future // improvement. package runtime import ( "internal/gclayout" "internal/task" "unsafe" ) const needsStaticHeap = false // zeroSizedAlloc is just a sentinel that gets returned when allocating 0 bytes. var zeroSizedAlloc uint8 var gcLock task.PMutex func initHeap() { libgc_init() // Call GC_set_push_other_roots(gcCallback) in C because of function // signature differences that do matter in WebAssembly. gcInit() } //export tinygo_runtime_bdwgc_init func gcInit() //export tinygo_runtime_bdwgc_callback func gcCallback() { // Mark globals and all stacks, and stop the world if we're using threading. gcMarkReachable() } func markRoots(start, end uintptr) { libgc_push_all(start, end) } func markCurrentGoroutineStack(sp uintptr) { // Only mark the area of the stack that is currently in use. // (This doesn't work for other goroutines, but at least it doesn't keep // more pointers alive than needed on the current stack). base := libgc_base(sp) if base == 0 { // && asserts runtimePanic("goroutine stack not in a heap allocation?") } stackBottom := base + libgc_size(base) libgc_push_all_stack(sp, stackBottom) } //go:noinline func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { if size == 0 { return unsafe.Pointer(&zeroSizedAlloc) } gcLock.Lock() var ptr unsafe.Pointer if layout == gclayout.NoPtrs.AsPtr() { // This object is entirely pointer free, for example make([]int, ...). // Make sure the GC knows this so it doesn't scan the object // unnecessarily to improve performance. ptr = libgc_malloc_atomic(size) // Memory returned from libgc_malloc_atomic has not been zeroed so we // have to do that manually. memzero(ptr, size) } else { // TODO: bdwgc supports typed allocations, which could be useful to // implement a mostly-precise GC. ptr = libgc_malloc(size) // Memory returned from libgc_malloc has already been zeroed, so nothing // to do here. } gcResumeWorld() gcLock.Unlock() if ptr == nil { runtimePanic("gc: out of memory") } return ptr } func free(ptr unsafe.Pointer) { libgc_free(ptr) } func GC() { gcLock.Lock() libgc_gcollect() gcResumeWorld() gcLock.Unlock() } // This should be stack-allocated, but we don't currently have a good way of // ensuring that happens. var gcMemStats libgc_prof_stats func ReadMemStats(m *MemStats) { gcLock.Lock() libgc_get_prof_stats(&gcMemStats, unsafe.Sizeof(gcMemStats)) // Fill in MemStats as well as we can, given the information that bdwgc // provides to us. m.HeapIdle = uint64(gcMemStats.free_bytes_full - gcMemStats.unmapped_bytes) m.HeapInuse = uint64(gcMemStats.heapsize_full - gcMemStats.unmapped_bytes) m.HeapReleased = uint64(gcMemStats.unmapped_bytes) m.HeapSys = uint64(m.HeapInuse + m.HeapIdle) m.GCSys = 0 // not provided by bdwgc m.TotalAlloc = uint64(gcMemStats.allocd_bytes_before_gc + gcMemStats.bytes_allocd_since_gc) m.Mallocs = 0 // not provided by bdwgc m.Frees = 0 // not provided by bdwgc m.Sys = uint64(gcMemStats.obtained_from_os_bytes) gcLock.Unlock() } func setHeapEnd(newHeapEnd uintptr) { runtimePanic("gc: did not expect setHeapEnd call") } func SetFinalizer(obj interface{}, finalizer interface{}) { // Unimplemented. // The GC *does* support finalization, so this could be added relatively // easily I think. } //export GC_init func libgc_init() //export GC_malloc func libgc_malloc(uintptr) unsafe.Pointer //export GC_malloc_atomic func libgc_malloc_atomic(uintptr) unsafe.Pointer //export GC_free func libgc_free(unsafe.Pointer) //export GC_base func libgc_base(ptr uintptr) uintptr //export GC_size func libgc_size(ptr uintptr) uintptr //export GC_push_all func libgc_push_all(bottom, top uintptr) //export GC_push_all_stack func libgc_push_all_stack(bottom, top uintptr) //export GC_gcollect func libgc_gcollect() //export GC_get_prof_stats func libgc_get_prof_stats(*libgc_prof_stats, uintptr) uintptr //export GC_set_push_other_roots func libgc_set_push_other_roots(unsafe.Pointer) type libgc_prof_stats struct { heapsize_full uintptr free_bytes_full uintptr unmapped_bytes uintptr bytes_allocd_since_gc uintptr allocd_bytes_before_gc uintptr non_gc_bytes uintptr gc_no uintptr markers_m1 uintptr bytes_reclaimed_since_gc uintptr reclaimed_bytes_before_gc uintptr expl_freed_bytes_since_gc uintptr obtained_from_os_bytes uintptr } ================================================ FILE: src/runtime/gc_conservative.go ================================================ //go:build gc.conservative // This implements the block-based heap as a fully conservative GC. No tracking // of pointers is done, every word in an object is considered live if it looks // like a pointer. package runtime import "unsafe" // parseGCLayout stores the layout information passed to alloc into a gcLayout value. // The conservative GC discards this information. func parseGCLayout(layout unsafe.Pointer) gcLayout { return gcLayout{} } // gcLayout tracks pointer locations in a heap object. // The conservative GC treats all locations as potential pointers, so this doesn't need to store anything. type gcLayout struct { } func (l gcLayout) pointerFree() bool { // We don't know whether this object contains pointers, so conservatively // return false. return false } func (l gcLayout) scan(start, len uintptr) { scanConservative(start, len) } ================================================ FILE: src/runtime/gc_custom.go ================================================ //go:build gc.custom // +build gc.custom package runtime // This GC strategy allows an external GC to be plugged in instead of the builtin // implementations. // // The interface defined in this file is not stable and can be broken at anytime, even // across minor versions. // // runtime.markStack() must be called at the beginning of any GC cycle. //go:linkname // on a function without a body can be used to access this internal function. // // The custom implementation must provide the following functions in the runtime package // using the go:linkname directive: // // - func initHeap() // - func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer // - func free(ptr unsafe.Pointer) // - func markRoots(start, end uintptr) // - func GC() // - func SetFinalizer(obj interface{}, finalizer interface{}) // - func ReadMemStats(ms *runtime.MemStats) // // // In addition, if targeting wasi, the following functions should be exported for interoperability // with wasi libraries that use them. Note, this requires the export directive, not go:linkname. // // - func malloc(size uintptr) unsafe.Pointer // - func free(ptr unsafe.Pointer) // - func calloc(nmemb, size uintptr) unsafe.Pointer // - func realloc(oldPtr unsafe.Pointer, size uintptr) unsafe.Pointer import ( "unsafe" ) const needsStaticHeap = false // initHeap is called when the heap is first initialized at program start. func initHeap() // alloc is called to allocate memory. layout is currently not used. func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer // free is called to explicitly free a previously allocated pointer. func free(ptr unsafe.Pointer) // markRoots is called with the start and end addresses to scan for references. // It is currently only called with the top and bottom of the stack. func markRoots(start, end uintptr) // GC is called to explicitly run garbage collection. func GC() // SetFinalizer registers a finalizer. func SetFinalizer(obj interface{}, finalizer interface{}) // ReadMemStats populates m with memory statistics. func ReadMemStats(ms *MemStats) func setHeapEnd(newHeapEnd uintptr) { // Heap is in custom GC so ignore for when called from wasm initialization. } ================================================ FILE: src/runtime/gc_globals.go ================================================ //go:build baremetal || tinygo.wasm package runtime // This file implements findGlobals for all systems where the start and end of // the globals section can be found through linker-defined symbols. // findGlobals finds all globals (which are reachable by definition) and calls // the callback for them. // // This implementation marks all globals conservatively and assumes it can use // linker-defined symbols for the start and end of the .data section. func findGlobals(found func(start, end uintptr)) { found(globalsStart, globalsEnd) } ================================================ FILE: src/runtime/gc_leaking.go ================================================ //go:build gc.leaking package runtime // This GC implementation is the simplest useful memory allocator possible: it // only allocates memory and never frees it. For some constrained systems, it // may be the only memory allocator possible. import ( "internal/task" "unsafe" ) const needsStaticHeap = true // Ever-incrementing pointer: no memory is freed. var heapptr uintptr // Total amount allocated for runtime.MemStats var gcTotalAlloc uint64 // Total number of calls to alloc() var gcMallocs uint64 // Heap lock for parallel goroutines. No-op when single threaded. var gcLock task.PMutex // Total number of objected freed; for leaking collector this stays 0 const gcFrees = 0 // Inlining alloc() speeds things up slightly but bloats the executable by 50%, // see https://github.com/tinygo-org/tinygo/issues/2674. So don't. // //go:noinline func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { // TODO: this can be optimized by not casting between pointers and ints so // much. And by using platform-native data types (e.g. *uint8 for 8-bit // systems). gcLock.Lock() size = align(size) addr := heapptr gcTotalAlloc += uint64(size) gcMallocs++ heapptr += size for heapptr >= heapEnd { // Try to increase the heap and check again. if growHeap() { continue } // Failed to make the heap bigger, so we must really be out of memory. runtimePanic("out of memory") } gcLock.Unlock() pointer := unsafe.Pointer(addr) zero_new_alloc(pointer, size) return pointer } func realloc(ptr unsafe.Pointer, size uintptr) unsafe.Pointer { newAlloc := alloc(size, nil) if ptr == nil { return newAlloc } // according to POSIX everything beyond the previous pointer's // size will have indeterminate values so we can just copy garbage memcpy(newAlloc, ptr, size) return newAlloc } func free(ptr unsafe.Pointer) { // Memory is never freed. } func markRoots(start, end uintptr) { runtimePanic("unreachable: markRoots") } // ReadMemStats populates m with memory statistics. // // The returned memory statistics are up to date as of the // call to ReadMemStats. This would not do GC implicitly for you. func ReadMemStats(m *MemStats) { gcLock.Lock() m.HeapIdle = 0 m.HeapInuse = gcTotalAlloc m.HeapReleased = 0 // always 0, we don't currently release memory back to the OS. m.HeapSys = m.HeapInuse + m.HeapIdle m.GCSys = 0 m.TotalAlloc = gcTotalAlloc m.Mallocs = gcMallocs m.Frees = gcFrees m.Sys = uint64(heapEnd - heapStart) // no free -- current in use heap is the total allocated m.HeapAlloc = gcTotalAlloc m.Alloc = m.HeapAlloc gcLock.Unlock() } func GC() { // No-op. } func SetFinalizer(obj interface{}, finalizer interface{}) { // No-op. } func initHeap() { // Initialize this bump-pointer allocator to the start of the heap. // Needed here because heapStart may not be a compile-time constant. heapptr = heapStart } // setHeapEnd sets a new (larger) heapEnd pointer. func setHeapEnd(newHeapEnd uintptr) { // This "heap" is so simple that simply assigning a new value is good // enough. heapEnd = newHeapEnd } ================================================ FILE: src/runtime/gc_none.go ================================================ //go:build gc.none package runtime // This GC strategy provides no memory allocation at all. It can be useful to // detect where in a program memory is allocated (via linker errors) or for // targets that have far too little RAM even for the leaking memory allocator. import ( "unsafe" ) const needsStaticHeap = false var gcTotalAlloc uint64 // for runtime.MemStats var gcMallocs uint64 var gcFrees uint64 func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer func realloc(ptr unsafe.Pointer, size uintptr) unsafe.Pointer func free(ptr unsafe.Pointer) { // Nothing to free when nothing gets allocated. } func GC() { // Unimplemented. } func markRoots(start, end uintptr) { runtimePanic("unreachable: markRoots") } func SetFinalizer(obj interface{}, finalizer interface{}) { // Unimplemented. } func initHeap() { // Nothing to initialize. } func setHeapEnd(newHeapEnd uintptr) { // Nothing to do here, this function is never actually called. } ================================================ FILE: src/runtime/gc_precise.go ================================================ //go:build gc.precise // This implements the block-based GC as a partially precise GC. This means that // for most heap allocations it is known which words contain a pointer and which // don't. This should in theory make the GC faster (because it can skip // non-pointer object) and have fewer false positives in a GC cycle. It does // however use a bit more RAM to store the layout of each object. // // The pointer/non-pointer information for objects is stored in the first word // of the object. It is described below but in essence it contains a bitstring // of a particular size. This size does not indicate the size of the object: // instead the allocated object is a multiple of the bitstring size. This is so // that arrays and slices can store the size of the object efficiently. The // bitstring indicates where the pointers are in the object (the bit is set when // the value may be a pointer, and cleared when it certainly isn't a pointer). // Some examples (assuming a 32-bit system for the moment): // // | object type | size | bitstring | note // |-------------|------|-----------|------ // | int | 1 | 0 | no pointers in this object // | string | 2 | 01 | {pointer, len} pair so there is one pointer // | []int | 3 | 001 | {pointer, len, cap} // | [4]*int | 1 | 1 | even though it contains 4 pointers, an array repeats so it can be stored with size=1 // | [30]byte | 1 | 0 | there are no pointers so the layout is very simple // // The garbage collector scans objects by starting at the first word value in // the object. If the least significant bit of the bitstring is clear, it is // skipped (it's not a pointer). If the bit is set, it is treated as if it could // be a pointer. The garbage collector continues by scanning further words in // the object and checking them against the corresponding bit in the bitstring. // Once it reaches the end of the bitstring, it wraps around (for arrays, // slices, strings, etc). // // The layout as passed to the runtime.alloc function and stored in the object // is a pointer-sized value. If the least significant bit of the value is set, // the bitstring is contained directly inside the value, of the form // pppp_pppp_ppps_sss1. // * The 'p' bits indicate which parts of the object are a pointer. // * The 's' bits indicate the size of the object. In this case, there are 11 // pointer bits so four bits are enough for the size (0-15). // * The lowest bit is always set to distinguish this value from a pointer. // This example is for a 16-bit architecture. For example, 32-bit architectures // use a layout format of pppppppp_pppppppp_pppppppp_ppsssss1 (26 bits for // pointer/non-pointer information, 5 size bits, and one bit that's always set). // // For larger objects that don't fit in an uintptr, the layout value is a // pointer to a global with a format as follows: // struct { // size uintptr // bits [...]uint8 // } // The 'size' field is the number of bits in the bitstring. The 'bits' field is // a byte array that contains the bitstring itself, in little endian form. The // length of the bits array is ceil(size/8). package runtime import "unsafe" const sizeFieldBits = 4 + (unsafe.Sizeof(uintptr(0)) / 4) // parseGCLayout stores the layout information passed to alloc into a gcLayout value. func parseGCLayout(layout unsafe.Pointer) gcLayout { return gcLayout(layout) } // gcLayout tracks pointer locations in a heap object. type gcLayout uintptr func (layout gcLayout) pointerFree() bool { return layout&1 != 0 && layout>>(sizeFieldBits+1) == 0 } // scan an object with this element layout. // The starting address must be valid and pointer-aligned. // The length is rounded down to a multiple of the element size. func (layout gcLayout) scan(start, len uintptr) { switch { case layout == 0: // This is an unknown layout. // Scan conservatively. // NOTE: This is *NOT* equivalent to a slice of pointers on AVR. scanConservative(start, len) case layout&1 != 0: // The layout is stored directly in the integer value. // Extract the bitfields. size := uintptr(layout>>1) & (1<> (1 + sizeFieldBits) // Scan with the extracted mask. scanSimple(start, len, size*unsafe.Alignof(start), mask) default: // The layout is stored seperately in a global object. // Extract the size and bitmap. layoutAddr := unsafe.Pointer(layout) size := *(*uintptr)(layoutAddr) bitmapPtr := unsafe.Add(layoutAddr, unsafe.Sizeof(uintptr(0))) bitmapLen := (size + 7) / 8 bitmap := unsafe.Slice((*byte)(bitmapPtr), bitmapLen) // Scan with the bitmap. scanComplex(start, len, size*unsafe.Alignof(start), bitmap) } } // scanSimple scans an object with an integer bitmask of pointer locations. // The starting address must be valid and pointer-aligned. func scanSimple(start, len, size, mask uintptr) { for len >= size { // Scan this element. scanWithMask(start, mask) // Move to the next element. start += size len -= size } } // scanComplex scans an object with a bitmap of pointer locations. // The starting address must be valid and pointer-aligned. func scanComplex(start, len, size uintptr, bitmap []byte) { for len >= size { // Scan this element. for i, mask := range bitmap { addr := start + 8*unsafe.Alignof(start)*uintptr(i) scanWithMask(addr, uintptr(mask)) } // Move to the next element. start += size len -= size } } // scanWithMask scans a portion of an object with a mask of pointer locations. // The address must be valid and pointer-aligned. func scanWithMask(addr, mask uintptr) { // TODO: use ctz when available for mask != 0 { if mask&1 != 0 { // Load and mark this pointer. root := *(*uintptr)(unsafe.Pointer(addr)) markRoot(addr, root) } // Move to the next offset. mask >>= 1 addr += unsafe.Alignof(addr) } } ================================================ FILE: src/runtime/gc_stack_cores.go ================================================ //go:build scheduler.cores package runtime import ( "internal/task" "sync/atomic" ) // Normally 0. During a GC scan it has various purposes for signalling between // the core running the GC and the other cores in the system. var gcScanState atomic.Uint32 // Start GC scan by pausing the world (all other cores) and scanning their // stacks. It doesn't resume the world. func gcMarkReachable() { // If the other cores haven't started yet (for example, when a GC cycle // happens during init()), we only need to scan the stack of the current // core. if !secondaryCoresStarted { // Scan the stack(s) of the current core. scanCurrentStack() if !task.OnSystemStack() { // Mark system stack. markRoots(task.SystemStack(), stackTop) } // Scan globals. findGlobals(markRoots) // Nothing more to do: the other cores haven't started yet. return } core := currentCPU() // Interrupt all other cores. gcScanState.Store(1) for i := uint32(0); i < numCPU; i++ { if i == core { continue } gcPauseCore(i) } // Scan the stack(s) of the current core. scanCurrentStack() if !task.OnSystemStack() { // Mark system stack. markRoots(task.SystemStack(), coreStackTop(core)) } // Scan globals. findGlobals(markRoots) // Busy-wait until all the other cores are ready. They certainly should be, // after the scanning we did above. for gcScanState.Load() != numCPU { spinLoopWait() } gcScanState.Store(0) // Signal each core in turn that they can scan the stack. for i := uint32(0); i < numCPU; i++ { if i == core { continue } // Wake up the core to scan the stack. gcSignalCore(i) // Busy-wait until this core finished scanning. for gcScanState.Load() == 0 { spinLoopWait() } gcScanState.Store(0) } // All the stack are now scanned. } //go:export tinygo_scanCurrentStack func scanCurrentStack() //go:export tinygo_scanstack func scanstack(sp uintptr) { // Mark the current stack. // This function is called by scanCurrentStack, after pushing all registers // onto the stack. if task.OnSystemStack() { // This is the system stack. // Scan all words on the stack. markRoots(sp, coreStackTop(currentCPU())) } else { // This is a goroutine stack. markCurrentGoroutineStack(sp) } } // Resume the world after a call to gcMarkReachable. func gcResumeWorld() { if !secondaryCoresStarted { // Nothing to do: the world wasn't stopped in gcMarkReachable. return } // Signal each core that they can resume. hartID := currentCPU() for i := uint32(0); i < numCPU; i++ { if i == hartID { continue } // Signal the core. gcSignalCore(i) } // Busy-wait until the core acknowledges the signal (and is going to return // from the interrupt handler). for gcScanState.Load() != numCPU-1 { spinLoopWait() } gcScanState.Store(0) } ================================================ FILE: src/runtime/gc_stack_portable.go ================================================ //go:build (gc.conservative || gc.custom || gc.precise || gc.boehm) && tinygo.wasm package runtime import ( "internal/task" "runtime/volatile" "unsafe" ) func gcMarkReachable() { markStack() findGlobals(markRoots) } //go:extern runtime.stackChainStart var stackChainStart *stackChainObject type stackChainObject struct { parent *stackChainObject numSlots uintptr } // markStack marks all root pointers found on the stack. // // - Goroutine stacks are heap allocated and always reachable in some way // (for example through internal/task.currentTask) so they will always be // scanned. // - The system stack (aka startup stack) is not heap allocated, so even // though it may be referenced it will not be scanned by default. // // The compiler also inserts code to store all globals in a chain via // stackChainStart. Luckily we don't need to scan these, as these globals are // stored on the goroutine stack and are therefore already getting scanned. func markStack() { // Hack to force LLVM to consider stackChainStart to be live. // Without this hack, loads and stores may be considered dead and objects on // the stack might not be correctly tracked. With this volatile load, LLVM // is forced to consider stackChainStart (and everything it points to) as // live. volatile.LoadUint32((*uint32)(unsafe.Pointer(&stackChainStart))) // Scan the system stack. var sysSP uintptr if task.OnSystemStack() { // We are on the system stack. // Use the current stack pointer. sysSP = getCurrentStackPointer() } else { // We are in a goroutine. // Use the saved stack pointer. sysSP = savedStackPointer } markRoots(sysSP, stackTop) } // trackPointer is a stub function call inserted by the compiler during IR // construction. Calls to it are later replaced with regular stack bookkeeping // code. func trackPointer(ptr, alloca unsafe.Pointer) // swapStackChain swaps the stack chain. // This is called from internal/task when switching goroutines. func swapStackChain(dst **stackChainObject) { *dst, stackChainStart = stackChainStart, *dst } func gcResumeWorld() { // Nothing to do here (single threaded). } ================================================ FILE: src/runtime/gc_stack_raw.go ================================================ //go:build (gc.conservative || gc.precise || gc.boehm) && !tinygo.wasm && !scheduler.threads && !scheduler.cores package runtime import ( "internal/task" "sync/atomic" ) // Unused. var gcScanState atomic.Uint32 func gcMarkReachable() { markStack() findGlobals(markRoots) } // markStack marks all root pointers found on the stack. // // This implementation is conservative and relies on the stack top (provided by // the linker) and getting the current stack pointer from a register. Also, it // assumes a descending stack. Thus, it is not very portable. func markStack() { // Scan the current stack, and all current registers. scanCurrentStack() if !task.OnSystemStack() { // Mark system stack. markRoots(task.SystemStack(), stackTop) } } //go:export tinygo_scanCurrentStack func scanCurrentStack() //go:export tinygo_scanstack func scanstack(sp uintptr) { // Mark current stack. // This function is called by scanCurrentStack, after pushing all registers onto the stack. // Callee-saved registers have been pushed onto stack by tinygo_localscan, so this will scan them too. if task.OnSystemStack() { // This is the system stack. // Scan all words on the stack. markRoots(sp, stackTop) } else { // This is a goroutine stack. markCurrentGoroutineStack(sp) } } func gcResumeWorld() { // Nothing to do here (single threaded). } ================================================ FILE: src/runtime/gc_stack_threads.go ================================================ //go:build scheduler.threads package runtime import "internal/task" func gcMarkReachable() { task.GCStopWorldAndScan() } // Scan globals inside the stop-the-world phase. Called from the STW // implementation in the internal/task package. // //go:linkname gcScanGlobals internal/task.gcScanGlobals func gcScanGlobals() { findGlobals(markRoots) } // Function called from assembly with all registers pushed, to actually scan the // stack. // //go:export tinygo_scanstack func scanstack(sp uintptr) { markRoots(sp, task.StackTop()) } func gcResumeWorld() { task.GCResumeWorld() } ================================================ FILE: src/runtime/hashmap.go ================================================ package runtime // This is a hashmap implementation for the map[T]T type. // It is very roughly based on the implementation of the Go hashmap: // // https://golang.org/src/runtime/map.go import ( "internal/reflectlite" "tinygo" "unsafe" ) // The underlying hashmap structure for Go. type hashmap struct { buckets unsafe.Pointer // pointer to array of buckets seed uintptr count uintptr keySize uintptr // maybe this can store the key type as well? E.g. keysize == 5 means string? valueSize uintptr bucketBits uint8 keyEqual func(x, y unsafe.Pointer, n uintptr) bool keyHash func(key unsafe.Pointer, size, seed uintptr) uint32 } // A hashmap bucket. A bucket is a container of 8 key/value pairs: first the // following two entries, then the 8 keys, then the 8 values. This somewhat odd // ordering is to make sure the keys and values are well aligned when one of // them is smaller than the system word size. type hashmapBucket struct { tophash [8]uint8 next *hashmapBucket // next bucket (if there are more than 8 in a chain) // Followed by the actual keys, and then the actual values. These are // allocated but as they're of variable size they can't be shown here. } type hashmapIterator struct { buckets unsafe.Pointer // pointer to array of hashapBuckets numBuckets uintptr // length of buckets array bucketNumber uintptr // current index into buckets array startBucket uintptr // starting location for iterator bucket *hashmapBucket // current bucket in chain bucketIndex uint8 // current index into bucket startIndex uint8 // starting bucket index for iterator wrapped bool // true if the iterator has wrapped } func hashmapNewIterator() unsafe.Pointer { return unsafe.Pointer(new(hashmapIterator)) } // Get the topmost 8 bits of the hash, without using a special value (like 0). func hashmapTopHash(hash uint32) uint8 { tophash := uint8(hash >> 24) if tophash < 1 { // 0 means empty slot, so make it bigger. tophash += 1 } return tophash } // Create a new hashmap with the given keySize and valueSize. func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) *hashmap { bucketBits := uint8(0) for hashmapHasSpaceToGrow(bucketBits) && hashmapOverLoadFactor(sizeHint, bucketBits) { bucketBits++ } bucketBufSize := unsafe.Sizeof(hashmapBucket{}) + keySize*8 + valueSize*8 buckets := alloc(bucketBufSize*(1< If the map or slice is nil, clear is a no-op. return } m.count = 0 numBuckets := uintptr(1) << m.bucketBits bucketSize := hashmapBucketSize(m) for i := uintptr(0); i < numBuckets; i++ { bucket := hashmapBucketAddr(m, m.buckets, i) for bucket != nil { // Clear the tophash, to mark these keys/values as removed. bucket.tophash = [8]uint8{} // Clear the keys and values in the bucket so that the GC won't pin // these allocations. memzero(unsafe.Add(unsafe.Pointer(bucket), unsafe.Sizeof(hashmapBucket{})), bucketSize-unsafe.Sizeof(hashmapBucket{})) // Move on to the next bucket in the chain. bucket = bucket.next } } } func hashmapKeyEqualAlg(alg tinygo.HashmapAlgorithm) func(x, y unsafe.Pointer, n uintptr) bool { switch alg { case tinygo.HashmapAlgorithmBinary: return memequal case tinygo.HashmapAlgorithmString: return hashmapStringEqual case tinygo.HashmapAlgorithmInterface: return hashmapInterfaceEqual default: // compiler bug :( return nil } } func hashmapKeyHashAlg(alg tinygo.HashmapAlgorithm) func(key unsafe.Pointer, n, seed uintptr) uint32 { switch alg { case tinygo.HashmapAlgorithmBinary: return hash32 case tinygo.HashmapAlgorithmString: return hashmapStringPtrHash case tinygo.HashmapAlgorithmInterface: return hashmapInterfacePtrHash default: // compiler bug :( return nil } } func hashmapHasSpaceToGrow(bucketBits uint8) bool { // Over this limit, we're likely to overflow uintptrs during calculations // or numbers of hash elements. Don't allow any more growth. // With 29 bits, this is 2^32 elements anyway. return bucketBits <= uint8((unsafe.Sizeof(uintptr(0))*8)-3) } func hashmapOverLoadFactor(n uintptr, bucketBits uint8) bool { // "maximum" number of elements is 0.75 * buckets * elements per bucket // to avoid overflow, this is calculated as // max = 3 * (1/4 * buckets * elements per bucket) // = 3 * (buckets * (elements per bucket)/4) // = 3 * (buckets * (8/4) // = 3 * (buckets * 2) // = 6 * buckets max := (uintptr(6) << bucketBits) return n > max } // Return the number of entries in this hashmap, called from the len builtin. // A nil hashmap is defined as having length 0. // //go:inline func hashmapLen(m *hashmap) int { if m == nil { return 0 } return int(m.count) } //go:inline func hashmapBucketSize(m *hashmap) uintptr { return unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8 } //go:inline func hashmapBucketAddr(m *hashmap, buckets unsafe.Pointer, n uintptr) *hashmapBucket { bucketSize := hashmapBucketSize(m) bucket := (*hashmapBucket)(unsafe.Add(buckets, bucketSize*n)) return bucket } //go:inline func hashmapBucketAddrForHash(m *hashmap, hash uint32) *hashmapBucket { numBuckets := uintptr(1) << m.bucketBits bucketNumber := (uintptr(hash) & (numBuckets - 1)) return hashmapBucketAddr(m, m.buckets, bucketNumber) } //go:inline func hashmapSlotKey(m *hashmap, bucket *hashmapBucket, slot uint8) unsafe.Pointer { slotKeyOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*uintptr(slot) slotKey := unsafe.Add(unsafe.Pointer(bucket), slotKeyOffset) return slotKey } //go:inline func hashmapSlotValue(m *hashmap, bucket *hashmapBucket, slot uint8) unsafe.Pointer { slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*uintptr(slot) slotValue := unsafe.Add(unsafe.Pointer(bucket), slotValueOffset) return slotValue } // Set a specified key to a given value. Grow the map if necessary. // //go:nobounds func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32) { if hashmapHasSpaceToGrow(m.bucketBits) && hashmapOverLoadFactor(m.count, m.bucketBits) { hashmapGrow(m) // seed changed when we grew; rehash key with new seed hash = m.keyHash(key, m.keySize, m.seed) } tophash := hashmapTopHash(hash) bucket := hashmapBucketAddrForHash(m, hash) var lastBucket *hashmapBucket // See whether the key already exists somewhere. var emptySlotKey unsafe.Pointer var emptySlotValue unsafe.Pointer var emptySlotTophash *byte for bucket != nil { for i := uint8(0); i < 8; i++ { slotKey := hashmapSlotKey(m, bucket, i) slotValue := hashmapSlotValue(m, bucket, i) if bucket.tophash[i] == 0 && emptySlotKey == nil { // Found an empty slot, store it for if we couldn't find an // existing slot. emptySlotKey = slotKey emptySlotValue = slotValue emptySlotTophash = &bucket.tophash[i] } if bucket.tophash[i] == tophash { // Could be an existing key that's the same. if m.keyEqual(key, slotKey, m.keySize) { // found same key, replace it memcpy(slotValue, value, m.valueSize) return } } } lastBucket = bucket bucket = bucket.next } if emptySlotKey == nil { // Add a new bucket to the bucket chain. // TODO: rebalance if necessary to avoid O(n) insert and lookup time. lastBucket.next = (*hashmapBucket)(hashmapInsertIntoNewBucket(m, key, value, tophash)) return } m.count++ memcpy(emptySlotKey, key, m.keySize) memcpy(emptySlotValue, value, m.valueSize) *emptySlotTophash = tophash } // hashmapInsertIntoNewBucket creates a new bucket, inserts the given key and // value into the bucket, and returns a pointer to this bucket. func hashmapInsertIntoNewBucket(m *hashmap, key, value unsafe.Pointer, tophash uint8) *hashmapBucket { bucketBufSize := hashmapBucketSize(m) bucketBuf := alloc(bucketBufSize, nil) bucket := (*hashmapBucket)(bucketBuf) // Insert into the first slot, which is empty as it has just been allocated. slotKey := hashmapSlotKey(m, bucket, 0) slotValue := hashmapSlotValue(m, bucket, 0) m.count++ memcpy(slotKey, key, m.keySize) memcpy(slotValue, value, m.valueSize) bucket.tophash[0] = tophash return bucket } func hashmapGrow(m *hashmap) { // allocate our new buckets twice as big n := hashmapCopy(m, m.bucketBits+1) *m = n } //go:linkname hashmapClone maps.clone func hashmapClone(intf _interface) _interface { typ, val := decomposeInterface(intf) m := (*hashmap)(val) n := hashmapCopy(m, m.bucketBits) return composeInterface(typ, unsafe.Pointer(&n)) } func hashmapCopy(m *hashmap, sizeBits uint8) hashmap { // clone map as empty n := *m n.count = 0 n.seed = uintptr(fastrand()) n.bucketBits = sizeBits numBuckets := uintptr(1) << n.bucketBits bucketBufSize := hashmapBucketSize(m) n.buckets = alloc(bucketBufSize*numBuckets, nil) // use a hashmap iterator to go through the old map var it hashmapIterator var key = alloc(m.keySize, nil) var value = alloc(m.valueSize, nil) for hashmapNext(m, &it, key, value) { h := n.keyHash(key, uintptr(n.keySize), n.seed) hashmapSet(&n, key, value, h) } return n } // Get the value of a specified key, or zero the value if not found. // //go:nobounds func hashmapGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr, hash uint32) bool { if m == nil { // Getting a value out of a nil map is valid. From the spec: // > if the map is nil or does not contain such an entry, a[x] is the // > zero value for the element type of M memzero(value, uintptr(valueSize)) return false } tophash := hashmapTopHash(hash) bucket := hashmapBucketAddrForHash(m, hash) // Try to find the key. for bucket != nil { for i := uint8(0); i < 8; i++ { slotKey := hashmapSlotKey(m, bucket, i) slotValue := hashmapSlotValue(m, bucket, i) if bucket.tophash[i] == tophash { // This could be the key we're looking for. if m.keyEqual(key, slotKey, m.keySize) { // Found the key, copy it. memcpy(value, slotValue, m.valueSize) return true } } } bucket = bucket.next } // Did not find the key. memzero(value, m.valueSize) return false } // Delete a given key from the map. No-op when the key does not exist in the // map. // //go:nobounds func hashmapDelete(m *hashmap, key unsafe.Pointer, hash uint32) { if m == nil { // The delete builtin is defined even when the map is nil. From the spec: // > If the map m is nil or the element m[k] does not exist, delete is a // > no-op. return } tophash := hashmapTopHash(hash) bucket := hashmapBucketAddrForHash(m, hash) // Try to find the key. for bucket != nil { for i := uint8(0); i < 8; i++ { slotKey := hashmapSlotKey(m, bucket, i) if bucket.tophash[i] == tophash { // This could be the key we're looking for. if m.keyEqual(key, slotKey, m.keySize) { // Found the key, delete it. bucket.tophash[i] = 0 // Zero out the key and value so garbage collector doesn't pin the allocations. memzero(slotKey, m.keySize) slotValue := hashmapSlotValue(m, bucket, i) memzero(slotValue, m.valueSize) m.count-- return } } } bucket = bucket.next } } // Iterate over a hashmap. // //go:nobounds func hashmapNext(m *hashmap, it *hashmapIterator, key, value unsafe.Pointer) bool { if m == nil { // From the spec: If the map is nil, the number of iterations is 0. return false } if it.buckets == nil { // initialize iterator it.buckets = m.buckets it.numBuckets = uintptr(1) << m.bucketBits it.startBucket = uintptr(fastrand()) & (it.numBuckets - 1) it.startIndex = uint8(fastrand() & 7) it.bucketNumber = it.startBucket it.bucket = hashmapBucketAddr(m, it.buckets, it.bucketNumber) it.bucketIndex = it.startIndex } for { // If we've wrapped and we're back at our starting location, terminate the iteration. if it.wrapped && it.bucketNumber == it.startBucket && it.bucketIndex == it.startIndex { return false } if it.bucketIndex >= 8 { // end of bucket, move to the next in the chain it.bucketIndex = 0 it.bucket = it.bucket.next } if it.bucket == nil { it.bucketNumber++ // next bucket if it.bucketNumber >= it.numBuckets { // went through all buckets -- wrap around it.bucketNumber = 0 it.wrapped = true } it.bucket = hashmapBucketAddr(m, it.buckets, it.bucketNumber) continue } if it.bucket.tophash[it.bucketIndex] == 0 { // slot is empty - move on it.bucketIndex++ continue } // Found a key. slotKey := hashmapSlotKey(m, it.bucket, it.bucketIndex) memcpy(key, slotKey, m.keySize) if it.buckets == m.buckets { // Our view of the buckets is the same as the parent map. // Just copy the value we have slotValue := hashmapSlotValue(m, it.bucket, it.bucketIndex) memcpy(value, slotValue, m.valueSize) it.bucketIndex++ } else { it.bucketIndex++ // Our view of the buckets doesn't match the parent map. // Look up the key in the new buckets and return that value if it exists hash := m.keyHash(key, m.keySize, m.seed) ok := hashmapGet(m, key, value, m.valueSize, hash) if !ok { // doesn't exist in parent map; try next key continue } // All good. } return true } } // Hashmap with plain binary data keys (not containing strings etc.). func hashmapBinarySet(m *hashmap, key, value unsafe.Pointer) { if m == nil { nilMapPanic() } hash := hash32(key, m.keySize, m.seed) hashmapSet(m, key, value, hash) } func hashmapBinaryGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr) bool { if m == nil { memzero(value, uintptr(valueSize)) return false } hash := hash32(key, m.keySize, m.seed) return hashmapGet(m, key, value, valueSize, hash) } func hashmapBinaryDelete(m *hashmap, key unsafe.Pointer) { if m == nil { return } hash := hash32(key, m.keySize, m.seed) hashmapDelete(m, key, hash) } // Hashmap with string keys (a common case). func hashmapStringEqual(x, y unsafe.Pointer, n uintptr) bool { return *(*string)(x) == *(*string)(y) } func hashmapStringHash(s string, seed uintptr) uint32 { _s := (*_string)(unsafe.Pointer(&s)) return hash32(unsafe.Pointer(_s.ptr), uintptr(_s.length), seed) } func hashmapStringPtrHash(sptr unsafe.Pointer, size uintptr, seed uintptr) uint32 { _s := *(*_string)(sptr) return hash32(unsafe.Pointer(_s.ptr), uintptr(_s.length), seed) } func hashmapStringSet(m *hashmap, key string, value unsafe.Pointer) { if m == nil { nilMapPanic() } hash := hashmapStringHash(key, m.seed) hashmapSet(m, unsafe.Pointer(&key), value, hash) } func hashmapStringGet(m *hashmap, key string, value unsafe.Pointer, valueSize uintptr) bool { if m == nil { memzero(value, uintptr(valueSize)) return false } hash := hashmapStringHash(key, m.seed) return hashmapGet(m, unsafe.Pointer(&key), value, valueSize, hash) } func hashmapStringDelete(m *hashmap, key string) { if m == nil { return } hash := hashmapStringHash(key, m.seed) hashmapDelete(m, unsafe.Pointer(&key), hash) } // Hashmap with interface keys (for everything else). // This is a method that is intentionally unexported in the reflect package. It // is identical to the Interface() method call, except it doesn't check whether // a field is exported and thus allows circumventing the type system. // The hash function needs it as it also needs to hash unexported struct fields. // //go:linkname valueInterfaceUnsafe internal/reflectlite.valueInterfaceUnsafe func valueInterfaceUnsafe(v reflectlite.Value) interface{} func hashmapFloat32Hash(ptr unsafe.Pointer, seed uintptr) uint32 { f := *(*uint32)(ptr) if f == 0x80000000 { // convert -0 to 0 for hashing f = 0 } return hash32(unsafe.Pointer(&f), 4, seed) } func hashmapFloat64Hash(ptr unsafe.Pointer, seed uintptr) uint32 { f := *(*uint64)(ptr) if f == 0x8000000000000000 { // convert -0 to 0 for hashing f = 0 } return hash32(unsafe.Pointer(&f), 8, seed) } func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 { x := reflectlite.ValueOf(itf) if x.RawType() == nil { return 0 // nil interface } value := (*_interface)(unsafe.Pointer(&itf)).value ptr := value if x.RawType().Size() <= unsafe.Sizeof(uintptr(0)) { // Value fits in pointer, so it's directly stored in the pointer. ptr = unsafe.Pointer(&value) } switch x.RawType().Kind() { case reflectlite.Int, reflectlite.Int8, reflectlite.Int16, reflectlite.Int32, reflectlite.Int64: return hash32(ptr, x.RawType().Size(), seed) case reflectlite.Bool, reflectlite.Uint, reflectlite.Uint8, reflectlite.Uint16, reflectlite.Uint32, reflectlite.Uint64, reflectlite.Uintptr: return hash32(ptr, x.RawType().Size(), seed) case reflectlite.Float32: // It should be possible to just has the contents. However, NaN != NaN // so if you're using lots of NaNs as map keys (you shouldn't) then hash // time may become exponential. To fix that, it would be better to // return a random number instead: // https://research.swtch.com/randhash return hashmapFloat32Hash(ptr, seed) case reflectlite.Float64: return hashmapFloat64Hash(ptr, seed) case reflectlite.Complex64: rptr, iptr := ptr, unsafe.Add(ptr, 4) return hashmapFloat32Hash(rptr, seed) ^ hashmapFloat32Hash(iptr, seed) case reflectlite.Complex128: rptr, iptr := ptr, unsafe.Add(ptr, 8) return hashmapFloat64Hash(rptr, seed) ^ hashmapFloat64Hash(iptr, seed) case reflectlite.String: return hashmapStringHash(x.String(), seed) case reflectlite.Chan, reflectlite.Ptr, reflectlite.UnsafePointer: // It might seem better to just return the pointer, but that won't // result in an evenly distributed hashmap. Instead, hash the pointer // like most other types. return hash32(ptr, x.RawType().Size(), seed) case reflectlite.Array: var hash uint32 for i := 0; i < x.Len(); i++ { hash ^= hashmapInterfaceHash(valueInterfaceUnsafe(x.Index(i)), seed) } return hash case reflectlite.Struct: var hash uint32 for i := 0; i < x.NumField(); i++ { hash ^= hashmapInterfaceHash(valueInterfaceUnsafe(x.Field(i)), seed) } return hash default: runtimePanic("comparing un-comparable type") return 0 // unreachable } } func hashmapInterfacePtrHash(iptr unsafe.Pointer, size uintptr, seed uintptr) uint32 { _i := *(*interface{})(iptr) return hashmapInterfaceHash(_i, seed) } func hashmapInterfaceEqual(x, y unsafe.Pointer, n uintptr) bool { return *(*interface{})(x) == *(*interface{})(y) } func hashmapInterfaceSet(m *hashmap, key interface{}, value unsafe.Pointer) { if m == nil { nilMapPanic() } hash := hashmapInterfaceHash(key, m.seed) hashmapSet(m, unsafe.Pointer(&key), value, hash) } func hashmapInterfaceGet(m *hashmap, key interface{}, value unsafe.Pointer, valueSize uintptr) bool { if m == nil { memzero(value, uintptr(valueSize)) return false } hash := hashmapInterfaceHash(key, m.seed) return hashmapGet(m, unsafe.Pointer(&key), value, valueSize, hash) } func hashmapInterfaceDelete(m *hashmap, key interface{}) { if m == nil { return } hash := hashmapInterfaceHash(key, m.seed) hashmapDelete(m, unsafe.Pointer(&key), hash) } ================================================ FILE: src/runtime/hosted.go ================================================ //go:build !baremetal package runtime const baremetal = false ================================================ FILE: src/runtime/interface.go ================================================ package runtime // This file implements Go interfaces. // // Interfaces are represented as a pair of {typecode, value}, where value can be // anything (including non-pointers). import ( "internal/reflectlite" "unsafe" ) type _interface struct { typecode unsafe.Pointer value unsafe.Pointer } //go:inline func composeInterface(typecode, value unsafe.Pointer) _interface { return _interface{typecode, value} } //go:inline func decomposeInterface(i _interface) (unsafe.Pointer, unsafe.Pointer) { return i.typecode, i.value } // Return true iff both interfaces are equal. func interfaceEqual(x, y interface{}) bool { return reflectValueEqual(reflectlite.ValueOf(x), reflectlite.ValueOf(y)) } func reflectValueEqual(x, y reflectlite.Value) bool { // Note: doing a x.Type() == y.Type() comparison would not work here as that // would introduce an infinite recursion: comparing two reflectlite.Type values // is done with this reflectValueEqual runtime call. if x.RawType() == nil || y.RawType() == nil { // One of them is nil. return x.RawType() == y.RawType() } if x.RawType() != y.RawType() { // The type is not the same, which means the interfaces are definitely // not the same. return false } switch x.RawType().Kind() { case reflectlite.Bool: return x.Bool() == y.Bool() case reflectlite.Int, reflectlite.Int8, reflectlite.Int16, reflectlite.Int32, reflectlite.Int64: return x.Int() == y.Int() case reflectlite.Uint, reflectlite.Uint8, reflectlite.Uint16, reflectlite.Uint32, reflectlite.Uint64, reflectlite.Uintptr: return x.Uint() == y.Uint() case reflectlite.Float32, reflectlite.Float64: return x.Float() == y.Float() case reflectlite.Complex64, reflectlite.Complex128: return x.Complex() == y.Complex() case reflectlite.String: return x.String() == y.String() case reflectlite.Chan, reflectlite.Ptr, reflectlite.UnsafePointer: return x.UnsafePointer() == y.UnsafePointer() case reflectlite.Array: for i := 0; i < x.Len(); i++ { if !reflectValueEqual(x.Index(i), y.Index(i)) { return false } } return true case reflectlite.Struct: for i := 0; i < x.NumField(); i++ { if !reflectValueEqual(x.Field(i), y.Field(i)) { return false } } return true case reflectlite.Interface: return reflectValueEqual(x.Elem(), y.Elem()) default: runtimePanic("comparing un-comparable type") return false // unreachable } } // interfaceTypeAssert is called when a type assert without comma-ok still // returns false. func interfaceTypeAssert(ok bool) { if !ok { runtimePanic("type assert failed") } } // The following declarations are only used during IR construction. They are // lowered to inline IR in the interface lowering pass. // See compiler/interface-lowering.go for details. type structField struct { typecode unsafe.Pointer // type of this struct field data *uint8 // pointer to byte array containing name, tag, varint-encoded offset, and some flags } // Pseudo function call used during a type assert. It is used during interface // lowering, to assign the lowest type numbers to the types with the most type // asserts. Also, it is replaced with const false if this type assert can never // happen. func typeAssert(actualType unsafe.Pointer, assertedType *uint8) bool ================================================ FILE: src/runtime/internal/sys/zversion.go ================================================ package sys const TheVersion = `go0.1.0` ================================================ FILE: src/runtime/interrupt/checkpoint.go ================================================ package interrupt // A checkpoint is a setjmp like buffer, that can be used as a flag for // interrupts. // // It can be used as follows: // // // global var // var c Checkpoint // // // to set up the checkpoint and wait for it // if c.Save() { // setupInterrupt() // for { // waitForInterrupt() // } // } // // // Inside the interrupt handler: // if c.Saved() { // c.Jump() // } type Checkpoint struct { jumpSP uintptr jumpPC uintptr } // Save the execution state in the given checkpoint, overwriting a previous // saved checkpoint. // // This function returns twice: once the normal way after saving (returning // true) and once after jumping (returning false). // // This function is a compiler intrinsic, it is not implemented in Go. func (c *Checkpoint) Save() bool // Returns whether a jump point was saved (and not erased due to a jump). func (c *Checkpoint) Saved() bool { return c.jumpPC != 0 } // Jump to the point where the execution state was saved, and erase the saved // jump point. This must *only* be called from inside an interrupt. // // This method does not return in the conventional way, it resumes execution at // the last point a checkpoint was saved. func (c *Checkpoint) Jump() { if !c.Saved() { panic("runtime/interrupt: no checkpoint was saved") } jumpPC := c.jumpPC jumpSP := c.jumpSP c.jumpPC = 0 c.jumpSP = 0 if jumpPC == 0 { panic("jumping to 0") } checkpointJump(jumpSP, jumpPC) } //export tinygo_checkpointJump func checkpointJump(jumpSP, jumpPC uintptr) ================================================ FILE: src/runtime/interrupt/interrupt.go ================================================ // Package interrupt provides access to hardware interrupts. It provides a way // to define interrupts and to enable/disable them. package interrupt import "unsafe" // Interrupt provides direct access to hardware interrupts. You can configure // this interrupt through this interface. // // Do not use the zero value of an Interrupt object. Instead, call New to obtain // an interrupt handle. type Interrupt struct { // Make this number unexported so it cannot be set directly. This provides // some encapsulation. num int } // New is a compiler intrinsic that creates a new Interrupt object. You may call // it only once, and must pass constant parameters to it. That means that the // interrupt ID must be a Go constant and that the handler must be a simple // function: closures are not supported. func New(id int, handler func(Interrupt)) Interrupt // handle is used internally, between IR generation and interrupt lowering. The // frontend will create runtime/interrupt.handle objects, cast them to an int, // and use that in an Interrupt object. That way the compiler will be able to // optimize away all interrupt handles that are never used in a program. // This system only works when interrupts need to be enabled before use and this // is done only through calling Enable() on this object. If interrupts cannot // individually be enabled/disabled, the compiler should create a pseudo-call // (like runtime/interrupt.use()) that keeps the interrupt alive. type handle struct { context unsafe.Pointer funcPtr uintptr Interrupt } ================================================ FILE: src/runtime/interrupt/interrupt_avr.go ================================================ //go:build avr package interrupt import "device" // State represents the previous global interrupt state. type State uint8 // Disable disables all interrupts and returns the previous interrupt state. It // can be used in a critical section like this: // // state := interrupt.Disable() // // critical section // interrupt.Restore(state) // // Critical sections can be nested. Make sure to call Restore in the same order // as you called Disable (this happens naturally with the pattern above). func Disable() (state State) { // SREG is at I/O address 0x3f. return State(device.AsmFull(` in {}, 0x3f cli `, nil)) } // Restore restores interrupts to what they were before. Give the previous state // returned by Disable as a parameter. If interrupts were disabled before // calling Disable, this will not re-enable interrupts, allowing for nested // critical sections. func Restore(state State) { // SREG is at I/O address 0x3f. device.AsmFull("out 0x3f, {state}", map[string]interface{}{ "state": state, }) } // In returns whether the system is currently in an interrupt. // // Warning: this always returns false on AVR, as there does not appear to be a // reliable way to determine whether we're currently running inside an interrupt // handler. func In() bool { return false } ================================================ FILE: src/runtime/interrupt/interrupt_cortexm.go ================================================ //go:build cortexm package interrupt import ( "device/arm" ) // Enable enables this interrupt. Right after calling this function, the // interrupt may be invoked if it was already pending. func (irq Interrupt) Enable() { // Clear the ARM pending bit, an asserting device may still // trigger the interrupt once enabled. arm.ClearPendingIRQ(uint32(irq.num)) arm.EnableIRQ(uint32(irq.num)) } // Disable disables this interrupt. func (irq Interrupt) Disable() { arm.DisableIRQ(uint32(irq.num)) } // SetPriority sets the interrupt priority for this interrupt. A lower number // means a higher priority. Additionally, most hardware doesn't implement all // priority bits (only the uppoer bits). // // Examples: 0xff (lowest priority), 0xc0 (low priority), 0x00 (highest possible // priority). func (irq Interrupt) SetPriority(priority uint8) { arm.SetPriority(uint32(irq.num), uint32(priority)) } // State represents the previous global interrupt state. type State uintptr // Disable disables all interrupts and returns the previous interrupt state. It // can be used in a critical section like this: // // state := interrupt.Disable() // // critical section // interrupt.Restore(state) // // Critical sections can be nested. Make sure to call Restore in the same order // as you called Disable (this happens naturally with the pattern above). func Disable() (state State) { return State(arm.DisableInterrupts()) } // Restore restores interrupts to what they were before. Give the previous state // returned by Disable as a parameter. If interrupts were disabled before // calling Disable, this will not re-enable interrupts, allowing for nested // critical sections. func Restore(state State) { arm.EnableInterrupts(uintptr(state)) } // In returns whether the system is currently in an interrupt. func In() bool { // The VECTACTIVE field gives the instruction vector that is currently // active (in handler mode), or 0 if not in an interrupt. // Documentation: // https://developer.arm.com/documentation/dui0497/a/cortex-m0-peripherals/system-control-block/interrupt-control-and-state-register vectactive := uint8(arm.SCB.ICSR.Get()) return vectactive != 0 } ================================================ FILE: src/runtime/interrupt/interrupt_esp32c3.go ================================================ //go:build esp32c3 package interrupt import ( "device/esp" "device/riscv" "errors" "runtime/volatile" "unsafe" ) // Enable register CPU interrupt with interrupt.Interrupt. // The ESP32-C3 has 31 CPU independent interrupts. // The Interrupt.New(x, f) (x = [1..31]) attaches CPU interrupt to function f. // Caller must map the selected interrupt using following sequence (for example using id 5): // // // map interrupt 5 to my XXXX module // esp.INTERRUPT_CORE0.XXXX_INTERRUPT_PRO_MAP.Set( 5 ) // _ = Interrupt.New(5, func(interrupt.Interrupt) { // ... // }).Enable() func (i Interrupt) Enable() error { if i.num < 1 && i.num > 31 { return errors.New("interrupt for ESP32-C3 must be in range of 1 through 31") } mask := riscv.DisableInterrupts() defer riscv.EnableInterrupts(mask) // enable CPU interrupt number i.num esp.INTERRUPT_CORE0.CPU_INT_ENABLE.SetBits(1 << i.num) // Set pulse interrupt type (rising edge detection) esp.INTERRUPT_CORE0.CPU_INT_TYPE.SetBits(1 << i.num) // Set default threshold to defaultThreshold reg := (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.INTERRUPT_CORE0.CPU_INT_PRI_0), i.num*4)) reg.Set(defaultThreshold) // Reset interrupt before reenabling esp.INTERRUPT_CORE0.CPU_INT_CLEAR.SetBits(1 << i.num) esp.INTERRUPT_CORE0.CPU_INT_CLEAR.ClearBits(1 << i.num) // we must wait for any pending write operations to complete riscv.Asm("fence") return nil } // Adding pseudo function calls that is replaced by the compiler with the actual // functions registered through interrupt.New. // //go:linkname callHandlers runtime/interrupt.callHandlers func callHandlers(num int) const ( IRQNUM_1 = 1 + iota IRQNUM_2 IRQNUM_3 IRQNUM_4 IRQNUM_5 IRQNUM_6 IRQNUM_7 IRQNUM_8 IRQNUM_9 IRQNUM_10 IRQNUM_11 IRQNUM_12 IRQNUM_13 IRQNUM_14 IRQNUM_15 IRQNUM_16 IRQNUM_17 IRQNUM_18 IRQNUM_19 IRQNUM_20 IRQNUM_21 IRQNUM_22 IRQNUM_23 IRQNUM_24 IRQNUM_25 IRQNUM_26 IRQNUM_27 IRQNUM_28 IRQNUM_29 IRQNUM_30 IRQNUM_31 ) const ( defaultThreshold = 5 disableThreshold = 10 ) //go:inline func callHandler(n int) { switch n { case IRQNUM_1: callHandlers(IRQNUM_1) case IRQNUM_2: callHandlers(IRQNUM_2) case IRQNUM_3: callHandlers(IRQNUM_3) case IRQNUM_4: callHandlers(IRQNUM_4) case IRQNUM_5: callHandlers(IRQNUM_5) case IRQNUM_6: callHandlers(IRQNUM_6) case IRQNUM_7: callHandlers(IRQNUM_7) case IRQNUM_8: callHandlers(IRQNUM_8) case IRQNUM_9: callHandlers(IRQNUM_9) case IRQNUM_10: callHandlers(IRQNUM_10) case IRQNUM_11: callHandlers(IRQNUM_11) case IRQNUM_12: callHandlers(IRQNUM_12) case IRQNUM_13: callHandlers(IRQNUM_13) case IRQNUM_14: callHandlers(IRQNUM_14) case IRQNUM_15: callHandlers(IRQNUM_15) case IRQNUM_16: callHandlers(IRQNUM_16) case IRQNUM_17: callHandlers(IRQNUM_17) case IRQNUM_18: callHandlers(IRQNUM_18) case IRQNUM_19: callHandlers(IRQNUM_19) case IRQNUM_20: callHandlers(IRQNUM_20) case IRQNUM_21: callHandlers(IRQNUM_21) case IRQNUM_22: callHandlers(IRQNUM_22) case IRQNUM_23: callHandlers(IRQNUM_23) case IRQNUM_24: callHandlers(IRQNUM_24) case IRQNUM_25: callHandlers(IRQNUM_25) case IRQNUM_26: callHandlers(IRQNUM_26) case IRQNUM_27: callHandlers(IRQNUM_27) case IRQNUM_28: callHandlers(IRQNUM_28) case IRQNUM_29: callHandlers(IRQNUM_29) case IRQNUM_30: callHandlers(IRQNUM_30) case IRQNUM_31: callHandlers(IRQNUM_31) } } //export handleInterrupt func handleInterrupt() { mcause := riscv.MCAUSE.Get() exception := mcause&(1<<31) == 0 interruptNumber := uint32(mcause & 0x1f) if !exception && interruptNumber > 0 { // save MSTATUS & MEPC, which could be overwritten by another CPU interrupt mstatus := riscv.MSTATUS.Get() mepc := riscv.MEPC.Get() // Using threshold to temporary disable this interrupts. // FYI: using CPU interrupt enable bit make runtime to loose interrupts. reg := (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.INTERRUPT_CORE0.CPU_INT_PRI_0), interruptNumber*4)) thresholdSave := reg.Get() reg.Set(disableThreshold) riscv.Asm("fence") interruptBit := uint32(1 << interruptNumber) // reset pending status interrupt if esp.INTERRUPT_CORE0.CPU_INT_TYPE.Get()&interruptBit != 0 { // this is edge type interrupt esp.INTERRUPT_CORE0.CPU_INT_CLEAR.SetBits(interruptBit) esp.INTERRUPT_CORE0.CPU_INT_CLEAR.ClearBits(interruptBit) } else { // this is level type interrupt esp.INTERRUPT_CORE0.CPU_INT_CLEAR.ClearBits(interruptBit) } // enable CPU interrupts riscv.MSTATUS.SetBits(riscv.MSTATUS_MIE) // Call registered interrupt handler(s) callHandler(int(interruptNumber)) // disable CPU interrupts riscv.MSTATUS.ClearBits(riscv.MSTATUS_MIE) // restore interrupt threshold to enable interrupt again reg.Set(thresholdSave) riscv.Asm("fence") // restore MSTATUS & MEPC riscv.MSTATUS.Set(mstatus) riscv.MEPC.Set(mepc) // do not enable CPU interrupts now // the 'MRET' in src/device/riscv/handleinterrupt.S will copies the state of MPIE back into MIE, and subsequently clears MPIE. // riscv.MSTATUS.SetBits(riscv.MSTATUS_MIE) } else { // Topmost bit is clear, so it is an exception of some sort. // We could implement support for unsupported instructions here (such as // misaligned loads). However, for now we'll just print a fatal error. handleException(mcause) } } func handleException(mcause uintptr) { println("*** Exception: pc:", riscv.MEPC.Get()) println("*** Exception: code:", uint32(mcause&0x1f)) println("*** Exception: mcause:", mcause) switch uint32(mcause & 0x1f) { case riscv.InstructionAccessFault: println("*** virtual address:", riscv.MTVAL.Get()) case riscv.IllegalInstruction: println("*** opcode:", riscv.MTVAL.Get()) case riscv.LoadAccessFault: println("*** read address:", riscv.MTVAL.Get()) case riscv.StoreOrAMOAccessFault: println("*** write address:", riscv.MTVAL.Get()) } for { riscv.Asm("wfi") } } ================================================ FILE: src/runtime/interrupt/interrupt_gameboyadvance.go ================================================ //go:build gameboyadvance package interrupt // This is good documentation of the GBA: https://www.akkit.org/info/gbatek.htm import ( "device/gba" ) // Enable enables this interrupt. Right after calling this function, the // interrupt may be invoked if it was already pending. func (irq Interrupt) Enable() { gba.INTERRUPT.IE.SetBits(1 << uint(irq.num)) } var inInterrupt bool //export handleInterrupt func handleInterrupt() { inInterrupt = true flags := gba.INTERRUPT.IF.Get() for i := 0; i < 14; i++ { if flags&(1< 16 bytes. // https://github.com/google/leveldb // https://en.wikipedia.org/wiki/LevelDB package runtime import ( "unsafe" ) // leveldb hash func hash32(ptr unsafe.Pointer, n, seed uintptr) uint32 { const ( lseed = 0xbc9f1d34 m = 0xc6a4a793 ) b := unsafe.Slice((*byte)(ptr), n) h := uint32(lseed^seed) ^ uint32(uint(len(b))*uint(m)) for ; len(b) >= 4; b = b[4:] { h += uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 h *= m h ^= h >> 16 } switch len(b) { case 3: h += uint32(b[2]) << 16 fallthrough case 2: h += uint32(b[1]) << 8 fallthrough case 1: h += uint32(b[0]) h *= m h ^= h >> 24 } return h } // hash64finalizer is a 64-bit integer mixing function from // https://web.archive.org/web/20120720045250/http://www.cris.com/~Ttwang/tech/inthash.htm func hash64finalizer(key uint64) uint64 { key = ^key + (key << 21) // key = (key << 21) - key - 1; key = key ^ (key >> 24) key = (key + (key << 3)) + (key << 8) // key * 265 key = key ^ (key >> 14) key = (key + (key << 2)) + (key << 4) // key * 21 key = key ^ (key >> 28) key = key + (key << 31) return key } // hash64 turns hash32 into a 64-bit hash, by use hash64finalizer to // mix the result of hash32 function combined with an xorshifted version of // the seed. func hash64(ptr unsafe.Pointer, n, seed uintptr) uint64 { h32 := hash32(ptr, n, seed) return hash64finalizer((uint64(h32^xorshift32(uint32(seed))) << 32) | uint64(h32)) } ================================================ FILE: src/runtime/memhash_tsip.go ================================================ //go:build runtime_memhash_tsip // This is the tsip hash developed by Damian Gryski, based on ideas from SipHash. // It is slower than leveldb's hash, but should be "stronger". // https://en.wikipedia.org/wiki/SipHash // https://github.com/veorq/SipHash // https://github.com/dgryski/tsip package runtime import ( "internal/binary" "math/bits" "unsafe" ) type sip struct { v0, v1 uint64 } func (s *sip) round() { s.v0 += s.v1 s.v1 = bits.RotateLeft64(s.v1, 13) ^ s.v0 s.v0 = bits.RotateLeft64(s.v0, 35) + s.v1 s.v1 = bits.RotateLeft64(s.v1, 17) ^ s.v0 s.v0 = bits.RotateLeft64(s.v0, 21) } func hash64(ptr unsafe.Pointer, n uintptr, seed uintptr) uint64 { p := unsafe.Slice((*byte)(ptr), n) k0 := uint64(seed) k1 := uint64(0) s := sip{ v0: k0 ^ 0x736f6d6570736575, v1: k1 ^ 0x646f72616e646f6d, } b := uint64(len(p)) << 56 for len(p) >= 8 { m := binary.LittleEndian.Uint64(p[:8]) s.v1 ^= m s.round() s.v0 ^= m p = p[8:] } switch len(p) { case 7: b |= uint64(p[6]) << 48 fallthrough case 6: b |= uint64(p[5]) << 40 fallthrough case 5: b |= uint64(p[4]) << 32 fallthrough case 4: b |= uint64(p[3]) << 24 fallthrough case 3: b |= uint64(p[2]) << 16 fallthrough case 2: b |= uint64(p[1]) << 8 fallthrough case 1: b |= uint64(p[0]) } // last block s.v1 ^= b s.round() s.v0 ^= b // finalization s.v1 ^= 0xff s.round() s.v1 = bits.RotateLeft64(s.v1, 32) s.round() s.v1 = bits.RotateLeft64(s.v1, 32) return s.v0 ^ s.v1 } type sip32 struct { v0, v1 uint32 } func (s *sip32) round() { s.v0 += s.v1 s.v1 = bits.RotateLeft32(s.v1, 5) ^ s.v0 s.v0 = bits.RotateLeft32(s.v0, 8) + s.v1 s.v1 = bits.RotateLeft32(s.v1, 13) ^ s.v0 s.v0 = bits.RotateLeft32(s.v0, 7) } func hash32(ptr unsafe.Pointer, n uintptr, seed uintptr) uint32 { p := unsafe.Slice((*byte)(ptr), n) k0 := uint32(seed) k1 := uint32(seed >> 32) s := sip32{ v0: k0 ^ 0x74656462, v1: k1 ^ 0x6c796765, } b := uint32(len(p)) << 24 for len(p) >= 4 { m := binary.LittleEndian.Uint32(p[:4]) s.v1 ^= m s.round() s.v0 ^= m p = p[4:] } switch len(p) { case 3: b |= uint32(p[2]) << 16 fallthrough case 2: b |= uint32(p[1]) << 8 fallthrough case 1: b |= uint32(p[0]) } // last block s.v1 ^= b s.round() s.v0 ^= b // finalization s.v1 ^= 0xff s.round() s.v1 = bits.RotateLeft32(s.v1, 16) s.round() s.v1 = bits.RotateLeft32(s.v1, 16) return s.v0 ^ s.v1 } ================================================ FILE: src/runtime/metrics/metrics.go ================================================ // Package metrics is a dummy package that is not yet implemented. package metrics type Description struct { Name string Description string Kind ValueKind Cumulative bool } func All() []Description { return nil } type Float64Histogram struct { Counts []uint64 Buckets []float64 } type Sample struct { Name string Value Value } func Read(m []Sample) {} type Value struct{} func (v Value) Float64() float64 { return 0 } func (v Value) Float64Histogram() *Float64Histogram { return nil } func (v Value) Kind() ValueKind { return KindBad } func (v Value) Uint64() uint64 { return 0 } type ValueKind int const ( KindBad ValueKind = iota KindUint64 KindFloat64 KindFloat64Histogram ) ================================================ FILE: src/runtime/metrics.go ================================================ package runtime // Implementation of functions needed by runtime/metrics. // Mostly just dummy implementations: we don't currently use any of these // metrics. //go:linkname godebug_registerMetric internal/godebug.registerMetric func godebug_registerMetric(name string, read func() uint64) { // Dummy function for compatibility with Go 1.21. } ================================================ FILE: src/runtime/mstats.go ================================================ package runtime // Memory statistics // Subset of memory statistics from upstream Go. // Works with conservative gc only. // A MemStats records statistics about the memory allocator. type MemStats struct { // General statistics. // Alloc is bytes of allocated heap objects. // // This is the same as HeapAlloc (see below). Alloc uint64 // Sys is the total bytes of memory obtained from the OS. // // Sys is the sum of the XSys fields below. Sys measures the // address space reserved by the runtime for the // heap, stacks, and other internal data structures. Sys uint64 // Heap memory statistics. // HeapAlloc is bytes of allocated heap objects. // // "Allocated" heap objects include all reachable objects, as // well as unreachable objects that the garbage collector has // not yet freed. Specifically, HeapAlloc increases as heap // objects are allocated and decreases as the heap is swept // and unreachable objects are freed. Sweeping occurs // incrementally between GC cycles, so these two processes // occur simultaneously, and as a result HeapAlloc tends to // change smoothly (in contrast with the sawtooth that is // typical of stop-the-world garbage collectors). HeapAlloc uint64 // HeapSys is bytes of heap memory, total. // // In TinyGo unlike upstream Go, we make no distinction between // regular heap blocks used by escaped-to-the-heap variables and // blocks occupied by goroutine stacks, // all such blocks are marked as in-use, see HeapInuse below. HeapSys uint64 // HeapIdle is bytes in idle (unused) blocks. HeapIdle uint64 // HeapInuse is bytes in in-use blocks. HeapInuse uint64 // HeapReleased is bytes of physical memory returned to the OS. HeapReleased uint64 // TotalAlloc is cumulative bytes allocated for heap objects. // // TotalAlloc increases as heap objects are allocated, but // unlike Alloc and HeapAlloc, it does not decrease when // objects are freed. TotalAlloc uint64 // Mallocs is the cumulative count of heap objects allocated. // The number of live objects is Mallocs - Frees. Mallocs uint64 // Frees is the cumulative count of heap objects freed. Frees uint64 // Off-heap memory statistics. // // The following statistics measure runtime-internal // structures that are not allocated from heap memory (usually // because they are part of implementing the heap). // GCSys is bytes of memory in garbage collection metadata. GCSys uint64 } ================================================ FILE: src/runtime/nonhosted.go ================================================ //go:build baremetal || js || wasm_unknown || nintendoswitch package runtime // This file is for non-hosted environments, that don't support command line // parameters or environment variables. To still be able to run certain tests, // command line parameters and environment variables can be passed to the binary // by setting the variables `runtime.osArgs` and `runtime.osEnv`, both of which // are strings separated by newlines. // // The primary use case is `tinygo test`, which takes some parameters (such as // -test.v). // This is the default set of arguments, if nothing else has been set. var args = []string{"/proc/self/exe"} //go:linkname os_runtime_args os.runtime_args func os_runtime_args() []string { return args } var env []string //go:linkname syscall_runtime_envs syscall.runtime_envs func syscall_runtime_envs() []string { return env } var osArgs string var osEnv string func init() { if osArgs != "" { s := osArgs start := 0 for i := 0; i < len(s); i++ { if s[i] == 0 { args = append(args, s[start:i]) start = i + 1 } } args = append(args, s[start:]) } if osEnv != "" { s := osEnv start := 0 for i := 0; i < len(s); i++ { if s[i] == 0 { env = append(env, s[start:i]) start = i + 1 } } env = append(env, s[start:]) } } ================================================ FILE: src/runtime/os_darwin.c ================================================ //go:build none // This file is included in the build, despite the //go:build line above. #include // Wrapper function because 'open' is a variadic function and variadic functions // use a different (incompatible) calling convention on darwin/arm64. // This function is referenced from the compiler, when it sees a // syscall.libc_open_trampoline function. int syscall_libc_open(const char *pathname, int flags, mode_t mode) { return open(pathname, flags, mode); } // The following functions are called by the runtime because Go can't call // function pointers directly. int tinygo_syscall(int (*fn)(uintptr_t a1, uintptr_t a2, uintptr_t a3), uintptr_t a1, uintptr_t a2, uintptr_t a3) { return fn(a1, a2, a3); } uintptr_t tinygo_syscallX(uintptr_t (*fn)(uintptr_t a1, uintptr_t a2, uintptr_t a3), uintptr_t a1, uintptr_t a2, uintptr_t a3) { return fn(a1, a2, a3); } int tinygo_syscall6(int (*fn)(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6), uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6) { return fn(a1, a2, a3, a4, a5, a6); } uintptr_t tinygo_syscall6X(uintptr_t (*fn)(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6), uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6) { return fn(a1, a2, a3, a4, a5, a6); } ================================================ FILE: src/runtime/os_darwin.go ================================================ //go:build darwin package runtime import "unsafe" const GOOS = "darwin" const ( // See https://github.com/golang/go/blob/master/src/syscall/zerrors_darwin_amd64.go flag_PROT_READ = 0x1 flag_PROT_WRITE = 0x2 flag_MAP_PRIVATE = 0x2 flag_MAP_ANONYMOUS = 0x1000 // MAP_ANON ) // Source: https://opensource.apple.com/source/Libc/Libc-1439.100.3/include/time.h.auto.html const ( clock_REALTIME = 0 clock_MONOTONIC_RAW = 4 ) // Source: // https://opensource.apple.com/source/xnu/xnu-7195.141.2/bsd/sys/signal.h.auto.html const ( sig_SIGBUS = 10 sig_SIGILL = 4 sig_SIGSEGV = 11 ) // https://opensource.apple.com/source/xnu/xnu-7195.141.2/EXTERNAL_HEADERS/mach-o/loader.h.auto.html type machHeader struct { magic uint32 cputype uint32 cpusubtype uint32 filetype uint32 ncmds uint32 sizeofcmds uint32 flags uint32 reserved uint32 } // Struct for the LC_SEGMENT_64 load command. type segmentLoadCommand struct { cmd uint32 // LC_SEGMENT_64 cmdsize uint32 segname [16]byte vmaddr uintptr vmsize uintptr fileoff uintptr filesize uintptr maxprot uint32 initprot uint32 nsects uint32 flags uint32 } // MachO header of the currently running process. // //go:extern _mh_execute_header var libc_mh_execute_header machHeader // Find global variables in .data/.bss sections. // The MachO linker doesn't seem to provide symbols for the start and end of the // data section. There is get_etext, get_edata, and get_end, but these are // undocumented and don't work with ASLR (which is enabled by default). // Therefore, read the MachO header directly. func findGlobals(found func(start, end uintptr)) { // Here is a useful blog post to understand the MachO file format: // https://h3adsh0tzz.com/2020/01/macho-file-format/ const ( MH_MAGIC_64 = 0xfeedfacf LC_SEGMENT_64 = 0x19 VM_PROT_WRITE = 0x02 ) // Sanity check that we're actually looking at a MachO header. if gcAsserts && libc_mh_execute_header.magic != MH_MAGIC_64 { runtimePanic("gc: unexpected MachO header") } // Iterate through the load commands. // Because we're only interested in LC_SEGMENT_64 load commands, cast the // pointer to that struct in advance. var offset uintptr var hasOffset bool cmd := (*segmentLoadCommand)(unsafe.Pointer(uintptr(unsafe.Pointer(&libc_mh_execute_header)) + unsafe.Sizeof(machHeader{}))) for i := libc_mh_execute_header.ncmds; i != 0; i-- { if cmd.cmd == LC_SEGMENT_64 { if cmd.fileoff == 0 && cmd.nsects != 0 { // Detect ASLR offset by checking fileoff and nsects. This // locates the __TEXT segment. This matches getsectiondata: // https://opensource.apple.com/source/cctools/cctools-973.0.1/libmacho/getsecbyname.c.auto.html offset = uintptr(unsafe.Pointer(&libc_mh_execute_header)) - cmd.vmaddr hasOffset = true } if cmd.maxprot&VM_PROT_WRITE != 0 { // Found a writable segment, which may contain Go globals. if gcAsserts && !hasOffset { // No ASLR offset detected. Did the __TEXT segment come // after the __DATA segment? // Note that when ASLR is disabled (for example, when // running inside lldb), the offset is zero. That's why we // need a separate hasOffset for this assert. runtimePanic("gc: did not detect ASLR offset") } // Scan this segment for GC roots. // This could be improved by only reading the memory areas // covered by sections. That would reduce the amount of memory // scanned a little bit (up to a single VM page). found(offset+cmd.vmaddr, offset+cmd.vmaddr+cmd.vmsize) } } // Move on to the next load command (which may or may not be a // LC_SEGMENT_64). cmd = (*segmentLoadCommand)(unsafe.Add(unsafe.Pointer(cmd), cmd.cmdsize)) } } func hardwareRand() (n uint64, ok bool) { n |= uint64(libc_arc4random()) n |= uint64(libc_arc4random()) << 32 return n, true } //go:linkname syscall_Getpagesize syscall.Getpagesize func syscall_Getpagesize() int { return int(libc_getpagesize()) } // Call "system calls" (actually: libc functions) in a special way. // - Most calls calls return a C int (which is 32-bits), and -1 on failure. // - syscallX* is for functions that return a 64-bit integer (and also return // -1 on failure). // - syscallPtr is for functions that return a pointer on success or NULL on // failure. // - rawSyscall seems to avoid some stack modifications, which isn't relevant // to TinyGo. //go:linkname syscall_syscall syscall.syscall func syscall_syscall(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) { // For TinyGo we don't need to do anything special to call C functions. return syscall_rawSyscall(fn, a1, a2, a3) } //go:linkname syscall_rawSyscall syscall.rawSyscall func syscall_rawSyscall(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) { result := call_syscall(fn, a1, a2, a3) r1 = uintptr(result) if result == -1 { // Syscall returns -1 on failure. err = uintptr(*libc_errno_location()) } return } //go:linkname syscall_syscallX syscall.syscallX func syscall_syscallX(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) { r1 = call_syscallX(fn, a1, a2, a3) if int64(r1) == -1 { // Syscall returns -1 on failure. err = uintptr(*libc_errno_location()) } return } //go:linkname syscall_syscallPtr syscall.syscallPtr func syscall_syscallPtr(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) { r1 = call_syscallX(fn, a1, a2, a3) if r1 == 0 { // Syscall returns a pointer on success, or NULL on failure. err = uintptr(*libc_errno_location()) } return } //go:linkname syscall_syscall6 syscall.syscall6 func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) { result := call_syscall6(fn, a1, a2, a3, a4, a5, a6) r1 = uintptr(result) if result == -1 { // Syscall returns -1 on failure. err = uintptr(*libc_errno_location()) } return } //go:linkname syscall_syscall6X syscall.syscall6X func syscall_syscall6X(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) { r1 = call_syscall6X(fn, a1, a2, a3, a4, a5, a6) if int64(r1) == -1 { // Syscall returns -1 on failure. err = uintptr(*libc_errno_location()) } return } // uint32_t arc4random(void); // //export arc4random func libc_arc4random() uint32 // int getpagesize(void); // //export getpagesize func libc_getpagesize() int32 // This function returns the error location in the darwin ABI. // Discovered by compiling the following code using Clang: // // #include // int getErrno() { // return errno; // } // //export __error func libc_errno_location() *int32 //export tinygo_syscall func call_syscall(fn, a1, a2, a3 uintptr) int32 //export tinygo_syscallX func call_syscallX(fn, a1, a2, a3 uintptr) uintptr //export tinygo_syscall6 func call_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) int32 //export tinygo_syscall6X func call_syscall6X(fn, a1, a2, a3, a4, a5, a6 uintptr) uintptr //go:linkname os_runtime_executable_path os.runtime_executable_path func os_runtime_executable_path() string { argv := (*unsafe.Pointer)(unsafe.Pointer(main_argv)) // skip over argv argv = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(argv), (uintptr(main_argc)+1)*unsafe.Sizeof(argv))) // skip over envv for (*argv) != nil { argv = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(argv), unsafe.Sizeof(argv))) } // next string is exe path argv = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(argv), unsafe.Sizeof(argv))) cstr := unsafe.Pointer(*argv) length := strlen(cstr) argString := _string{ length: length, ptr: (*byte)(cstr), } executablePath := *(*string)(unsafe.Pointer(&argString)) // strip "executable_path=" prefix if available, it's added after OS X 10.11. executablePath = stringsTrimPrefix(executablePath, "executable_path=") return executablePath } func stringsTrimPrefix(s, prefix string) string { if len(s) >= len(prefix) && s[:len(prefix)] == prefix { return s[len(prefix):] } return s } ================================================ FILE: src/runtime/os_js.go ================================================ //go:build js package runtime const GOOS = "js" ================================================ FILE: src/runtime/os_linux.go ================================================ //go:build linux && !baremetal && !nintendoswitch && !wasip1 && !wasm_unknown && !wasip2 package runtime // This file is for systems that are _actually_ Linux (not systems that pretend // to be Linux, like baremetal systems). import ( "unsafe" ) const GOOS = "linux" const ( // See https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/mman-common.h flag_PROT_READ = 0x1 flag_PROT_WRITE = 0x2 flag_MAP_PRIVATE = 0x2 flag_MAP_ANONYMOUS = linux_MAP_ANONYMOUS // different on alpha, hppa, mips, xtensa ) // Source: https://github.com/torvalds/linux/blob/master/include/uapi/linux/time.h const ( clock_REALTIME = 0 clock_MONOTONIC_RAW = 4 ) const ( sig_SIGBUS = linux_SIGBUS sig_SIGILL = linux_SIGILL sig_SIGSEGV = linux_SIGSEGV ) // For the definition of the various header structs, see: // https://refspecs.linuxfoundation.org/elf/elf.pdf // Also useful: // https://en.wikipedia.org/wiki/Executable_and_Linkable_Format type elfHeader struct { ident_magic uint32 ident_class uint8 ident_data uint8 ident_version uint8 ident_osabi uint8 ident_abiversion uint8 _ [7]byte // reserved filetype uint16 machine uint16 version uint32 entry uintptr phoff uintptr shoff uintptr flags uint32 ehsize uint16 phentsize uint16 phnum uint16 shentsize uint16 shnum uint16 shstrndx uint16 } type elfProgramHeader64 struct { _type uint32 flags uint32 offset uintptr vaddr uintptr paddr uintptr filesz uintptr memsz uintptr align uintptr } type elfProgramHeader32 struct { _type uint32 offset uintptr vaddr uintptr paddr uintptr filesz uintptr memsz uintptr flags uint32 align uintptr } // ELF header of the currently running process. // //go:extern __ehdr_start var ehdr_start elfHeader // int *__errno_location(void); // //export __errno_location func libc_errno_location() *int32 // findGlobals finds globals in the .data/.bss sections. // It parses the ELF program header to find writable segments. func findGlobals(found func(start, end uintptr)) { // Relevant constants from the ELF specification. // See: https://refspecs.linuxfoundation.org/elf/elf.pdf const ( PT_LOAD = 1 PF_W = 0x2 // program flag: write access ) headerPtr := unsafe.Pointer(uintptr(unsafe.Pointer(&ehdr_start)) + ehdr_start.phoff) for i := 0; i < int(ehdr_start.phnum); i++ { // Look for a writable segment and scan its contents. // There is a little bit of duplication here, which is unfortunate. But // the alternative would be to put elfProgramHeader in separate files // which is IMHO a lot uglier. If only the ELF spec was consistent // between 32-bit and 64-bit... if TargetBits == 64 { header := (*elfProgramHeader64)(headerPtr) if header._type == PT_LOAD && header.flags&PF_W != 0 { start := header.vaddr end := start + header.memsz found(start, end) } } else { header := (*elfProgramHeader32)(headerPtr) if header._type == PT_LOAD && header.flags&PF_W != 0 { start := header.vaddr end := start + header.memsz found(start, end) } } headerPtr = unsafe.Add(headerPtr, ehdr_start.phentsize) } } //export getpagesize func libc_getpagesize() int //go:linkname syscall_Getpagesize syscall.Getpagesize func syscall_Getpagesize() int { return libc_getpagesize() } func hardwareRand() (n uint64, ok bool) { read := libc_getrandom(unsafe.Pointer(&n), 8, 0) if read != 8 { return 0, false } return n, true } // ssize_t getrandom(void buf[.buflen], size_t buflen, unsigned int flags); // //export getrandom func libc_getrandom(buf unsafe.Pointer, buflen uintptr, flags uint32) uint32 // int fcntl(int fd, int cmd, int arg); // //export fcntl func libc_fcntl(fd int, cmd int, arg int) (ret int) func fcntl(fd int32, cmd int32, arg int32) (ret int32, errno int32) { ret = int32(libc_fcntl(int(fd), int(cmd), int(arg))) errno = *libc_errno_location() return } ================================================ FILE: src/runtime/os_other.go ================================================ //go:build linux && (baremetal || nintendoswitch || wasm_unknown) // Other systems that aren't operating systems supported by the Go toolchain // need to pretend to be an existing operating system. Linux seems like a good // choice for this for its wide hardware support. package runtime const GOOS = "linux" ================================================ FILE: src/runtime/os_wasip1.go ================================================ //go:build wasip1 package runtime // The actual GOOS=wasip1, as newly added in the Go 1.21 toolchain. // Previously we supported -target=wasi, but that was essentially faked by using // linux/arm instead because that was the closest thing that was already // supported in the Go standard library. const GOOS = "wasip1" ================================================ FILE: src/runtime/os_wasip2.go ================================================ //go:build wasip2 package runtime const GOOS = "wasip2" ================================================ FILE: src/runtime/os_windows.go ================================================ package runtime import "unsafe" const GOOS = "windows" //export GetModuleHandleExA func _GetModuleHandleExA(dwFlags uint32, lpModuleName unsafe.Pointer, phModule **exeHeader) bool // MS-DOS stub with PE header offset: // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#ms-dos-stub-image-only type exeHeader struct { signature uint16 _ [58]byte // skip DOS header peHeader uint32 // at offset 0x3C } // COFF file header: // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#file-headers type peHeader struct { magic uint32 machine uint16 numberOfSections uint16 timeDateStamp uint32 pointerToSymbolTable uint32 numberOfSymbols uint32 sizeOfOptionalHeader uint16 characteristics uint16 } // COFF section header: // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#section-table-section-headers type peSection struct { name [8]byte virtualSize uint32 virtualAddress uint32 sizeOfRawData uint32 pointerToRawData uint32 pointerToRelocations uint32 pointerToLinenumbers uint32 numberOfRelocations uint16 numberOfLinenumbers uint16 characteristics uint32 } var module *exeHeader // Mark global variables. // Unfortunately, the linker doesn't provide symbols for the start and end of // the data/bss sections. Therefore these addresses need to be determined at // runtime. This might seem complex and it kind of is, but it only compiles to // around 160 bytes of amd64 instructions. // Most of this function is based on the documentation in // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format. func findGlobals(found func(start, end uintptr)) { // Constants used in this function. const ( // https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandleexa GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 0x00000002 // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format IMAGE_SCN_MEM_WRITE = 0x80000000 ) if module == nil { // Obtain a handle to the currently executing image. What we're getting // here is really just __ImageBase, but it's probably better to obtain // it using GetModuleHandle to account for ASLR etc. result := _GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, nil, &module) if gcAsserts && (!result || module.signature != 0x5A4D) { // 0x4D5A is "MZ" runtimePanic("cannot get module handle") } } // Find the PE header at offset 0x3C. pe := (*peHeader)(unsafe.Add(unsafe.Pointer(module), module.peHeader)) if gcAsserts && pe.magic != 0x00004550 { // 0x4550 is "PE" runtimePanic("cannot find PE header") } // Iterate through sections. section := (*peSection)(unsafe.Pointer(uintptr(unsafe.Pointer(pe)) + uintptr(pe.sizeOfOptionalHeader) + unsafe.Sizeof(peHeader{}))) for i := 0; i < int(pe.numberOfSections); i++ { if section.characteristics&IMAGE_SCN_MEM_WRITE != 0 { // Found a writable section. Scan the entire section for roots. start := uintptr(unsafe.Pointer(module)) + uintptr(section.virtualAddress) end := uintptr(unsafe.Pointer(module)) + uintptr(section.virtualAddress) + uintptr(section.virtualSize) found(start, end) } section = (*peSection)(unsafe.Add(unsafe.Pointer(section), unsafe.Sizeof(peSection{}))) } } type systeminfo struct { anon0 [4]byte dwpagesize uint32 lpminimumapplicationaddress *byte lpmaximumapplicationaddress *byte dwactiveprocessormask uintptr dwnumberofprocessors uint32 dwprocessortype uint32 dwallocationgranularity uint32 wprocessorlevel uint16 wprocessorrevision uint16 } //export GetSystemInfo func _GetSystemInfo(lpSystemInfo unsafe.Pointer) //go:linkname syscall_Getpagesize syscall.Getpagesize func syscall_Getpagesize() int { var info systeminfo _GetSystemInfo(unsafe.Pointer(&info)) return int(info.dwpagesize) } //export _errno func libc_errno_location() *int32 ================================================ FILE: src/runtime/panic.go ================================================ package runtime import ( "internal/task" "runtime/interrupt" "tinygo" "unsafe" ) // trap is a compiler hint that this function cannot be executed. It is // translated into either a trap instruction or a call to abort(). // //export llvm.trap func trap() // Inline assembly stub. It is essentially C longjmp but modified a bit for the // purposes of TinyGo. It restores the stack pointer and jumps to the given pc. // //export tinygo_longjmp func tinygo_longjmp(frame *deferFrame) // Compiler intrinsic. // Returns whether recover is supported on the current architecture. func supportsRecover() bool // Compile intrinsic. // Returns which strategy is used. This is usually "print" but can be changed // using the -panic= compiler flag. func panicStrategy() uint8 // DeferFrame is a stack allocated object that stores information for the // current "defer frame", which is used in functions that use the `defer` // keyword. // The compiler knows about the JumpPC struct offset, so it should not be moved // without also updating compiler/defer.go. type deferFrame struct { JumpSP unsafe.Pointer // stack pointer to return to JumpPC unsafe.Pointer // pc to return to ExtraRegs [deferExtraRegs]unsafe.Pointer // extra registers (depending on the architecture) Previous *deferFrame // previous recover buffer pointer Panicking panicState // not panicking, panicking, or in Goexit PanicValue interface{} // panic value, might be nil for panic(nil) for example } type panicState uint8 const ( panicFalse panicState = iota panicTrue panicGoexit ) // Builtin function panic(msg), used as a compiler intrinsic. func _panic(message interface{}) { panicOrGoexit(message, panicTrue) } func panicOrGoexit(message interface{}, panicking panicState) { if panicStrategy() == tinygo.PanicStrategyTrap { trap() } // Note: recover is not supported inside interrupts. // (This could be supported, like defer, but we currently don't). if supportsRecover() && !interrupt.In() { frame := (*deferFrame)(task.Current().DeferFrame) if frame != nil { frame.PanicValue = message frame.Panicking = panicking tinygo_longjmp(frame) // unreachable } } if panicking == panicGoexit { // Call to Goexit() instead of a panic. // Exit the goroutine instead of printing a panic message. deadlock() } printstring("panic: ") printitf(message) printnl() abort() } // Cause a runtime panic, which is (currently) always a string. func runtimePanic(msg string) { // As long as this function is inined, llvm.returnaddress(0) will return // something sensible. runtimePanicAt(returnAddress(0), msg) } func runtimePanicAt(addr unsafe.Pointer, msg string) { if panicStrategy() == tinygo.PanicStrategyTrap { trap() } if hasReturnAddr { // Note: the string "panic: runtime error at " is also used in // runtime_cortexm_hardfault.go. It is kept the same so that the string // can be deduplicated by the compiler. printstring("panic: runtime error at ") printptr(uintptr(addr) - callInstSize) printstring(": ") } else { printstring("panic: runtime error: ") } printstring(msg) printnl() abort() } // Called at the start of a function that includes a deferred call. // It gets passed in the stack-allocated defer frame and configures it. // Note that the frame is not zeroed yet, so we need to initialize all values // that will be used. // //go:inline //go:nobounds func setupDeferFrame(frame *deferFrame, jumpSP unsafe.Pointer) { if interrupt.In() { // Defer is not currently allowed in interrupts. // We could add support for this, but since defer might also allocate // (especially in loops) it might not be a good idea anyway. runtimePanicAt(returnAddress(0), "defer in interrupt") } currentTask := task.Current() frame.Previous = (*deferFrame)(currentTask.DeferFrame) frame.JumpSP = jumpSP frame.Panicking = panicFalse currentTask.DeferFrame = unsafe.Pointer(frame) } // Called right before the return instruction. It pops the defer frame from the // linked list of defer frames. It also re-raises a panic if the goroutine is // still panicking. // //go:inline //go:nobounds func destroyDeferFrame(frame *deferFrame) { task.Current().DeferFrame = unsafe.Pointer(frame.Previous) if frame.Panicking != panicFalse { // We're still panicking! // Re-raise the panic now. panicOrGoexit(frame.PanicValue, frame.Panicking) } } // _recover is the built-in recover() function. It tries to recover a currently // panicking goroutine. // useParentFrame is set when the caller of runtime._recover has a defer frame // itself. In that case, recover() shouldn't check that frame but one frame up. func _recover(useParentFrame bool) interface{} { if !supportsRecover() || interrupt.In() { // Either we're compiling without stack unwinding support, or we're // inside an interrupt where panic/recover is not supported. Either way, // make this a no-op since panic() won't do any long jumps to a deferred // function. return nil } // TODO: somehow check that recover() is called directly by a deferred // function in a panicking goroutine. Maybe this can be done by comparing // the frame pointer? frame := (*deferFrame)(task.Current().DeferFrame) if useParentFrame { // Don't recover panic from the current frame (which can't be panicking // already), but instead from the previous frame. frame = frame.Previous } if frame != nil && frame.Panicking != panicFalse { if frame.Panicking == panicGoexit { // Special value that indicates we're exiting the goroutine using // Goexit(). Therefore, make this recover call a no-op. return nil } // Only the first call to recover returns the panic value. It also stops // the panicking sequence, hence setting panicking to false. frame.Panicking = panicFalse return frame.PanicValue } // Not panicking, so return a nil interface. return nil } // Panic when trying to dereference a nil pointer. func nilPanic() { runtimePanicAt(returnAddress(0), "nil pointer dereference") } // Panic when trying to add an entry to a nil map func nilMapPanic() { runtimePanicAt(returnAddress(0), "assignment to entry in nil map") } // Panic when trying to access an array or slice out of bounds. func lookupPanic() { runtimePanicAt(returnAddress(0), "index out of range") } // Panic when trying to slice a slice out of bounds. func slicePanic() { runtimePanicAt(returnAddress(0), "slice out of range") } // Panic when trying to convert a slice to an array pointer (Go 1.17+) and the // slice is shorter than the array. func sliceToArrayPointerPanic() { runtimePanicAt(returnAddress(0), "slice smaller than array") } // Panic when calling unsafe.Slice() (Go 1.17+) or unsafe.String() (Go 1.20+) // with a len that's too large (which includes if the ptr is nil and len is // nonzero). func unsafeSlicePanic() { runtimePanicAt(returnAddress(0), "unsafe.Slice/String: len out of range") } // Panic when trying to create a new channel that is too big. func chanMakePanic() { runtimePanicAt(returnAddress(0), "new channel is too big") } // Panic when a shift value is negative. func negativeShiftPanic() { runtimePanicAt(returnAddress(0), "negative shift") } // Panic when there is a divide by zero. func divideByZeroPanic() { runtimePanicAt(returnAddress(0), "divide by zero") } func blockingPanic() { runtimePanicAt(returnAddress(0), "trying to do blocking operation in exported function") } //go:linkname fips_fatal crypto/internal/fips140.fatal func fips_fatal(msg string) { runtimePanic(msg) } ================================================ FILE: src/runtime/poll.go ================================================ package runtime // This file implements stub functions for internal/poll. //go:linkname poll_runtime_pollServerInit internal/poll.runtime_pollServerInit func poll_runtime_pollServerInit() { panic("todo: runtime_pollServerInit") } //go:linkname poll_runtime_pollOpen internal/poll.runtime_pollOpen func poll_runtime_pollOpen(fd uintptr) (uintptr, int) { panic("todo: runtime_pollOpen") } //go:linkname poll_runtime_pollClose internal/poll.runtime_pollClose func poll_runtime_pollClose(ctx uintptr) { panic("todo: runtime_pollClose") } //go:linkname poll_runtime_pollUnblock internal/poll.runtime_pollUnblock func poll_runtime_pollUnblock(ctx uintptr) { panic("todo: runtime_pollUnblock") } ================================================ FILE: src/runtime/pprof/pprof.go ================================================ package pprof // TinyGo does not implement pprof. However, a dummy shell is needed for the // testing package (and testing/internal/pprof). import ( "errors" "io" ) var ErrUnimplemented = errors.New("runtime/pprof: unimplemented") type Profile struct{} func StartCPUProfile(w io.Writer) error { return nil } func StopCPUProfile() { } func WriteHeapProfile(w io.Writer) error { return nil } func Lookup(name string) *Profile { return nil } func (p *Profile) Name() string { return "" } func (p *Profile) Count() int { return 0 } func (p *Profile) WriteTo(w io.Writer, debug int) error { return ErrUnimplemented } func Profiles() []*Profile { return nil } ================================================ FILE: src/runtime/print.go ================================================ package runtime import ( "unsafe" ) type stringer interface { String() string } //go:nobounds func printstring(s string) { for i := 0; i < len(s); i++ { putchar(s[i]) } } func printuint8(n uint8) { if TargetBits >= 32 { printuint32(uint32(n)) } else { prevdigits := n / 10 if prevdigits != 0 { printuint8(prevdigits) } putchar(byte((n % 10) + '0')) } } func printint8(n int8) { if TargetBits >= 32 { printint32(int32(n)) } else { if n < 0 { putchar('-') n = -n } printuint8(uint8(n)) } } func printuintptr(n uintptr) { switch unsafe.Sizeof(n) { case 2: printuint16(uint16(n)) case 4: printuint32(uint32(n)) case 8: printuint64(uint64(n)) } } func printuint16(n uint16) { printuint32(uint32(n)) } func printint16(n int16) { printint32(int32(n)) } func printuint32(n uint32) { printuint64(uint64(n)) } func printint32(n int32) { // Print integer in signed big-endian base-10 notation, for humans to // read. if n < 0 { putchar('-') n = -n } printuint32(uint32(n)) } //go:nobounds func printuint64(n uint64) { digits := [20]byte{} // enough to hold (2^64)-1 // Fill in all 10 digits. firstdigit := 19 // digit index that isn't zero (by default, the last to handle '0' correctly) for i := 19; i >= 0; i-- { digit := byte(n%10 + '0') digits[i] = digit if digit != '0' { firstdigit = i } n /= 10 } // Print digits without the leading zeroes. for i := firstdigit; i < 20; i++ { putchar(digits[i]) } } func printint64(n int64) { if n < 0 { putchar('-') n = -n } printuint64(uint64(n)) } // printfloat32() was copied from the relevant source in the original Go // implementation and modified to work with float32 instead of float64. It is // copyright by the Go authors, licensed under the same BSD 3-clause license. // See https://golang.org/LICENSE for details. // // It is a near-duplicate of printfloat64. This is done so that printing a // float32 value doesn't involve float64 routines, which can be unexpected and a // problem sometimes. It comes with a possible code size reduction if both // printfloat32 and printfloat64 are used, which seems uncommon. // // Source: // https://github.com/golang/go/blob/master/src/runtime/print.go func printfloat32(v float32) { switch { case v != v: printstring("NaN") return case v+v == v && v > 0: printstring("+Inf") return case v+v == v && v < 0: printstring("-Inf") return } const n = 7 // digits printed var buf [n + 7]byte buf[0] = '+' e := 0 // exp if v == 0 { if 1/v < 0 { buf[0] = '-' } } else { if v < 0 { v = -v buf[0] = '-' } // normalize for v >= 10 { e++ v /= 10 } for v < 1 { e-- v *= 10 } // round h := float32(5.0) for i := 0; i < n; i++ { h /= 10 } v += h if v >= 10 { e++ v /= 10 } } // format +d.dddd+edd for i := 0; i < n; i++ { s := int(v) buf[i+2] = byte(s + '0') v -= float32(s) v *= 10 } buf[1] = buf[2] buf[2] = '.' buf[n+2] = 'e' buf[n+3] = '+' if e < 0 { e = -e buf[n+3] = '-' } buf[n+4] = byte(e/100) + '0' buf[n+5] = byte(e/10)%10 + '0' buf[n+6] = byte(e%10) + '0' for _, c := range buf { putchar(c) } } // printfloat64() was copied from the relevant source in the original Go // implementation. It is copyright by the Go authors, licensed under the same // BSD 3-clause license. See https://golang.org/LICENSE for details. // // Source: // https://github.com/golang/go/blob/master/src/runtime/print.go func printfloat64(v float64) { switch { case v != v: printstring("NaN") return case v+v == v && v > 0: printstring("+Inf") return case v+v == v && v < 0: printstring("-Inf") return } const n = 7 // digits printed var buf [n + 7]byte buf[0] = '+' e := 0 // exp if v == 0 { if 1/v < 0 { buf[0] = '-' } } else { if v < 0 { v = -v buf[0] = '-' } // normalize for v >= 10 { e++ v /= 10 } for v < 1 { e-- v *= 10 } // round h := 5.0 for i := 0; i < n; i++ { h /= 10 } v += h if v >= 10 { e++ v /= 10 } } // format +d.dddd+edd for i := 0; i < n; i++ { s := int(v) buf[i+2] = byte(s + '0') v -= float64(s) v *= 10 } buf[1] = buf[2] buf[2] = '.' buf[n+2] = 'e' buf[n+3] = '+' if e < 0 { e = -e buf[n+3] = '-' } buf[n+4] = byte(e/100) + '0' buf[n+5] = byte(e/10)%10 + '0' buf[n+6] = byte(e%10) + '0' for _, c := range buf { putchar(c) } } func printcomplex64(c complex64) { putchar('(') printfloat32(real(c)) printfloat32(imag(c)) printstring("i)") } func printcomplex128(c complex128) { putchar('(') printfloat64(real(c)) printfloat64(imag(c)) printstring("i)") } func printspace() { putchar(' ') } func printnl() { if baremetal { putchar('\r') } putchar('\n') } func printitf(msg interface{}) { switch msg := msg.(type) { case bool: printbool(msg) case int: switch unsafe.Sizeof(msg) { case 8: printint64(int64(msg)) case 4: printint32(int32(msg)) } case int8: printint8(msg) case int16: printint16(msg) case int32: printint32(msg) case int64: printint64(msg) case uint: switch unsafe.Sizeof(msg) { case 8: printuint64(uint64(msg)) case 4: printuint32(uint32(msg)) } case uint8: printuint8(msg) case uint16: printuint16(msg) case uint32: printuint32(msg) case uint64: printuint64(msg) case uintptr: printuintptr(msg) case float32: printfloat32(msg) case float64: printfloat64(msg) case complex64: printcomplex64(msg) case complex128: printcomplex128(msg) case string: printstring(msg) case error: printstring(msg.Error()) case stringer: printstring(msg.String()) default: // cast to underlying type itf := *(*_interface)(unsafe.Pointer(&msg)) putchar('(') printuintptr(uintptr(itf.typecode)) putchar(':') printptr(uintptr(itf.value)) putchar(')') } } func printmap(m *hashmap) { printstring("map[") if m == nil { printstring("nil") } else { switch unsafe.Sizeof(m.count) { case 8: printuint64(uint64(m.count)) case 4: printuint32(uint32(m.count)) case 2: printuint16(uint16(m.count)) } } putchar(']') } func printptr(ptr uintptr) { if ptr == 0 { printstring("nil") return } putchar('0') putchar('x') for i := 0; i < int(unsafe.Sizeof(ptr))*2; i++ { nibble := byte(ptr >> (unsafe.Sizeof(ptr)*8 - 4)) if nibble < 10 { putchar(nibble + '0') } else { putchar(nibble - 10 + 'a') } ptr <<= 4 } } func printbool(b bool) { if b { printstring("true") } else { printstring("false") } } func printslice(ptr, len_, cap_ uintptr) { putchar('[') printuintptr(len_) putchar('/') printuintptr(cap_) putchar(']') printptr(ptr) } ================================================ FILE: src/runtime/proc.go ================================================ // Copyright 2014 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package runtime // Called from syscall package before Exec. // //go:linkname syscall_runtime_BeforeExec syscall.runtime_BeforeExec func syscall_runtime_BeforeExec() { // Used in BigGo to serialize exec / thread creation. Stubbing to // satisfy link. } // Called from syscall package after Exec. // //go:linkname syscall_runtime_AfterExec syscall.runtime_AfterExec func syscall_runtime_AfterExec() { } ================================================ FILE: src/runtime/rand.go ================================================ package runtime import _ "unsafe" // TODO: Use hardware when available // //go:linkname vgetrandom func vgetrandom(p []byte, flags uint32) (ret int, supported bool) { return 0, false } //go:linkname fatal crypto/internal/sysrand.fatal func fatal(msg string) { runtimePanic(msg) } ================================================ FILE: src/runtime/rand_hwrng.go ================================================ //go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt) || rp2040 || rp2350) // If you update the above build constraint, you'll probably also need to update // src/crypto/rand/rand_baremetal.go. // // The rp2040 and rp2350 implementations are not included in src/crypto/rand/rand_baremetal.go // due to not being sufficiently random for the Go crypto libs. // However since the randomness here does not provide those same guarantees, // they are included in the list for hardwareRand() implementations. package runtime import "machine" func hardwareRand() (n uint64, ok bool) { n1, err1 := machine.GetRNG() n2, err2 := machine.GetRNG() n = uint64(n1)<<32 | uint64(n2) ok = err1 == nil && err2 == nil return } ================================================ FILE: src/runtime/rand_norng.go ================================================ //go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt) || rp2040 || rp2350) package runtime func hardwareRand() (n uint64, ok bool) { return 0, false // no RNG available } ================================================ FILE: src/runtime/runtime.go ================================================ package runtime import ( "unsafe" ) //go:generate go run ../../tools/gen-critical-atomics -out ./atomics_critical.go const Compiler = "tinygo" // Unit for the 'ticks' and 'sleepTicks' functions. // // This is the native time unit for the given system. One timeUnit tick might be // 1ns or 100ns on a desktop system, or 1/32768s on baremetal systems with a // low-power RTC. Many other tick durations are possible. // // Conversion from time units to nanoseconds and back is done using // ticksToNanoseconds and nanosecondsToTicks, which need to be implemented for // each system as needed. type timeUnit int64 // The compiler will fill this with calls to the initialization function of each // package. func initAll() //go:linkname callMain main.main func callMain() func GOMAXPROCS(n int) int { // Note: setting GOMAXPROCS is ignored. return 1 } func GOROOT() string { // TODO: don't hardcode but take the one at compile time. return "/usr/local/go" } // Copy size bytes from src to dst. The memory areas must not overlap. // This function is implemented by the compiler as a call to a LLVM intrinsic // like llvm.memcpy.p0.p0.i32(dst, src, size, false). func memcpy(dst, src unsafe.Pointer, size uintptr) // Copy size bytes from src to dst. The memory areas may overlap and will do the // correct thing. // This function is implemented by the compiler as a call to a LLVM intrinsic // like llvm.memmove.p0.p0.i32(dst, src, size, false). func memmove(dst, src unsafe.Pointer, size uintptr) // Set the given number of bytes to zero. // This function is implemented by the compiler as a call to a LLVM intrinsic // like llvm.memset.p0.i32(ptr, 0, size, false). func memzero(ptr unsafe.Pointer, size uintptr) // Return the current stack pointer using the llvm.stacksave.p0 intrinsic. // It is normally used together with llvm.stackrestore.p0 but also works to get // the current stack pointer in a platform-independent way. func stacksave() unsafe.Pointer // Special LLVM intrinsic that returns the SP register on entry to the calling // function. // //export llvm.sponentry.p0 func llvm_sponentry() unsafe.Pointer //export strlen func strlen(ptr unsafe.Pointer) uintptr //export malloc func malloc(size uintptr) unsafe.Pointer // Return the address of an exported function. // This is mainly useful to pass a function pointer without extra context // parameter to C, for example. func exportedFuncPtr(fn func()) uintptr // Compare two same-size buffers for equality. func memequal(x, y unsafe.Pointer, n uintptr) bool { for i := uintptr(0); i < n; i++ { cx := *(*uint8)(unsafe.Add(x, i)) cy := *(*uint8)(unsafe.Add(y, i)) if cx != cy { return false } } return true } func nanotime() int64 { return ticksToNanoseconds(ticks()) } // Copied from the Go runtime source code. // //go:linkname os_sigpipe os.sigpipe func os_sigpipe() { runtimePanic("too many writes on closed pipe") } // LockOSThread wires the calling goroutine to its current operating system thread. // Stub for now // Called by go1.18 standard library on windows, see https://github.com/golang/go/issues/49320 func LockOSThread() { } // UnlockOSThread undoes an earlier call to LockOSThread. // Stub for now func UnlockOSThread() { } // KeepAlive makes sure the value in the interface is alive until at least the // point of the call. func KeepAlive(x interface{}) // AddCleanup is a dummy cleanup implementation. It doesn't do any cleaning up. // // We base this on the following loophole in the official runtime.AddCleanup // documentation: // // > The cleanup(arg) call is not always guaranteed to run; in particular it is // > not guaranteed to run before program exit. // // So it's technically correct (the best kind of correct) to not run any // cleanups. But of course, this can lead to resource leaks so cleanups may need // to be implemented eventually. func AddCleanup[T, S any](ptr *T, cleanup func(S), arg S) Cleanup { return Cleanup{} } type Cleanup struct{} func (c Cleanup) Stop() {} //go:linkname registerWeakPointer weak.runtime_registerWeakPointer func registerWeakPointer(ptr unsafe.Pointer) unsafe.Pointer { // TODO: unimplemented. // I hope not implementing this won't break anything, like packages that // expect weak pointers to be GC'd before they actually are. return ptr } var godebugUpdate func(string, string) //go:linkname godebug_setUpdate internal/godebug.setUpdate func godebug_setUpdate(update func(string, string)) { // The 'update' function needs to be called whenever the GODEBUG environment // variable changes (for example, via os.Setenv). godebugUpdate = update } //go:linkname godebug_setNewIncNonDefault internal/godebug.setNewIncNonDefault func godebug_setNewIncNonDefault(newIncNonDefault func(string) func()) { // Dummy function necessary in Go 1.21. } // Write to the given file descriptor. // This is called from internal/godebug starting with Go 1.21, and only seems to // be called with the stderr file descriptor. func write(fd uintptr, p unsafe.Pointer, n int32) int32 { if fd == 2 { // stderr // Convert to a string, because we know that p won't change during the // call to printstring. // TODO: use unsafe.String instead once we require Go 1.20. s := _string{ ptr: (*byte)(p), length: uintptr(n), } str := *(*string)(unsafe.Pointer(&s)) printstring(str) return n } return 0 } // getAuxv is linknamed from golang.org/x/sys/cpu. func getAuxv() []uintptr { return nil } // Called from cgo to obtain the errno value. func cgo_errno() uintptr { return uintptr(*libc_errno_location()) } ================================================ FILE: src/runtime/runtime_arm7tdmi.go ================================================ //go:build arm7tdmi package runtime import ( _ "runtime/interrupt" // make sure the interrupt handler is defined "unsafe" ) func putchar(c byte) { // dummy, TODO } func getchar() byte { // dummy, TODO return 0 } func buffered() int { // dummy, TODO return 0 } //go:extern _sbss var _sbss [0]byte //go:extern _ebss var _ebss [0]byte //go:extern _sdata var _sdata [0]byte //go:extern _sidata var _sidata [0]byte //go:extern _edata var _edata [0]byte // Entry point for Go. Initialize all packages and call main.main(). // //export main func main() { // Initialize .data and .bss sections. preinit() // Run program. run() } func preinit() { // Initialize .bss: zero-initialized global variables. ptr := unsafe.Pointer(&_sbss) for ptr != unsafe.Pointer(&_ebss) { *(*uint32)(ptr) = 0 ptr = unsafe.Add(ptr, 4) } // Initialize .data: global variables initialized from flash. src := unsafe.Pointer(&_sidata) dst := unsafe.Pointer(&_sdata) for dst != unsafe.Pointer(&_edata) { *(*uint32)(dst) = *(*uint32)(src) dst = unsafe.Add(dst, 4) src = unsafe.Add(src, 4) } } func ticksToNanoseconds(ticks timeUnit) int64 { return int64(ticks) } func nanosecondsToTicks(ns int64) timeUnit { return timeUnit(ns) } func ticks() timeUnit { // TODO return 0 } func sleepTicks(d timeUnit) { // TODO } func exit(code int) { abort() } func abort() { // TODO for { } } ================================================ FILE: src/runtime/runtime_atmega.go ================================================ //go:build avr && (atmega || atmega32u4) package runtime import ( "device/avr" "machine" ) func initUART() { machine.InitSerial() } func putchar(c byte) { machine.Serial.WriteByte(c) } func getchar() byte { for machine.Serial.Buffered() == 0 { Gosched() } v, _ := machine.Serial.ReadByte() return v } func buffered() int { return machine.Serial.Buffered() } // Sleep for a given period. The period is defined by the WDT peripheral, and is // on most chips (at least) 3 bits wide, in powers of two from 16ms to 2s // (0=16ms, 1=32ms, 2=64ms...). Note that the WDT is not very accurate: it can // be off by a large margin depending on temperature and supply voltage. // // TODO: disable more peripherals etc. to reduce sleep current. func sleepWDT(period uint8) { // Configure WDT avr.Asm("cli") avr.Asm("wdr") // Start timed sequence. avr.WDTCSR.SetBits(avr.WDTCSR_WDCE | avr.WDTCSR_WDE) // Enable WDT and set new timeout avr.WDTCSR.SetBits(avr.WDTCSR_WDIE | period) avr.Asm("sei") // Set sleep mode to idle and enable sleep mode. // Note: when using something other than idle, the UART won't work // correctly. This needs to be fixed, though, so we can truly sleep. avr.SMCR.Set((0 << 1) | avr.SMCR_SE) // go to sleep avr.Asm("sleep") // disable sleep avr.SMCR.Set(0) } ================================================ FILE: src/runtime/runtime_atsamd21.go ================================================ //go:build sam && atsamd21 package runtime import ( "device/arm" "device/sam" "machine" _ "machine/usb/cdc" "runtime/interrupt" "runtime/volatile" "unsafe" ) //export Reset_Handler func main() { preinit() run() exit(0) } func init() { initClocks() initRTC() initSERCOMClocks() initUSBClock() initADCClock() machine.InitSerial() } func putchar(c byte) { machine.Serial.WriteByte(c) } func getchar() byte { for machine.Serial.Buffered() == 0 { Gosched() } v, _ := machine.Serial.ReadByte() return v } func buffered() int { return machine.Serial.Buffered() } func initClocks() { // Set 1 Flash Wait State for 48MHz, required for 3.3V operation according to SAMD21 Datasheet sam.NVMCTRL.CTRLB.SetBits(sam.NVMCTRL_CTRLB_RWS_HALF << sam.NVMCTRL_CTRLB_RWS_Pos) // Turn on the digital interface clock sam.PM.APBAMASK.SetBits(sam.PM_APBAMASK_GCLK_) // turn off RTC sam.PM.APBAMASK.ClearBits(sam.PM_APBAMASK_RTC_) // Enable OSC32K clock (Internal 32.768Hz oscillator). // This requires registers that are not included in the SVD file. // This is from samd21g18a.h and nvmctrl.h: // // #define NVMCTRL_OTP4 0x00806020 // // #define SYSCTRL_FUSES_OSC32K_CAL_ADDR (NVMCTRL_OTP4 + 4) // #define SYSCTRL_FUSES_OSC32K_CAL_Pos 6 /** (NVMCTRL_OTP4) OSC32K Calibration */ // #define SYSCTRL_FUSES_OSC32K_CAL_Msk (0x7Fu << SYSCTRL_FUSES_OSC32K_CAL_Pos) // #define SYSCTRL_FUSES_OSC32K_CAL(value) ((SYSCTRL_FUSES_OSC32K_CAL_Msk & ((value) << SYSCTRL_FUSES_OSC32K_CAL_Pos))) // u32_t fuse = *(u32_t *)FUSES_OSC32K_CAL_ADDR; // u32_t calib = (fuse & FUSES_OSC32K_CAL_Msk) >> FUSES_OSC32K_CAL_Pos; fuse := *(*uint32)(unsafe.Pointer(uintptr(0x00806020) + 4)) calib := (fuse & uint32(0x7f<<6)) >> 6 // SYSCTRL_OSC32K_CALIB(calib) | // SYSCTRL_OSC32K_STARTUP(0x6u) | // SYSCTRL_OSC32K_EN32K | SYSCTRL_OSC32K_ENABLE; sam.SYSCTRL.OSC32K.Set((calib << sam.SYSCTRL_OSC32K_CALIB_Pos) | (0x6 << sam.SYSCTRL_OSC32K_STARTUP_Pos) | sam.SYSCTRL_OSC32K_EN32K | sam.SYSCTRL_OSC32K_EN1K | sam.SYSCTRL_OSC32K_ENABLE) // Wait for oscillator stabilization for !sam.SYSCTRL.PCLKSR.HasBits(sam.SYSCTRL_PCLKSR_OSC32KRDY) { } // Software reset the module to ensure it is re-initialized correctly sam.GCLK.CTRL.Set(sam.GCLK_CTRL_SWRST) // Wait for reset to complete for sam.GCLK.CTRL.HasBits(sam.GCLK_CTRL_SWRST) && sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) { } // Put OSC32K as source of Generic Clock Generator 1 sam.GCLK.GENDIV.Set((1 << sam.GCLK_GENDIV_ID_Pos) | (0 << sam.GCLK_GENDIV_DIV_Pos)) waitForSync() // GCLK_GENCTRL_ID(1) | GCLK_GENCTRL_SRC_OSC32K | GCLK_GENCTRL_GENEN; sam.GCLK.GENCTRL.Set((1 << sam.GCLK_GENCTRL_ID_Pos) | (sam.GCLK_GENCTRL_SRC_OSC32K << sam.GCLK_GENCTRL_SRC_Pos) | sam.GCLK_GENCTRL_GENEN) waitForSync() // Use Generic Clock Generator 1 as source for Generic Clock Multiplexer 0 (DFLL48M reference) sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_DFLL48 << sam.GCLK_CLKCTRL_ID_Pos) | (sam.GCLK_CLKCTRL_GEN_GCLK1 << sam.GCLK_CLKCTRL_GEN_Pos) | sam.GCLK_CLKCTRL_CLKEN) waitForSync() // Remove the OnDemand mode, Bug http://avr32.icgroup.norway.atmel.com/bugzilla/show_bug.cgi?id=9905 sam.SYSCTRL.DFLLCTRL.Set(sam.SYSCTRL_DFLLCTRL_ENABLE) // Wait for ready for !sam.SYSCTRL.PCLKSR.HasBits(sam.SYSCTRL_PCLKSR_DFLLRDY) { } // Handle DFLL calibration based on info learned from Arduino SAMD implementation, // using value stored in fuse. // #define SYSCTRL_FUSES_DFLL48M_COARSE_CAL_ADDR (NVMCTRL_OTP4 + 4) // #define SYSCTRL_FUSES_DFLL48M_COARSE_CAL_Pos 26 /**< \brief (NVMCTRL_OTP4) DFLL48M Coarse Calibration */ // #define SYSCTRL_FUSES_DFLL48M_COARSE_CAL_Msk (0x3Fu << SYSCTRL_FUSES_DFLL48M_COARSE_CAL_Pos) // #define SYSCTRL_FUSES_DFLL48M_COARSE_CAL(value) ((SYSCTRL_FUSES_DFLL48M_COARSE_CAL_Msk & ((value) << SYSCTRL_FUSES_DFLL48M_COARSE_CAL_Pos))) coarse := (fuse >> 26) & 0x3F if coarse == 0x3f { coarse = 0x1f } sam.SYSCTRL.DFLLVAL.SetBits(coarse << sam.SYSCTRL_DFLLVAL_COARSE_Pos) sam.SYSCTRL.DFLLVAL.SetBits(0x1ff << sam.SYSCTRL_DFLLVAL_FINE_Pos) // Write full configuration to DFLL control register // SYSCTRL_DFLLMUL_CSTEP( 0x1f / 4 ) | // Coarse step is 31, half of the max value // SYSCTRL_DFLLMUL_FSTEP( 10 ) | // SYSCTRL_DFLLMUL_MUL( (48000) ) ; sam.SYSCTRL.DFLLMUL.Set(((31 / 4) << sam.SYSCTRL_DFLLMUL_CSTEP_Pos) | (10 << sam.SYSCTRL_DFLLMUL_FSTEP_Pos) | (48000 << sam.SYSCTRL_DFLLMUL_MUL_Pos)) // disable DFLL sam.SYSCTRL.DFLLCTRL.Set(0) waitForSync() sam.SYSCTRL.DFLLCTRL.SetBits(sam.SYSCTRL_DFLLCTRL_MODE | sam.SYSCTRL_DFLLCTRL_CCDIS | sam.SYSCTRL_DFLLCTRL_USBCRM | sam.SYSCTRL_DFLLCTRL_BPLCKC) // Wait for ready for !sam.SYSCTRL.PCLKSR.HasBits(sam.SYSCTRL_PCLKSR_DFLLRDY) { } // Re-enable the DFLL sam.SYSCTRL.DFLLCTRL.SetBits(sam.SYSCTRL_DFLLCTRL_ENABLE) // Wait for ready for !sam.SYSCTRL.PCLKSR.HasBits(sam.SYSCTRL_PCLKSR_DFLLRDY) { } // Switch Generic Clock Generator 0 to DFLL48M. CPU will run at 48MHz. sam.GCLK.GENDIV.Set((0 << sam.GCLK_GENDIV_ID_Pos) | (0 << sam.GCLK_GENDIV_DIV_Pos)) waitForSync() sam.GCLK.GENCTRL.Set((0 << sam.GCLK_GENCTRL_ID_Pos) | (sam.GCLK_GENCTRL_SRC_DFLL48M << sam.GCLK_GENCTRL_SRC_Pos) | sam.GCLK_GENCTRL_IDC | sam.GCLK_GENCTRL_GENEN) waitForSync() // Modify PRESCaler value of OSC8M to have 8MHz sam.SYSCTRL.OSC8M.SetBits(sam.SYSCTRL_OSC8M_PRESC_0 << sam.SYSCTRL_OSC8M_PRESC_Pos) sam.SYSCTRL.OSC8M.ClearBits(1 << sam.SYSCTRL_OSC8M_ONDEMAND_Pos) // Wait for oscillator stabilization for !sam.SYSCTRL.PCLKSR.HasBits(sam.SYSCTRL_PCLKSR_OSC8MRDY) { } // Use OSC8M as source for Generic Clock Generator 3 sam.GCLK.GENDIV.Set((3 << sam.GCLK_GENDIV_ID_Pos)) waitForSync() sam.GCLK.GENCTRL.Set((3 << sam.GCLK_GENCTRL_ID_Pos) | (sam.GCLK_GENCTRL_SRC_OSC8M << sam.GCLK_GENCTRL_SRC_Pos) | sam.GCLK_GENCTRL_GENEN) waitForSync() // Use OSC32K as source for Generic Clock Generator 2 // OSC32K/1 -> GCLK2 at 32KHz sam.GCLK.GENDIV.Set(2 << sam.GCLK_GENDIV_ID_Pos) waitForSync() sam.GCLK.GENCTRL.Set((2 << sam.GCLK_GENCTRL_ID_Pos) | (sam.GCLK_GENCTRL_SRC_OSC32K << sam.GCLK_GENCTRL_SRC_Pos) | sam.GCLK_GENCTRL_GENEN) waitForSync() // Use GCLK2 for RTC sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_RTC << sam.GCLK_CLKCTRL_ID_Pos) | (sam.GCLK_CLKCTRL_GEN_GCLK2 << sam.GCLK_CLKCTRL_GEN_Pos) | sam.GCLK_CLKCTRL_CLKEN) waitForSync() // Set the CPU, APBA, B, and C dividers sam.PM.CPUSEL.Set(sam.PM_CPUSEL_CPUDIV_DIV1) sam.PM.APBASEL.Set(sam.PM_APBASEL_APBADIV_DIV1) sam.PM.APBBSEL.Set(sam.PM_APBBSEL_APBBDIV_DIV1) sam.PM.APBCSEL.Set(sam.PM_APBCSEL_APBCDIV_DIV1) // Disable automatic NVM write operations sam.NVMCTRL.CTRLB.SetBits(sam.NVMCTRL_CTRLB_MANW) } func initRTC() { // turn on digital interface clock sam.PM.APBAMASK.SetBits(sam.PM_APBAMASK_RTC_) // disable RTC sam.RTC_MODE0.CTRL.Set(0) waitForSync() // reset RTC sam.RTC_MODE0.CTRL.SetBits(sam.RTC_MODE0_CTRL_SWRST) waitForSync() // set Mode0 to 32-bit counter (mode 0) with prescaler 1 and GCLK2 is 32KHz/1 sam.RTC_MODE0.CTRL.Set((sam.RTC_MODE0_CTRL_MODE_COUNT32 << sam.RTC_MODE0_CTRL_MODE_Pos) | (sam.RTC_MODE0_CTRL_PRESCALER_DIV1 << sam.RTC_MODE0_CTRL_PRESCALER_Pos)) waitForSync() // re-enable RTC sam.RTC_MODE0.CTRL.SetBits(sam.RTC_MODE0_CTRL_ENABLE) waitForSync() rtcInterrupt := interrupt.New(sam.IRQ_RTC, func(intr interrupt.Interrupt) { flags := sam.RTC_MODE0.INTFLAG.Get() if flags&sam.RTC_MODE0_INTENSET_CMP0 != 0 { // The timer (for a sleep) has expired. timerWakeup.Set(1) } if flags&sam.RTC_MODE0_INTENSET_OVF != 0 { // The 32-bit RTC timer has overflowed. rtcOverflows.Set(rtcOverflows.Get() + 1) } // Mark this interrupt has handled for CMP0 and OVF. sam.RTC_MODE0.INTFLAG.Set(sam.RTC_MODE0_INTENSET_CMP0 | sam.RTC_MODE0_INTENSET_OVF) }) sam.RTC_MODE0.INTENSET.Set(sam.RTC_MODE0_INTENSET_OVF) rtcInterrupt.SetPriority(0xc0) rtcInterrupt.Enable() } func waitForSync() { for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) { } } var rtcOverflows volatile.Register32 // number of times the RTC wrapped around var timerWakeup volatile.Register8 // ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds. func ticksToNanoseconds(ticks timeUnit) int64 { // The following calculation is actually the following, but with both sides // reduced to reduce the risk of overflow: // ticks * 1e9 / 32768 return int64(ticks) * 1953125 / 64 } // nanosecondsToTicks converts nanoseconds to RTC ticks (running at 32768Hz). func nanosecondsToTicks(ns int64) timeUnit { // The following calculation is actually the following, but with both sides // reduced to reduce the risk of overflow: // ns * 32768 / 1e9 return timeUnit(ns * 64 / 1953125) } // sleepTicks should sleep for d number of microseconds. func sleepTicks(d timeUnit) { for d != 0 { ticks := uint32(d) if d > 0xffff_ffff { ticks = 0xffff_ffff } if !timerSleep(ticks) { // Bail out early to handle a non-time interrupt. return } d -= timeUnit(ticks) } } // ticks returns the elapsed time since reset. func ticks() timeUnit { // For some ways of capturing the time atomically, see this thread: // https://www.eevblog.com/forum/microcontrollers/correct-timing-by-timer-overflow-count/msg749617/#msg749617 // Here, instead of re-reading the counter register if an overflow has been // detected, we simply try again because that results in smaller code. for { mask := interrupt.Disable() counter := readRTC() overflows := rtcOverflows.Get() hasOverflow := sam.RTC_MODE0.INTFLAG.Get()&sam.RTC_MODE0_INTENSET_OVF != 0 interrupt.Restore(mask) if hasOverflow { // There was an overflow while trying to capture the timer. // Try again. continue } // This is a 32-bit timer, so the number of timer overflows forms the // upper 32 bits of this timer. return timeUnit(overflows)<<32 + timeUnit(counter) } } func readRTC() uint32 { // request read of count sam.RTC_MODE0.READREQ.Set(sam.RTC_MODE0_READREQ_RREQ) waitForSync() return sam.RTC_MODE0.COUNT.Get() } // ticks are in microseconds // Returns true if the timer completed. // Returns false if another interrupt occurred which requires an early return to scheduler. func timerSleep(ticks uint32) bool { timerWakeup.Set(0) if ticks < 7 { // Due to around 6 clock ticks delay waiting for the register value to // sync, the minimum sleep value for the SAMD21 is 214us. // For related info, see: // https://community.atmel.com/comment/2507091#comment-2507091 ticks = 7 } // request read of count sam.RTC_MODE0.READREQ.Set(sam.RTC_MODE0_READREQ_RREQ) waitForSync() // set compare value cnt := sam.RTC_MODE0.COUNT.Get() sam.RTC_MODE0.COMP0.Set(uint32(cnt) + ticks) waitForSync() // enable IRQ for CMP0 compare sam.RTC_MODE0.INTENSET.Set(sam.RTC_MODE0_INTENSET_CMP0) wait: waitForEvents() if timerWakeup.Get() != 0 { return true } if hasScheduler { // The interurpt may have awoken a goroutine, so bail out early. // Disable IRQ for CMP0 compare. sam.RTC_MODE0.INTENCLR.Set(sam.RTC_MODE0_INTENSET_CMP0) return false } else { // This is running without a scheduler. // The application expects this to sleep the whole time. goto wait } } func initUSBClock() { // Turn on clock for USB sam.PM.APBBMASK.SetBits(sam.PM_APBBMASK_USB_) // Put Generic Clock Generator 0 as source for Generic Clock Multiplexer 6 (USB reference) sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_USB << sam.GCLK_CLKCTRL_ID_Pos) | (sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) | sam.GCLK_CLKCTRL_CLKEN) waitForSync() } func initADCClock() { // Turn on clock for ADC sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_ADC_) // Put Generic Clock Generator 0 as source for Generic Clock Multiplexer for ADC. sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_ADC << sam.GCLK_CLKCTRL_ID_Pos) | (sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) | sam.GCLK_CLKCTRL_CLKEN) waitForSync() } func waitForEvents() { arm.Asm("wfe") } ================================================ FILE: src/runtime/runtime_atsamd21e18.go ================================================ //go:build sam && atsamd21 && atsamd21e18 package runtime import ( "device/sam" ) func initSERCOMClocks() { // Turn on clock to SERCOM0 for UART0 sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_SERCOM0_) // Use GCLK0 for SERCOM0 aka UART0 // GCLK_CLKCTRL_ID( clockId ) | // Generic Clock 0 (SERCOMx) // GCLK_CLKCTRL_GEN_GCLK0 | // Generic Clock Generator 0 is source // GCLK_CLKCTRL_CLKEN ; sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_SERCOM0_CORE << sam.GCLK_CLKCTRL_ID_Pos) | (sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) | sam.GCLK_CLKCTRL_CLKEN) waitForSync() // Turn on clock to SERCOM1 sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_SERCOM1_) sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_SERCOM1_CORE << sam.GCLK_CLKCTRL_ID_Pos) | (sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) | sam.GCLK_CLKCTRL_CLKEN) waitForSync() // Turn on clock to SERCOM2 sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_SERCOM2_) sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_SERCOM2_CORE << sam.GCLK_CLKCTRL_ID_Pos) | (sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) | sam.GCLK_CLKCTRL_CLKEN) waitForSync() // Turn on clock to SERCOM3 sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_SERCOM3_) sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_SERCOM3_CORE << sam.GCLK_CLKCTRL_ID_Pos) | (sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) | sam.GCLK_CLKCTRL_CLKEN) waitForSync() } ================================================ FILE: src/runtime/runtime_atsamd21g18.go ================================================ //go:build sam && atsamd21 && atsamd21g18 package runtime import ( "device/sam" ) func initSERCOMClocks() { // Turn on clock to SERCOM0 for UART0 sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_SERCOM0_) // Use GCLK0 for SERCOM0 aka UART0 // GCLK_CLKCTRL_ID( clockId ) | // Generic Clock 0 (SERCOMx) // GCLK_CLKCTRL_GEN_GCLK0 | // Generic Clock Generator 0 is source // GCLK_CLKCTRL_CLKEN ; sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_SERCOM0_CORE << sam.GCLK_CLKCTRL_ID_Pos) | (sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) | sam.GCLK_CLKCTRL_CLKEN) waitForSync() // Turn on clock to SERCOM1 sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_SERCOM1_) sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_SERCOM1_CORE << sam.GCLK_CLKCTRL_ID_Pos) | (sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) | sam.GCLK_CLKCTRL_CLKEN) waitForSync() // Turn on clock to SERCOM2 sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_SERCOM2_) sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_SERCOM2_CORE << sam.GCLK_CLKCTRL_ID_Pos) | (sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) | sam.GCLK_CLKCTRL_CLKEN) waitForSync() // Turn on clock to SERCOM3 sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_SERCOM3_) sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_SERCOM3_CORE << sam.GCLK_CLKCTRL_ID_Pos) | (sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) | sam.GCLK_CLKCTRL_CLKEN) waitForSync() // Turn on clock to SERCOM4 sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_SERCOM4_) // Use GCLK0 for SERCOM4 sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_SERCOM4_CORE << sam.GCLK_CLKCTRL_ID_Pos) | (sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) | sam.GCLK_CLKCTRL_CLKEN) waitForSync() // Turn on clock to SERCOM5 sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_SERCOM5_) // Use GCLK0 for SERCOM5 sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_SERCOM5_CORE << sam.GCLK_CLKCTRL_ID_Pos) | (sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) | sam.GCLK_CLKCTRL_CLKEN) waitForSync() } ================================================ FILE: src/runtime/runtime_atsamd51.go ================================================ //go:build (sam && atsamd51) || (sam && atsame5x) package runtime import ( "device/arm" "device/sam" "machine" _ "machine/usb/cdc" "runtime/interrupt" "runtime/volatile" ) //export Reset_Handler func main() { arm.SCB.CPACR.Set(0) // disable FPU if it is enabled preinit() run() exit(0) } func init() { initClocks() initRTC() initSERCOMClocks() initUSBClock() initADCClock() enableCache() machine.InitSerial() } func putchar(c byte) { machine.Serial.WriteByte(c) } func getchar() byte { for machine.Serial.Buffered() == 0 { Gosched() } v, _ := machine.Serial.ReadByte() return v } func buffered() int { return machine.Serial.Buffered() } func initClocks() { // set flash wait state sam.NVMCTRL.CTRLA.SetBits(0 << sam.NVMCTRL_CTRLA_RWS_Pos) // software reset sam.GCLK.CTRLA.SetBits(sam.GCLK_CTRLA_SWRST) for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_SWRST) { } // Set OSCULP32K as source of Generic Clock Generator 3 // GCLK->GENCTRL[GENERIC_CLOCK_GENERATOR_XOSC32K].reg = GCLK_GENCTRL_SRC(GCLK_GENCTRL_SRC_OSCULP32K) | GCLK_GENCTRL_GENEN; //generic clock gen 3 sam.GCLK.GENCTRL[3].Set((sam.GCLK_GENCTRL_SRC_OSCULP32K << sam.GCLK_GENCTRL_SRC_Pos) | sam.GCLK_GENCTRL_GENEN) for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL_GCLK3) { } // Set OSCULP32K as source of Generic Clock Generator 0 sam.GCLK.GENCTRL[0].Set((sam.GCLK_GENCTRL_SRC_OSCULP32K << sam.GCLK_GENCTRL_SRC_Pos) | sam.GCLK_GENCTRL_GENEN) for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL_GCLK0) { } // Enable DFLL48M clock sam.OSCCTRL.DFLLCTRLA.Set(0) sam.OSCCTRL.DFLLMUL.Set((0x1 << sam.OSCCTRL_DFLLMUL_CSTEP_Pos) | (0x1 << sam.OSCCTRL_DFLLMUL_FSTEP_Pos) | (0x0 << sam.OSCCTRL_DFLLMUL_MUL_Pos)) for sam.OSCCTRL.DFLLSYNC.HasBits(sam.OSCCTRL_DFLLSYNC_DFLLMUL) { } sam.OSCCTRL.DFLLCTRLB.Set(0) for sam.OSCCTRL.DFLLSYNC.HasBits(sam.OSCCTRL_DFLLSYNC_DFLLCTRLB) { } sam.OSCCTRL.DFLLCTRLA.SetBits(sam.OSCCTRL_DFLLCTRLA_ENABLE) for sam.OSCCTRL.DFLLSYNC.HasBits(sam.OSCCTRL_DFLLSYNC_ENABLE) { } sam.OSCCTRL.DFLLVAL.Set(sam.OSCCTRL.DFLLVAL.Get()) for sam.OSCCTRL.DFLLSYNC.HasBits(sam.OSCCTRL_DFLLSYNC_DFLLVAL) { } sam.OSCCTRL.DFLLCTRLB.Set(sam.OSCCTRL_DFLLCTRLB_WAITLOCK | sam.OSCCTRL_DFLLCTRLB_CCDIS | sam.OSCCTRL_DFLLCTRLB_USBCRM) for !sam.OSCCTRL.STATUS.HasBits(sam.OSCCTRL_STATUS_DFLLRDY) { } // set GCLK7 to run at 2MHz, using DFLL48M as clock source // GCLK7 = 48MHz / 24 = 2MHz sam.GCLK.GENCTRL[7].Set((sam.GCLK_GENCTRL_SRC_DFLL << sam.GCLK_GENCTRL_SRC_Pos) | (24 << sam.GCLK_GENCTRL_DIV_Pos) | sam.GCLK_GENCTRL_GENEN) for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL_GCLK7) { } // Set up the PLLs // Set PLL0 to run at 120MHz, using GCLK7 as clock source sam.GCLK.PCHCTRL[1].Set(sam.GCLK_PCHCTRL_CHEN | (sam.GCLK_PCHCTRL_GEN_GCLK7 << sam.GCLK_PCHCTRL_GEN_Pos)) // multiplier = 59 + 1 + (0/32) = 60 // PLL0 = 2MHz * 60 = 120MHz sam.OSCCTRL.DPLL[0].DPLLRATIO.Set((0x0 << sam.OSCCTRL_DPLL_DPLLRATIO_LDRFRAC_Pos) | (59 << sam.OSCCTRL_DPLL_DPLLRATIO_LDR_Pos)) for sam.OSCCTRL.DPLL[0].DPLLSYNCBUSY.HasBits(sam.OSCCTRL_DPLL_DPLLSYNCBUSY_DPLLRATIO) { } // MUST USE LBYPASS DUE TO BUG IN REV A OF SAMD51, via Adafruit lib. sam.OSCCTRL.DPLL[0].DPLLCTRLB.Set((sam.OSCCTRL_DPLL_DPLLCTRLB_REFCLK_GCLK << sam.OSCCTRL_DPLL_DPLLCTRLB_REFCLK_Pos) | sam.OSCCTRL_DPLL_DPLLCTRLB_LBYPASS) sam.OSCCTRL.DPLL[0].DPLLCTRLA.Set(sam.OSCCTRL_DPLL_DPLLCTRLA_ENABLE) for !sam.OSCCTRL.DPLL[0].DPLLSTATUS.HasBits(sam.OSCCTRL_DPLL_DPLLSTATUS_CLKRDY) || !sam.OSCCTRL.DPLL[0].DPLLSTATUS.HasBits(sam.OSCCTRL_DPLL_DPLLSTATUS_LOCK) { } // Set PLL1 to run at 100MHz, using GCLK7 as clock source sam.GCLK.PCHCTRL[2].Set(sam.GCLK_PCHCTRL_CHEN | (sam.GCLK_PCHCTRL_GEN_GCLK7 << sam.GCLK_PCHCTRL_GEN_Pos)) // multiplier = 49 + 1 + (0/32) = 50 // PLL1 = 2MHz * 50 = 100MHz sam.OSCCTRL.DPLL[1].DPLLRATIO.Set((0x0 << sam.OSCCTRL_DPLL_DPLLRATIO_LDRFRAC_Pos) | (49 << sam.OSCCTRL_DPLL_DPLLRATIO_LDR_Pos)) for sam.OSCCTRL.DPLL[1].DPLLSYNCBUSY.HasBits(sam.OSCCTRL_DPLL_DPLLSYNCBUSY_DPLLRATIO) { } // // MUST USE LBYPASS DUE TO BUG IN REV A OF SAMD51 sam.OSCCTRL.DPLL[1].DPLLCTRLB.Set((sam.OSCCTRL_DPLL_DPLLCTRLB_REFCLK_GCLK << sam.OSCCTRL_DPLL_DPLLCTRLB_REFCLK_Pos) | sam.OSCCTRL_DPLL_DPLLCTRLB_LBYPASS) sam.OSCCTRL.DPLL[1].DPLLCTRLA.Set(sam.OSCCTRL_DPLL_DPLLCTRLA_ENABLE) // for !sam.OSCCTRL.DPLLSTATUS1.HasBits(sam.OSCCTRL_DPLLSTATUS_CLKRDY) || // !sam.OSCCTRL.DPLLSTATUS1.HasBits(sam.OSCCTRL_DPLLSTATUS_LOCK) { // } // Set up the peripheral clocks // Set 48MHZ CLOCK FOR USB sam.GCLK.GENCTRL[1].Set((sam.GCLK_GENCTRL_SRC_DFLL << sam.GCLK_GENCTRL_SRC_Pos) | sam.GCLK_GENCTRL_IDC | sam.GCLK_GENCTRL_GENEN) for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL_GCLK1) { } // // Set 100MHZ CLOCK FOR OTHER PERIPHERALS // sam.GCLK.GENCTRL2.Set((sam.GCLK_GENCTRL_SRC_DPLL1 << sam.GCLK_GENCTRL_SRC_Pos) | // sam.GCLK_GENCTRL_IDC | // sam.GCLK_GENCTRL_GENEN) // for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL2) { // } // // Set 12MHZ CLOCK FOR DAC sam.GCLK.GENCTRL[4].Set((sam.GCLK_GENCTRL_SRC_DFLL << sam.GCLK_GENCTRL_SRC_Pos) | sam.GCLK_GENCTRL_IDC | (4 << sam.GCLK_GENCTRL_DIVSEL_Pos) | sam.GCLK_GENCTRL_GENEN) for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL_GCLK4) { } // // Set up main clock sam.GCLK.GENCTRL[0].Set((sam.GCLK_GENCTRL_SRC_DPLL0 << sam.GCLK_GENCTRL_SRC_Pos) | sam.GCLK_GENCTRL_IDC | sam.GCLK_GENCTRL_GENEN) for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL_GCLK0) { } sam.MCLK.CPUDIV.Set(sam.MCLK_CPUDIV_DIV_DIV1) // Use the LDO regulator by default sam.SUPC.VREG.ClearBits(sam.SUPC_VREG_SEL) // Start up the "Debug Watchpoint and Trace" unit, so that we can use // it's 32bit cycle counter for timing. //CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; //DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; // Disable automatic NVM write operations sam.NVMCTRL.SetCTRLA_WMODE(sam.NVMCTRL_CTRLA_WMODE_MAN) } func initRTC() { // turn on digital interface clock sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_RTC_) // disable RTC sam.RTC_MODE0.CTRLA.ClearBits(sam.RTC_MODE0_CTRLA_ENABLE) //sam.RTC_MODE0.CTRLA.Set(0) for sam.RTC_MODE0.SYNCBUSY.HasBits(sam.RTC_MODE0_SYNCBUSY_ENABLE) { } // reset RTC sam.RTC_MODE0.CTRLA.SetBits(sam.RTC_MODE0_CTRLA_SWRST) for sam.RTC_MODE0.SYNCBUSY.HasBits(sam.RTC_MODE0_SYNCBUSY_SWRST) { } // set to use ulp 32k oscillator sam.OSC32KCTRL.OSCULP32K.SetBits(sam.OSC32KCTRL_OSCULP32K_EN32K) sam.OSC32KCTRL.RTCCTRL.Set(sam.OSC32KCTRL_RTCCTRL_RTCSEL_ULP32K) // set Mode0 to 32-bit counter (mode 0) with prescaler 1 and GCLK2 is 32KHz/1 sam.RTC_MODE0.CTRLA.Set((sam.RTC_MODE0_CTRLA_MODE_COUNT32 << sam.RTC_MODE0_CTRLA_MODE_Pos) | (sam.RTC_MODE0_CTRLA_PRESCALER_DIV1 << sam.RTC_MODE0_CTRLA_PRESCALER_Pos) | (sam.RTC_MODE0_CTRLA_COUNTSYNC)) // re-enable RTC sam.RTC_MODE0.CTRLA.SetBits(sam.RTC_MODE0_CTRLA_ENABLE) for sam.RTC_MODE0.SYNCBUSY.HasBits(sam.RTC_MODE0_SYNCBUSY_ENABLE) { } irq := interrupt.New(sam.IRQ_RTC, func(interrupt.Interrupt) { flags := sam.RTC_MODE0.INTFLAG.Get() if flags&sam.RTC_MODE0_INTENSET_CMP0 != 0 { // The timer (for a sleep) has expired. timerWakeup.Set(1) } if flags&sam.RTC_MODE0_INTENSET_OVF != 0 { // The 32-bit RTC timer has overflowed. rtcOverflows.Set(rtcOverflows.Get() + 1) } // Mark this interrupt has handled for CMP0 and OVF. sam.RTC_MODE0.INTFLAG.Set(sam.RTC_MODE0_INTENSET_CMP0 | sam.RTC_MODE0_INTENSET_OVF) }) sam.RTC_MODE0.INTENSET.Set(sam.RTC_MODE0_INTENSET_OVF) irq.SetPriority(0xc0) irq.Enable() } func waitForSync() { for sam.RTC_MODE0.SYNCBUSY.HasBits(sam.RTC_MODE0_SYNCBUSY_COUNT) { } } var rtcOverflows volatile.Register32 // number of times the RTC wrapped around var timerWakeup volatile.Register8 // ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds. func ticksToNanoseconds(ticks timeUnit) int64 { // The following calculation is actually the following, but with both sides // reduced to reduce the risk of overflow: // ticks * 1e9 / 32768 return int64(ticks) * 1953125 / 64 } // nanosecondsToTicks converts nanoseconds to RTC ticks (running at 32768Hz). func nanosecondsToTicks(ns int64) timeUnit { // The following calculation is actually the following, but with both sides // reduced to reduce the risk of overflow: // ns * 32768 / 1e9 return timeUnit(ns * 64 / 1953125) } // sleepTicks should sleep for d number of microseconds. func sleepTicks(d timeUnit) { for d != 0 { ticks := uint32(d) if d > 0xffff_ffff { ticks = 0xffff_ffff } if !timerSleep(ticks) { return } d -= timeUnit(ticks) } } // ticks returns the elapsed time since reset. func ticks() timeUnit { // For some ways of capturing the time atomically, see this thread: // https://www.eevblog.com/forum/microcontrollers/correct-timing-by-timer-overflow-count/msg749617/#msg749617 // Here, instead of re-reading the counter register if an overflow has been // detected, we simply try again because that results in smaller code. for { mask := interrupt.Disable() counter := readRTC() overflows := rtcOverflows.Get() hasOverflow := sam.RTC_MODE0.INTFLAG.Get()&sam.RTC_MODE0_INTENSET_OVF != 0 interrupt.Restore(mask) if hasOverflow { // There was an overflow while trying to capture the timer. // Try again. continue } // This is a 32-bit timer, so the number of timer overflows forms the // upper 32 bits of this timer. return timeUnit(overflows)<<32 + timeUnit(counter) } } func readRTC() uint32 { waitForSync() return sam.RTC_MODE0.COUNT.Get() } // ticks are in microseconds // Returns true if the timer completed. // Returns false if another interrupt occurred which requires an early return to scheduler. func timerSleep(ticks uint32) bool { timerWakeup.Set(0) if ticks < 8 { // due to delay waiting for the register value to sync, the minimum sleep value // for the SAMD51 is 260us. // For related info for SAMD21, see: // https://community.atmel.com/comment/2507091#comment-2507091 ticks = 8 } // request read of count waitForSync() // set compare value cnt := sam.RTC_MODE0.COUNT.Get() sam.RTC_MODE0.COMP[0].Set(uint32(cnt) + ticks) // enable IRQ for CMP0 compare sam.RTC_MODE0.INTENSET.Set(sam.RTC_MODE0_INTENSET_CMP0) wait: waitForEvents() if timerWakeup.Get() != 0 { return true } if hasScheduler { // The interurpt may have awoken a goroutine, so bail out early. // Disable IRQ for CMP0 compare. sam.RTC_MODE0.INTENCLR.Set(sam.RTC_MODE0_INTENSET_CMP0) return false } else { // This is running without a scheduler. // The application expects this to sleep the whole time. goto wait } } func initUSBClock() { // Turn on clock(s) for USB //MCLK->APBBMASK.reg |= MCLK_APBBMASK_USB; //MCLK->AHBMASK.reg |= MCLK_AHBMASK_USB; sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_USB_) sam.MCLK.AHBMASK.SetBits(sam.MCLK_AHBMASK_USB_) // Put Generic Clock Generator 1 as source for USB //GCLK->PCHCTRL[USB_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK1_Val | (1 << GCLK_PCHCTRL_CHEN_Pos); sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_USB].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } func initADCClock() { // Turn on clocks for ADC0/ADC1. sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_ADC0_) sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_ADC1_) // Put Generic Clock Generator 1 as source for ADC0 and ADC1. sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_ADC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_ADC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } func enableCache() { sam.CMCC.CTRL.SetBits(sam.CMCC_CTRL_CEN) } func waitForEvents() { arm.Asm("wfe") } ================================================ FILE: src/runtime/runtime_atsamd51g19.go ================================================ //go:build sam && atsamd51 && atsamd51g19 package runtime import ( "device/sam" ) func initSERCOMClocks() { // Turn on clock to SERCOM0 for UART0 sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // sets the "slow" clock shared by all SERCOM sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOMX_SLOW].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM1 sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM2 sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM3 sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM4 sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM5 sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } ================================================ FILE: src/runtime/runtime_atsamd51j19.go ================================================ //go:build sam && atsamd51 && atsamd51j19 package runtime import ( "device/sam" ) func initSERCOMClocks() { // Turn on clock to SERCOM0 for UART0 sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // sets the "slow" clock shared by all SERCOM sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOMX_SLOW].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM1 sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM2 sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM3 sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM4 sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM5 sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } ================================================ FILE: src/runtime/runtime_atsamd51j20.go ================================================ //go:build sam && atsamd51 && atsamd51j20 package runtime import ( "device/sam" ) func initSERCOMClocks() { // Turn on clock to SERCOM0 for UART0 sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // sets the "slow" clock shared by all SERCOM sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOMX_SLOW].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM1 sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM2 sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM3 sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM4 sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM5 sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } ================================================ FILE: src/runtime/runtime_atsamd51p19.go ================================================ //go:build sam && atsamd51 && atsamd51p19 package runtime import ( "device/sam" ) func initSERCOMClocks() { // Turn on clock to SERCOM0 for UART0 sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // sets the "slow" clock shared by all SERCOM sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOMX_SLOW].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM1 sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM2 sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM3 sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM4 sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM5 sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM6 sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM6_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM6_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM7 sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM7_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM7_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } ================================================ FILE: src/runtime/runtime_atsamd51p20.go ================================================ //go:build sam && atsamd51 && atsamd51p20 package runtime import ( "device/sam" ) func initSERCOMClocks() { // Turn on clock to SERCOM0 for UART0 sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // sets the "slow" clock shared by all SERCOM sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOMX_SLOW].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM1 sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM2 sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM3 sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM4 sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM5 sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM6 sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM6_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM6_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM7 sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM7_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM7_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } ================================================ FILE: src/runtime/runtime_atsame51j19.go ================================================ //go:build sam && atsame51 && atsame51j19 package runtime import ( "device/sam" ) func initSERCOMClocks() { // Turn on clock to SERCOM0 for UART0 sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // sets the "slow" clock shared by all SERCOM sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOMX_SLOW].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM1 sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM2 sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM3 sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM4 sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM5 sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } ================================================ FILE: src/runtime/runtime_atsame54p20.go ================================================ //go:build sam && atsame5x && atsame54p20 package runtime import ( "device/sam" ) func initSERCOMClocks() { // Turn on clock to SERCOM0 for UART0 sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // sets the "slow" clock shared by all SERCOM sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOMX_SLOW].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM1 sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM2 sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM3 sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM4 sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM5 sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM6 sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM6_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM6_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) // Turn on clock to SERCOM7 sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM7_) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM7_CORE].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } ================================================ FILE: src/runtime/runtime_atsame5x_can.go ================================================ //go:build (sam && atsame51) || (sam && atsame54) package runtime import ( "device/sam" ) func init() { initCANClock() } func initCANClock() { // Turn on clocks for CAN0/CAN1. sam.MCLK.AHBMASK.SetBits(sam.MCLK_AHBMASK_CAN0_) sam.MCLK.AHBMASK.SetBits(sam.MCLK_AHBMASK_CAN1_) // Put Generic Clock Generator 1 as source for USB sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_CAN0].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_CAN1].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) } ================================================ FILE: src/runtime/runtime_attiny.go ================================================ //go:build avr && attiny package runtime import ( "device/avr" ) func initUART() { } func putchar(c byte) { // UART is not supported. } func getchar() byte { // UART is not supported. return 0 } func buffered() int { // UART is not supported. return 0 } func sleepWDT(period uint8) { // TODO: use the watchdog timer instead of a busy loop. for i := 0x45; i != 0; i-- { for i := 0xff; i != 0; i-- { avr.Asm("nop") } } } ================================================ FILE: src/runtime/runtime_avr.go ================================================ //go:build avr && !avrtiny package runtime import ( "device/avr" "machine" "runtime/interrupt" "runtime/volatile" "unsafe" ) const BOARD = "arduino" // Watchdog timer periods. These can be off by a large margin (hence the jump // between 64ms and 125ms which is not an exact double), so don't rely on this // for accurate time keeping. const ( WDT_PERIOD_16MS = iota WDT_PERIOD_32MS WDT_PERIOD_64MS WDT_PERIOD_125MS WDT_PERIOD_250MS WDT_PERIOD_500MS WDT_PERIOD_1S WDT_PERIOD_2S ) const timerRecalibrateInterval = 6e7 // 1 minute var nextTimerRecalibrate timeUnit //go:extern _sbss var _sbss [0]byte //go:extern _ebss var _ebss [0]byte //export main func main() { preinit() initHardware() run() exit(0) } func preinit() { // Initialize .bss: zero-initialized global variables. ptr := unsafe.Pointer(&_sbss) for ptr != unsafe.Pointer(&_ebss) { *(*uint8)(ptr) = 0 ptr = unsafe.Add(ptr, 1) } } func initHardware() { initUART() initMonotonicTimer() nextTimerRecalibrate = ticks() + timerRecalibrateInterval // Enable interrupts after initialization. avr.Asm("sei") } func ticksToNanoseconds(ticks timeUnit) int64 { return int64(ticks) } func nanosecondsToTicks(ns int64) timeUnit { return timeUnit(ns) } // Sleep this number of ticks of nanoseconds. func sleepTicks(d timeUnit) { waitTill := ticks() + d for { // wait for interrupt avr.Asm("sleep") if waitTill <= ticks() { // done waiting return } if hasScheduler { // The interrupt may have awoken a goroutine, so bail out early. return } } } func ticks() (ticksReturn timeUnit) { state := interrupt.Disable() // use volatile since ticksCount can be changed when running on multi-core boards. ticksReturn = timeUnit(volatile.LoadUint64((*uint64)(unsafe.Pointer(&ticksCount)))) interrupt.Restore(state) return } func exit(code int) { abort() } func abort() { // Disable interrupts and go to sleep. // This can never be awoken except for reset, and is recognized as termination by simavr. avr.Asm("cli") for { avr.Asm("sleep") } } var ticksCount int64 // nanoseconds since start var nanosecondsInTick int64 = 16000 // nanoseconds per each tick func initMonotonicTimer() { ticksCount = 0 interrupt.New(avr.IRQ_TIMER0_OVF, func(i interrupt.Interrupt) { // use volatile ticks := volatile.LoadUint64((*uint64)(unsafe.Pointer(&ticksCount))) ticks += uint64(nanosecondsInTick) volatile.StoreUint64((*uint64)(unsafe.Pointer(&ticksCount)), ticks) }) // initial initialization of the Timer0 // - Mask interrupt avr.TIMSK0.ClearBits(avr.TIMSK0_TOIE0 | avr.TIMSK0_OCIE0A | avr.TIMSK0_OCIE0B) // - Write new values to TCNT2, OCR2x, and TCCR2x. avr.TCNT0.Set(0) avr.OCR0A.Set(0xff) // - Set mode 3 avr.TCCR0A.Set(avr.TCCR0A_WGM00 | avr.TCCR0A_WGM01) // - Set prescaler 1 avr.TCCR0B.Set(avr.TCCR0B_CS00) // - Unmask interrupt avr.TIMSK0.SetBits(avr.TIMSK0_TOIE0) } //go:linkname adjustMonotonicTimer machine.adjustMonotonicTimer func adjustMonotonicTimer() { // adjust the nanosecondsInTick using volatile mask := interrupt.Disable() volatile.StoreUint64((*uint64)(unsafe.Pointer(&nanosecondsInTick)), uint64(currentNanosecondsInTick())) interrupt.Restore(mask) } func currentNanosecondsInTick() int64 { // this time depends on clk_IO, prescale, mode and OCR0A // assuming the clock source is CPU clock prescaler := int64(avr.TCCR0B.Get() & 0x7) clock := (int64(1e12) / prescaler) / int64(machine.CPUFrequency()) mode := avr.TCCR0A.Get() & 0x7 /* Mode WGM02 WGM01 WGM00 Timer/Counter TOP Update of TOV Flag Mode of Operation OCRx at Set on 0 0 0 0 Normal 0xFF Immediate MAX 1 0 0 1 PWM, Phase Correct 0xFF TOP BOTTOM 2 0 1 0 CTC OCRA Immediate MAX 3 0 1 1 Fast PWM 0xFF BOTTOM MAX 5 1 0 1 PWM, Phase Correct OCRA TOP BOTTOM 7 1 1 1 Fast PWM OCRA BOTTOM TOP */ switch mode { case 0, 3: // normal & fast PWM // TOV0 Interrupt when moving from MAX (0xff) to 0x00 return clock * 256 / 1000 case 1: // Phase Correct PWM // TOV0 Interrupt when moving from MAX (0xff) to 0x00 return clock * 256 * 2 / 1000 case 2, 7: // CTC & fast PWM // TOV0 Interrupt when moving from MAX (OCRA) to 0x00 return clock * int64(avr.OCR0A.Get()) / 1000 case 5: // Phase Correct PWM // TOV0 Interrupt when moving from MAX (OCRA) to 0x00 return clock * int64(avr.OCR0A.Get()) * 2 / 1000 } return clock / 1000 // for unknown } ================================================ FILE: src/runtime/runtime_avrtiny.go ================================================ //go:build avrtiny // Runtime for the newer AVRs introduced since around 2016 that work quite // different from older AVRs like the atmega328p or even the attiny85. // Because of these large differences, a new runtime and machine implementation // is needed. // Some key differences: // * Peripherals are now logically separated, instead of all mixed together as // one big bag of registers. No PORTA/DDRA etc registers anymore, instead a // real PORT peripheral type with multiple instances. // * There is a real RTC now! No need for using one of the timers as a time // source, which then conflicts with using it as a PWM. // * Flash and RAM are now in the same address space! This avoids the need for // PROGMEM which couldn't (easily) be supported in Go anyway. Constant // globals just get stored in flash, like on Cortex-M chips. package runtime import ( "device/avr" "runtime/interrupt" "runtime/volatile" ) //export main func main() { // Initialize RTC. for avr.RTC.STATUS.Get() != 0 { } avr.RTC.CTRLA.Set(avr.RTC_CTRLA_RTCEN | avr.RTC_CTRLA_RUNSTDBY) avr.RTC.INTCTRL.Set(avr.RTC_INTCTRL_OVF) // enable overflow interrupt interrupt.New(avr.IRQ_RTC_CNT, rtcInterrupt) // Configure sleep: // - enable sleep mode // - set sleep mode to STANDBY (mode 0x1) avr.SLPCTRL.CTRLA.Set(avr.SLPCTRL_CTRLA_SEN | 0x1<<1) // Enable interrupts after initialization. avr.Asm("sei") run() exit(0) } func initUART() { // no UART configured } func putchar(b byte) { // no-op } // ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds. func ticksToNanoseconds(ticks timeUnit) int64 { // The following calculation is actually the following, but with both sides // reduced to reduce the risk of overflow: // ticks * 1e9 / 32768 return int64(ticks) * 1953125 / 64 } // nanosecondsToTicks converts nanoseconds to RTC ticks (running at 32768Hz). func nanosecondsToTicks(ns int64) timeUnit { // The following calculation is actually the following, but with both sides // reduced to reduce the risk of overflow: // ns * 32768 / 1e9 return timeUnit(ns * 64 / 1953125) } // Sleep for the given number of timer ticks. func sleepTicks(d timeUnit) { ticksStart := ticks() sleepUntil := ticksStart + d // Sleep until we're in the right 2-second interval. for { avr.Asm("cli") overflows := rtcOverflows.Get() if overflows >= uint32(sleepUntil>>16) { // We're in the right 2-second interval. // At this point we know that the difference between ticks() and // sleepUntil is ≤0xffff. avr.Asm("sei") break } // Sleep some more, because we're not there yet. avr.Asm("sei\nsleep") } // Now we know the sleep duration is small enough to fit in rtc.CNT. // Update rtc.CMP (atomically). cnt := uint16(sleepUntil) low := uint8(cnt) high := uint8(cnt >> 8) avr.RTC.CMPH.Set(high) avr.RTC.CMPL.Set(low) // Disable interrupts, so we can change interrupt settings without racing. avr.Asm("cli") // Enable the CMP interrupt. avr.RTC.INTCTRL.Set(avr.RTC_INTCTRL_OVF | avr.RTC_INTCTRL_CMP) // Check whether we already reached CNT, in which case the interrupt may // have triggered already (but maybe not, it's a race condition). low2 := avr.RTC.CNTL.Get() high2 := avr.RTC.CNTH.Get() cnt2 := uint16(high2)<<8 | uint16(low2) if cnt2 < cnt { // We have not, so wait until the interrupt happens. for { // Sleep until the next interrupt happens. avr.Asm("sei\nsleep\ncli") if cmpMatch.Get() != 0 { // The CMP interrupt occurred, so we have slept long enough. cmpMatch.Set(0) break } } } // Disable the CMP interrupt, and restore things like they were before. avr.RTC.INTCTRL.Set(avr.RTC_INTCTRL_OVF) avr.Asm("sei") } // Number of RTC overflows, updated in the RTC interrupt handler. // The RTC is running at 32768Hz so an overflow happens every 2 seconds. A // 32-bit integer is large enough to run for about 279 years. var rtcOverflows volatile.Register32 // Set to one in the RTC CMP interrupt, to signal the expected number of ticks // have passed. var cmpMatch volatile.Register8 // Return the number of RTC ticks that happened since reset. func ticks() timeUnit { var ovf uint32 var count uint16 for { // Get the tick count and overflow value, in a 4-step process to avoid a // race with the overflow interrupt. mask := interrupt.Disable() // 1. Get the overflow value. ovf = rtcOverflows.Get() // 2. Read the RTC counter. // This way of reading is atomic (due to the TEMP register). low := avr.RTC.CNTL.Get() high := avr.RTC.CNTH.Get() // 3. Get the interrupt flags. intflags := avr.RTC.INTFLAGS.Get() interrupt.Restore(mask) // 4. Check whether an overflow happened somewhere in the last three // steps. If so, just repeat the loop. if intflags&avr.RTC_INTFLAGS_OVF == 0 { count = uint16(high)<<8 | uint16(low) break } } // Create the 64-bit tick count, combining the two. return timeUnit(ovf)<<16 | timeUnit(count) } // Interrupt handler for the RTC. // It happens every two seconds, and while sleeping using the CMP interrupt. func rtcInterrupt(interrupt.Interrupt) { flags := avr.RTC.INTFLAGS.Get() if flags&avr.RTC_INTFLAGS_OVF != 0 { rtcOverflows.Set(rtcOverflows.Get() + 1) } if flags&avr.RTC_INTFLAGS_CMP != 0 { cmpMatch.Set(1) } avr.RTC.INTFLAGS.Set(flags) // clear interrupts } func exit(code int) { abort() } //export __vector_default func abort() ================================================ FILE: src/runtime/runtime_cortexm.go ================================================ //go:build cortexm package runtime import ( "unsafe" ) //go:extern _sbss var _sbss [0]byte //go:extern _ebss var _ebss [0]byte //go:extern _sdata var _sdata [0]byte //go:extern _sidata var _sidata [0]byte //go:extern _edata var _edata [0]byte func preinit() { // Initialize .bss: zero-initialized global variables. ptr := unsafe.Pointer(&_sbss) for ptr != unsafe.Pointer(&_ebss) { *(*uint32)(ptr) = 0 ptr = unsafe.Add(ptr, 4) } // Initialize .data: global variables initialized from flash. src := unsafe.Pointer(&_sidata) dst := unsafe.Pointer(&_sdata) for dst != unsafe.Pointer(&_edata) { *(*uint32)(dst) = *(*uint32)(src) dst = unsafe.Add(dst, 4) src = unsafe.Add(src, 4) } } // The stack layout at the moment an interrupt occurs. // Registers can be accessed if the stack pointer is cast to a pointer to this // struct. type interruptStack struct { R0 uintptr R1 uintptr R2 uintptr R3 uintptr R12 uintptr LR uintptr PC uintptr PSR uintptr } ================================================ FILE: src/runtime/runtime_cortexm_abort.go ================================================ //go:build cortexm && !nxp && !qemu package runtime import ( "device/arm" ) func exit(code int) { abort() } func abort() { // lock up forever for { arm.Asm("wfi") } } ================================================ FILE: src/runtime/runtime_cortexm_hardfault.go ================================================ //go:build atsamd21 || nrf51 package runtime // This function is called at HardFault. // // For details, see: // https://community.arm.com/developer/ip-products/system/f/embedded-forum/3257/debugging-a-cortex-m0-hard-fault // https://blog.feabhas.com/2013/02/developing-a-generic-hard-fault-handler-for-arm-cortex-m3cortex-m4/ // //export HardFault_Handler func HardFault_Handler() { // Obtain the stack pointer as it was on entry to the HardFault. It contains // the registers that were pushed by the NVIC and that we can now read back // to print the PC value at the time of the hard fault, for example. sp := (*interruptStack)(llvm_sponentry()) // Note: by reusing the string "panic: runtime error at " we save a little // bit in terms of code size as the string can be deduplicated. print("panic: runtime error at ", sp.PC, ": HardFault with sp=", sp) // TODO: try to find the cause of the hard fault. Especially on Cortex-M3 // and higher it is possible to find more detailed information in special // status registers. println() abort() } ================================================ FILE: src/runtime/runtime_cortexm_hardfault_debug.go ================================================ //go:build cortexm && !atsamd21 && !nrf51 package runtime import ( "device/arm" "unsafe" ) const ( SCB_CFSR_KnownFault = arm.SCB_CFSR_IACCVIOL | arm.SCB_CFSR_DACCVIOL | arm.SCB_CFSR_MUNSTKERR | arm.SCB_CFSR_MSTKERR | arm.SCB_CFSR_MLSPERR | arm.SCB_CFSR_IBUSERR | arm.SCB_CFSR_PRECISERR | arm.SCB_CFSR_IMPRECISERR | arm.SCB_CFSR_UNSTKERR | arm.SCB_CFSR_STKERR | arm.SCB_CFSR_LSPERR | arm.SCB_CFSR_UNDEFINSTR | arm.SCB_CFSR_INVSTATE | arm.SCB_CFSR_INVPC | arm.SCB_CFSR_NOCP | arm.SCB_CFSR_UNALIGNED | arm.SCB_CFSR_DIVBYZERO ) // See runtime_cortexm_hardfault.go // //export HardFault_Handler func HardFault_Handler() { // Obtain the stack pointer as it was on entry to the HardFault. It contains // the registers that were pushed by the NVIC and that we can now read back // to print the PC value at the time of the hard fault, for example. sp := (*interruptStack)(llvm_sponentry()) fault := GetFaultStatus() spValid := !fault.Bus().ImpreciseDataBusError() print("fatal error: ") if spValid && uintptr(unsafe.Pointer(sp)) < 0x20000000 { print("stack overflow? ") } if fault.Mem().InstructionAccessViolation() { print("instruction access violation") } if fault.Mem().DataAccessViolation() { print("data access violation") } if fault.Mem().WhileUnstackingException() { print(" while unstacking exception") } if fault.Mem().WileStackingException() { print(" while stacking exception") } if fault.Mem().DuringFPLazyStatePres() { print(" during floating-point lazy state preservation") } if fault.Bus().InstructionBusError() { print("instruction bus error") } if fault.Bus().PreciseDataBusError() { print("data bus error (precise)") } if fault.Bus().ImpreciseDataBusError() { print("data bus error (imprecise)") } if fault.Bus().WhileUnstackingException() { print(" while unstacking exception") } if fault.Bus().WhileStackingException() { print(" while stacking exception") } if fault.Bus().DuringFPLazyStatePres() { print(" during floating-point lazy state preservation") } if fault.Usage().UndefinedInstruction() { print("undefined instruction") } if fault.Usage().IllegalUseOfEPSR() { print("illegal use of the EPSR") } if fault.Usage().IllegalExceptionReturn() { print("illegal load of EXC_RETURN to the PC") } if fault.Usage().AttemptedToAccessCoprocessor() { print("coprocessor access violation") } if fault.Usage().UnalignedMemoryAccess() { print("unaligned memory access") } if fault.Usage().DivideByZero() { print("divide by zero") } if fault.Unknown() { print("unknown hard fault") } if addr, ok := fault.Mem().Address(); ok { print(" with fault address ", addr) } if addr, ok := fault.Bus().Address(); ok { print(" with bus fault address ", addr) } if spValid { print(" with sp=", sp) if uintptr(unsafe.Pointer(&sp.PC)) >= 0x20000000 { // Only print the PC if it points into memory. // It may not point into memory during a stack overflow, so check that // first before accessing the stack. print(" pc=", sp.PC) } } println() abort() } // Descriptions are sourced from the K66 SVD and // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/Cihcfefj.html // GetFaultStatus reads the System Control Block Configurable Fault Status // Register and returns it as a FaultStatus. func GetFaultStatus() FaultStatus { return FaultStatus(arm.SCB.CFSR.Get()) } type FaultStatus uint32 type MemFaultStatus uint32 type BusFaultStatus uint32 type UsageFaultStatus uint32 func (fs FaultStatus) Mem() MemFaultStatus { return MemFaultStatus(fs) } func (fs FaultStatus) Bus() BusFaultStatus { return BusFaultStatus(fs) } func (fs FaultStatus) Usage() UsageFaultStatus { return UsageFaultStatus(fs) } // Unknown returns true if the cause of the fault is not know func (fs FaultStatus) Unknown() bool { return fs&SCB_CFSR_KnownFault == 0 } // InstructionAccessViolation: the processor attempted an instruction fetch from // a location that does not permit execution // // "This fault occurs on any access to an XN region, even when the MPU is // disabled or not present. // // When this bit is 1, the PC value stacked for the exception return points to // the faulting instruction. The processor has not written a fault address to // the MMAR." func (fs MemFaultStatus) InstructionAccessViolation() bool { return fs&arm.SCB_CFSR_IACCVIOL != 0 } // DataAccessViolation: the processor attempted a load or store at a location // that does not permit the operation // // "When this bit is 1, the PC value stacked for the exception return points to // the faulting instruction. The processor has loaded the MMAR with the address // of the attempted access." func (fs MemFaultStatus) DataAccessViolation() bool { return fs&arm.SCB_CFSR_DACCVIOL != 0 } // WhileUnstackingException: unstack for an exception return has caused one or // more access violations // // "This fault is chained to the handler. This means that when this bit is 1, // the original return stack is still present. The processor has not adjusted // the SP from the failing return, and has not performed a new save. The // processor has not written a fault address to the MMAR." func (fs MemFaultStatus) WhileUnstackingException() bool { return fs&arm.SCB_CFSR_MUNSTKERR != 0 } // WileStackingException: stacking for an exception entry has caused one or more // access violations // // "When this bit is 1, the SP is still adjusted but the values in the context // area on the stack might be incorrect. The processor has not written a fault // address to the MMAR." func (fs MemFaultStatus) WileStackingException() bool { return fs&arm.SCB_CFSR_MSTKERR != 0 } // DuringFPLazyStatePres: A MemManage fault occurred during floating-point lazy // state preservation func (fs MemFaultStatus) DuringFPLazyStatePres() bool { return fs&arm.SCB_CFSR_MLSPERR != 0 } // InstructionBusError: instruction bus error // // "The processor detects the instruction bus error on prefetching an // instruction, but it sets the IBUSERR flag to 1 only if it attempts to issue // the faulting instruction. // // When the processor sets this bit is 1, it does not write a fault address to // the BFAR." func (fs BusFaultStatus) InstructionBusError() bool { return fs&arm.SCB_CFSR_IBUSERR != 0 } // PreciseDataBusError: a data bus error has occurred, and the PC value stacked // for the exception return points to the instruction that caused the fault func (fs BusFaultStatus) PreciseDataBusError() bool { return fs&arm.SCB_CFSR_PRECISERR != 0 } // ImpreciseDataBusError: a data bus error has occurred, but the return address // in the stack frame is not related to the instruction that caused the error // // "When the processor sets this bit to 1, it does not write a fault address to // the BFAR. // // This is an asynchronous fault. Therefore, if it is detected when the priority // of the current process is higher than the BusFault priority, the BusFault // becomes pending and becomes active only when the processor returns from all // higher priority processes. If a precise fault occurs before the processor // enters the handler for the imprecise BusFault, the handler detects both // IMPRECISERR set to 1 and one of the precise fault status bits set to 1." func (fs BusFaultStatus) ImpreciseDataBusError() bool { return fs&arm.SCB_CFSR_IMPRECISERR != 0 } // WhileUnstackingException: unstack for an exception return has caused one or // more BusFaults // // "This fault is chained to the handler. This means that when the processor // sets this bit to 1, the original return stack is still present. The processor // does not adjust the SP from the failing return, does not performed a new // save, and does not write a fault address to the BFAR." func (fs BusFaultStatus) WhileUnstackingException() bool { return fs&arm.SCB_CFSR_UNSTKERR != 0 } // WhileStackingException: stacking for an exception entry has caused one or // more BusFaults // // "When the processor sets this bit to 1, the SP is still adjusted but the // values in the context area on the stack might be incorrect. The processor // does not write a fault address to the BFAR." func (fs BusFaultStatus) WhileStackingException() bool { return fs&arm.SCB_CFSR_STKERR != 0 } // DuringFPLazyStatePres: A bus fault occurred during floating-point lazy state // preservation func (fs BusFaultStatus) DuringFPLazyStatePres() bool { return fs&arm.SCB_CFSR_LSPERR != 0 } // UndefinedInstruction: the processor has attempted to execute an undefined // instruction // // "When this bit is set to 1, the PC value stacked for the exception return // points to the undefined instruction. // // An undefined instruction is an instruction that the processor cannot decode." func (fs UsageFaultStatus) UndefinedInstruction() bool { return fs&arm.SCB_CFSR_UNDEFINSTR != 0 } // IllegalUseOfEPSR: the processor has attempted to execute an instruction that // makes illegal use of the EPSR // // "When this bit is set to 1, the PC value stacked for the exception return // points to the instruction that attempted the illegal use of the EPSR. // // This bit is not set to 1 if an undefined instruction uses the EPSR." func (fs UsageFaultStatus) IllegalUseOfEPSR() bool { return fs&arm.SCB_CFSR_INVSTATE != 0 } // IllegalExceptionReturn: the processor has attempted an illegal load of // EXC_RETURN to the PC // // "When this bit is set to 1, the PC value stacked for the exception return // points to the instruction that tried to perform the illegal load of the PC." func (fs UsageFaultStatus) IllegalExceptionReturn() bool { return fs&arm.SCB_CFSR_INVPC != 0 } // AttemptedToAccessCoprocessor: the processor has attempted to access a // coprocessor func (fs UsageFaultStatus) AttemptedToAccessCoprocessor() bool { return fs&arm.SCB_CFSR_NOCP != 0 } // UnalignedMemoryAccess: the processor has made an unaligned memory access // // "Enable trapping of unaligned accesses by setting the UNALIGN_TRP bit in the // CCR to 1. // // Unaligned LDM, STM, LDRD, and STRD instructions always fault irrespective of // the setting of UNALIGN_TRP." func (fs UsageFaultStatus) UnalignedMemoryAccess() bool { return fs&arm.SCB_CFSR_UNALIGNED != 0 } // DivideByZero: the processor has executed an SDIV or UDIV instruction with a // divisor of 0 // // "When the processor sets this bit to 1, the PC value stacked for the // exception return points to the instruction that performed the divide by zero. // // Enable trapping of divide by zero by setting the DIV_0_TRP bit in the CCR to // 1." func (fs UsageFaultStatus) DivideByZero() bool { return fs&arm.SCB_CFSR_DIVBYZERO != 0 } // Address returns the MemManage Fault Address Register if the fault status // indicates the address is valid. // // "If a MemManage fault occurs and is escalated to a HardFault because of // priority, the HardFault handler must set this bit to 0. This prevents // problems on return to a stacked active MemManage fault handler whose MMAR // value has been overwritten." func (fs MemFaultStatus) Address() (uintptr, bool) { if fs&arm.SCB_CFSR_MMARVALID == 0 { return 0, false } else { return uintptr(arm.SCB.MMFAR.Get()), true } } // Address returns the BusFault Address Register if the fault status // indicates the address is valid. // // "The processor sets this bit to 1 after a BusFault where the address is // known. Other faults can set this bit to 0, such as a MemManage fault // occurring later. // // If a BusFault occurs and is escalated to a hard fault because of priority, // the hard fault handler must set this bit to 0. This prevents problems if // returning to a stacked active BusFault handler whose BFAR value has been // overwritten."" func (fs BusFaultStatus) Address() (uintptr, bool) { if fs&arm.SCB_CFSR_BFARVALID == 0 { return 0, false } else { return uintptr(arm.SCB.BFAR.Get()), true } } ================================================ FILE: src/runtime/runtime_cortexm_qemu.go ================================================ //go:build cortexm && qemu package runtime // This file implements the Stellaris LM3S6965 Cortex-M3 chip as implemented by // QEMU. import ( "device/arm" "runtime/volatile" "unsafe" ) var timestamp timeUnit //export Reset_Handler func main() { preinit() run() // Signal successful exit. exit(0) } func ticksToNanoseconds(ticks timeUnit) int64 { return int64(ticks) } func nanosecondsToTicks(ns int64) timeUnit { return timeUnit(ns) } func sleepTicks(d timeUnit) { // TODO: actually sleep here for the given time. timestamp += d } func ticks() timeUnit { return timestamp } // UART0 output register. var stdoutWrite = (*volatile.Register8)(unsafe.Pointer(uintptr(0x4000c000))) func putchar(c byte) { stdoutWrite.Set(uint8(c)) } func getchar() byte { // dummy, TODO return 0 } func buffered() int { // dummy, TODO return 0 } func waitForEvents() { arm.Asm("wfe") } func abort() { exit(1) } func exit(code int) { // Exit QEMU. if code == 0 { arm.SemihostingCall(arm.SemihostingReportException, arm.SemihostingApplicationExit) } else { arm.SemihostingCall(arm.SemihostingReportException, arm.SemihostingRunTimeErrorUnknown) } // Lock up forever (should be unreachable). for { arm.Asm("wfi") } } ================================================ FILE: src/runtime/runtime_esp32.go ================================================ //go:build esp32 package runtime import ( "device" "device/esp" "machine" ) // This is the function called on startup right after the stack pointer has been // set. // //export main func main() { // Disable the protection on the watchdog timer (needed when started from // the bootloader). esp.RTC_CNTL.WDTWPROTECT.Set(0x050D83AA1) // Disable both watchdog timers that are enabled by default on startup. // Note that these watchdogs can be protected, but the ROM bootloader // doesn't seem to protect them. esp.RTC_CNTL.WDTCONFIG0.Set(0) esp.TIMG0.WDTCONFIG0.Set(0) // Switch SoC clock source to PLL (instead of the default which is XTAL). // This switches the CPU (and APB) clock from 40MHz to 80MHz. // Options: // RTC_CNTL_CLK_CONF_SOC_CLK_SEL: PLL (1) (default XTAL) // RTC_CNTL_CLK_CONF_CK8M_DIV_SEL: 2 (default) // RTC_CNTL_CLK_CONF_DIG_CLK8M_D256_EN: Enable (default) // RTC_CNTL_CLK_CONF_CK8M_DIV: divide by 256 (default) // The only real change made here is modifying RTC_CNTL_CLK_CONF_SOC_CLK_SEL, // but setting a fixed value produces smaller code. esp.RTC_CNTL.CLK_CONF.Set((1 << esp.RTC_CNTL_CLK_CONF_SOC_CLK_SEL_Pos) | (2 << esp.RTC_CNTL_CLK_CONF_CK8M_DIV_SEL_Pos) | (1 << esp.RTC_CNTL_CLK_CONF_DIG_CLK8M_D256_EN_Pos) | (1 << esp.RTC_CNTL_CLK_CONF_CK8M_DIV_Pos)) // Switch CPU from 80MHz to 160MHz. This doesn't affect the APB clock, // which is still running at 80MHz. esp.DPORT.CPU_PER_CONF.Set(1) // PLL_CLK / 2, see table 3-3 in the reference manual // Clear .bss section. .data has already been loaded by the ROM bootloader. // Do this after increasing the CPU clock to possibly make startup slightly // faster. clearbss() // Initialize UART. machine.InitSerial() // Initialize main system timer used for time.Now. initTimer() // Initialize the heap, call main.main, etc. run() // Fallback: if main ever returns, hang the CPU. exit(0) } //go:extern _sbss var _sbss [0]byte //go:extern _ebss var _ebss [0]byte func abort() { for { device.Asm("waiti 0") } } ================================================ FILE: src/runtime/runtime_esp32c3.go ================================================ //go:build esp32c3 package runtime import ( "device/esp" "device/riscv" "machine" "runtime/volatile" "unsafe" ) // This is the function called on startup after the flash (IROM/DROM) is // initialized and the stack pointer has been set. // //export main func main() { // This initialization configures the following things: // * It disables all watchdog timers. They might be useful at some point in // the future, but will need integration into the scheduler. For now, // they're all disabled. // * It sets the CPU frequency to 160MHz, which is the maximum speed allowed // for this CPU. Lower frequencies might be possible in the future, but // running fast and sleeping quickly is often also a good strategy to save // power. // TODO: protect certain memory regions, especially the area below the stack // to protect against stack overflows. See // esp_cpu_configure_region_protection in ESP-IDF. // Disable Timer 0 watchdog. esp.TIMG0.WDTCONFIG0.Set(0) // Disable RTC watchdog. esp.RTC_CNTL.WDTWPROTECT.Set(0x50D83AA1) esp.RTC_CNTL.WDTCONFIG0.Set(0) // Disable super watchdog. esp.RTC_CNTL.SWD_WPROTECT.Set(0x8F1D312A) esp.RTC_CNTL.SWD_CONF.Set(esp.RTC_CNTL_SWD_CONF_SWD_DISABLE) // Change CPU frequency from 20MHz to 80MHz, by switching from the XTAL to // the PLL clock source (see table "CPU Clock Frequency" in the reference // manual). esp.SYSTEM.SYSCLK_CONF.Set(1 << esp.SYSTEM_SYSCLK_CONF_SOC_CLK_SEL_Pos) // Change CPU frequency from 80MHz to 160MHz by setting SYSTEM_CPUPERIOD_SEL // to 1 (see table "CPU Clock Frequency" in the reference manual). // Note: we might not want to set SYSTEM_CPU_WAIT_MODE_FORCE_ON to save // power. It is set here to keep the default on reset. esp.SYSTEM.CPU_PER_CONF.Set(esp.SYSTEM_CPU_PER_CONF_CPU_WAIT_MODE_FORCE_ON | esp.SYSTEM_CPU_PER_CONF_PLL_FREQ_SEL | 1<> 32)) sifive.CLINT.MTIMECMP.Set(uint32(target)) riscv.MIE.SetBits(riscv.MIE_MTIE) for { if timerWakeup.Get() != 0 { timerWakeup.Set(0) // Disable timer. break } riscv.Asm("wfi") } } // handleException is called from the interrupt handler for any exception. // Exceptions can be things like illegal instructions, invalid memory // read/write, and similar issues. func handleException(code uint) { // For a list of exception codes, see: // https://content.riscv.org/wp-content/uploads/2019/08/riscv-privileged-20190608-1.pdf#page=49 print("fatal error: exception with mcause=") print(code) print(" pc=") print(riscv.MEPC.Get()) println() abort() } ================================================ FILE: src/runtime/runtime_fe310_baremetal.go ================================================ //go:build fe310 && !qemu package runtime import ( "device/riscv" ) // ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds. func ticksToNanoseconds(ticks timeUnit) int64 { // The following calculation is actually the following, but with both sides // reduced to reduce the risk of overflow: // ticks * 1e9 / 32768 return int64(ticks) * 1953125 / 64 } // nanosecondsToTicks converts nanoseconds to RTC ticks (running at 32768Hz). func nanosecondsToTicks(ns int64) timeUnit { // The following calculation is actually the following, but with both sides // reduced to reduce the risk of overflow: // ns * 32768 / 1e9 return timeUnit(ns * 64 / 1953125) } func exit(code int) { abort() } func abort() { // lock up forever for { riscv.Asm("wfi") } } ================================================ FILE: src/runtime/runtime_fe310_qemu.go ================================================ //go:build fe310 && qemu package runtime import ( "runtime/volatile" "unsafe" ) // Special memory-mapped device to exit tests, created by SiFive. var testExit = (*volatile.Register32)(unsafe.Pointer(uintptr(0x100000))) // ticksToNanoseconds converts CLINT ticks (at 100ns per tick) to nanoseconds. func ticksToNanoseconds(ticks timeUnit) int64 { return int64(ticks) * 100 } // nanosecondsToTicks converts nanoseconds to CLINT ticks (at 100ns per tick). func nanosecondsToTicks(ns int64) timeUnit { return timeUnit(ns / 100) } func abort() { exit(1) } func exit(code int) { if code == 0 { // Signal a successful exit. testExit.Set(0x5555) // FINISHER_PASS } else { // Signal a failure. The exit code is stored in the upper 16 bits of the // 32 bit value. testExit.Set(uint32(code)<<16 | 0x3333) // FINISHER_FAIL } } ================================================ FILE: src/runtime/runtime_k210.go ================================================ //go:build k210 // This file implements target-specific things for the K210 chip as used in the // MAix Bit with Mic. package runtime import ( "device/kendryte" "device/riscv" "machine" "runtime/volatile" "unsafe" ) //export main func main() { // Both harts should disable all interrupts on startup. initPLIC() // Only use one hart for the moment. if riscv.MHARTID.Get() != 0 { abort() } // Reset all interrupt source priorities to zero. for i := 0; i < kendryte.IRQ_max; i++ { kendryte.PLIC.PRIORITY[i].Set(0) } // Zero MCAUSE, which is set to the reset reason on reset. It must be zeroed // to make interrupt.In() work. // This would also be a good time to save the reset reason, but that hasn't // been implemented yet. riscv.MCAUSE.Set(0) // Set the interrupt address. // Note that this address must be aligned specially, otherwise the MODE bits // of MTVEC won't be zero. riscv.MTVEC.Set(uintptr(unsafe.Pointer(&handleInterruptASM))) // Reset the MIE register and enable external interrupts. // It must be reset here because it not zeroed at startup. riscv.MIE.Set(riscv.MIE_MEIE) // MEIE is for machine external interrupts // Enable global interrupts now that they've been set up. riscv.MSTATUS.SetBits(riscv.MSTATUS_MIE) preinit() initPeripherals() run() exit(0) } func initPLIC() { hartId := riscv.MHARTID.Get() // Zero the PLIC enable bits at startup. for i := 0; i < ((kendryte.IRQ_max + 32) / 32); i++ { kendryte.PLIC.TARGET_ENABLES[hartId].ENABLE[i].Set(0) } // Zero the PLIC threshold bits to allow all interrupts. kendryte.PLIC.TARGETS[hartId].THRESHOLD.Set(0) } //go:extern handleInterruptASM var handleInterruptASM [0]uintptr //export handleInterrupt func handleInterrupt() { cause := riscv.MCAUSE.Get() code := uint64(cause &^ (1 << 63)) if cause&(1<<63) != 0 { // Topmost bit is set, which means that it is an interrupt. switch code { case riscv.MachineTimerInterrupt: // Signal timeout. timerWakeup.Set(1) // Disable the timer, to avoid triggering the interrupt right after // this interrupt returns. riscv.MIE.ClearBits(riscv.MIE_MTIE) case riscv.MachineExternalInterrupt: hartId := riscv.MHARTID.Get() // Claim this interrupt. id := kendryte.PLIC.TARGETS[hartId].CLAIM.Get() // Call the interrupt handler, if any is registered for this ID. kendryte.HandleInterrupt(int(id)) // Complete this interrupt. kendryte.PLIC.TARGETS[hartId].CLAIM.Set(id) } } else { // Topmost bit is clear, so it is an exception of some sort. // We could implement support for unsupported instructions here (such as // misaligned loads). However, for now we'll just print a fatal error. handleException(code) } // Zero MCAUSE so that it can later be used to see whether we're in an // interrupt or not. riscv.MCAUSE.Set(0) } // initPeripherals configures peripherals the way the runtime expects them. func initPeripherals() { // Enable APB0 clock. kendryte.SYSCTL.CLK_EN_CENT.SetBits(kendryte.SYSCTL_CLK_EN_CENT_APB0_CLK_EN) // Enable FPIOA peripheral. kendryte.SYSCTL.CLK_EN_PERI.SetBits(kendryte.SYSCTL_CLK_EN_PERI_FPIOA_CLK_EN) machine.InitSerial() } func putchar(c byte) { machine.Serial.WriteByte(c) } func getchar() byte { for machine.Serial.Buffered() == 0 { Gosched() } v, _ := machine.Serial.ReadByte() return v } func buffered() int { return machine.Serial.Buffered() } var timerWakeup volatile.Register8 func ticks() timeUnit { highBits := uint32(kendryte.CLINT.MTIME.Get() >> 32) for { lowBits := uint32(kendryte.CLINT.MTIME.Get() & 0xffffffff) newHighBits := uint32(kendryte.CLINT.MTIME.Get() >> 32) if newHighBits == highBits { return timeUnit(lowBits) | (timeUnit(highBits) << 32) } highBits = newHighBits } } func sleepTicks(d timeUnit) { target := uint64(ticks() + d) kendryte.CLINT.MTIMECMP[0].Set(target) riscv.MIE.SetBits(riscv.MIE_MTIE) for { if timerWakeup.Get() != 0 { timerWakeup.Set(0) // Disable timer. break } riscv.Asm("wfi") } } // handleException is called from the interrupt handler for any exception. // Exceptions can be things like illegal instructions, invalid memory // read/write, and similar issues. func handleException(code uint64) { // For a list of exception codes, see: // https://content.riscv.org/wp-content/uploads/2019/08/riscv-privileged-20190608-1.pdf#page=49 print("fatal error: exception with mcause=") print(code) print(" pc=") print(riscv.MEPC.Get()) println() abort() } ================================================ FILE: src/runtime/runtime_k210_baremetal.go ================================================ //go:build k210 && !qemu package runtime import ( "device/riscv" ) // ticksToNanoseconds converts CPU ticks to nanoseconds. func ticksToNanoseconds(ticks timeUnit) int64 { // The following calculation is actually the following, but with both sides // reduced to reduce the risk of overflow: // ticks * 1e9 / (390000000 / 50) // 50 is the CLINT divider and 390000000 is the CPU frequency. return int64(ticks) * 5000 / 39 } // nanosecondsToTicks converts nanoseconds to CPU ticks. func nanosecondsToTicks(ns int64) timeUnit { return timeUnit(ns * 39 / 5000) } func exit(code int) { abort() } func abort() { // lock up forever for { riscv.Asm("wfi") } } ================================================ FILE: src/runtime/runtime_mimxrt1062.go ================================================ //go:build mimxrt1062 package runtime import ( "device/arm" "device/nxp" "machine" "math/bits" "unsafe" ) //go:extern _svectors var _svectors [0]byte //go:extern _flexram_cfg var _flexram_cfg [0]byte //export Reset_Handler func main() { // disable interrupts irq := arm.DisableInterrupts() // initialize FPU and VTOR, reset watchdogs initSystem() // configure core and peripheral clocks/PLLs/PFDs initClocks() // copy data/bss sections from flash to RAM preinit() // initialize cache and MPU initCache() // enable SysTick, GPIO, and peripherals initPeripherals() // reenable interrupts arm.EnableInterrupts(irq) run() exit(0) } func getRamSizeConfig(itcmKB, dtcmKB uint32) uint32 { const minKB, disabled = uint32(4), uint32(0) if itcmKB < minKB { itcmKB = disabled } if dtcmKB < minKB { dtcmKB = disabled } itcmKB = uint32(bits.Len(uint(itcmKB))) << nxp.IOMUXC_GPR_GPR14_CM7_CFGITCMSZ_Pos dtcmKB = uint32(bits.Len(uint(dtcmKB))) << nxp.IOMUXC_GPR_GPR14_CM7_CFGDTCMSZ_Pos return (itcmKB & nxp.IOMUXC_GPR_GPR14_CM7_CFGITCMSZ_Msk) | (dtcmKB & nxp.IOMUXC_GPR_GPR14_CM7_CFGDTCMSZ_Msk) } func initSystem() { // configure SRAM capacity (512K for both ITCM and DTCM) ramc := uintptr(unsafe.Pointer(&_flexram_cfg)) nxp.IOMUXC_GPR.GPR17.Set(uint32(ramc)) nxp.IOMUXC_GPR.GPR16.Set(0x00200007) nxp.IOMUXC_GPR.GPR14.Set(getRamSizeConfig(512, 512)) // from Teensyduino nxp.PMU.MISC0_SET.Set(nxp.PMU_MISC0_REFTOP_SELFBIASOFF) // install vector table (TODO: initialize interrupt/exception table?) vtor := uintptr(unsafe.Pointer(&_svectors)) nxp.SystemControl.VTOR.Set(uint32(vtor)) const wdogUpdateKey = 0xD928C520 // disable watchdog powerdown counter nxp.WDOG1.WMCR.ClearBits(nxp.WDOG_WMCR_PDE_Msk) nxp.WDOG2.WMCR.ClearBits(nxp.WDOG_WMCR_PDE_Msk) // disable watchdog if nxp.WDOG1.WCR.HasBits(nxp.WDOG_WCR_WDE_Msk) { nxp.WDOG1.WCR.ClearBits(nxp.WDOG_WCR_WDE_Msk) } if nxp.WDOG2.WCR.HasBits(nxp.WDOG_WCR_WDE_Msk) { nxp.WDOG2.WCR.ClearBits(nxp.WDOG_WCR_WDE_Msk) } if nxp.RTWDOG.CS.HasBits(nxp.RTWDOG_CS_CMD32EN_Msk) { nxp.RTWDOG.CNT.Set(wdogUpdateKey) } else { nxp.RTWDOG.CNT.Set((wdogUpdateKey >> 0) & 0xFFFF) nxp.RTWDOG.CNT.Set((wdogUpdateKey >> 16) & 0xFFFF) } nxp.RTWDOG.TOVAL.Set(0xFFFF) nxp.RTWDOG.CS.Set((nxp.RTWDOG.CS.Get() & ^uint32(nxp.RTWDOG_CS_EN_Msk)) | nxp.RTWDOG_CS_UPDATE_Msk) } func initPeripherals() { enableTimerClocks() // activate GPT/PIT clock gates initSysTick() // enable SysTick initRTC() // enable real-time clock enablePinClocks() // activate IOMUXC(_GPR)/GPIO clock gates initPins() // configure GPIO enablePeripheralClocks() // activate peripheral clock gates initUART() // configure UART (initialized first for debugging) } func initPins() { // use fast GPIO for all pins (GPIO6-9) nxp.IOMUXC_GPR.GPR26.Set(0xFFFFFFFF) nxp.IOMUXC_GPR.GPR27.Set(0xFFFFFFFF) nxp.IOMUXC_GPR.GPR28.Set(0xFFFFFFFF) nxp.IOMUXC_GPR.GPR29.Set(0xFFFFFFFF) } func initUART() { machine.InitSerial() } func putchar(c byte) { machine.Serial.WriteByte(c) } func getchar() byte { for machine.UART1.Buffered() == 0 { Gosched() } v, _ := machine.UART1.ReadByte() return v } func buffered() int { return machine.UART1.Buffered() } func exit(code int) { abort() } func abort() { for { arm.Asm("wfe") } } func waitForEvents() { arm.Asm("wfe") } ================================================ FILE: src/runtime/runtime_mimxrt1062_clock.go ================================================ //go:build mimxrt1062 package runtime import ( "device/nxp" ) // Core clock frequencies (Hz) const ( CORE_FREQ = 600000000 // 600 MHz OSC_FREQ = 24000000 // 24 MHz ) // Note from Teensyduino (cores/teensy4/startup.c): // // | ARM SysTick is used for most Arduino timing functions, delay(), millis(), // | micros(). SysTick can run from either the ARM core clock, or from an // | "external" clock. NXP documents it as "24 MHz XTALOSC can be the external // | clock source of SYSTICK" (RT1052 ref manual, rev 1, page 411). However, // | NXP actually hid an undocumented divide-by-240 circuit in the hardware, so // | the external clock is really 100 kHz. We use this clock rather than the // | ARM clock, to allow SysTick to maintain correct timing even when we change // | the ARM clock to run at different speeds. const SYSTICK_FREQ = 100000 // 100 kHz var ( ArmPllConfig = nxp.ClockConfigArmPll{ LoopDivider: 100, // PLL loop divider, Fout=Fin*50 Src: 0, // bypass clock source, 0=OSC24M, 1=CLK1_P & CLK1_N } SysPllConfig = nxp.ClockConfigSysPll{ LoopDivider: 1, // PLL loop divider, Fout=Fin*(20+LOOP*2+NUMER/DENOM) Numerator: 0, // 30-bit NUMER of fractional loop divider Denominator: 1, // 30-bit DENOM of fractional loop divider Src: 0, // bypass clock source, 0=OSC24M, 1=CLK1_P & CLK1_N } Usb1PllConfig = nxp.ClockConfigUsbPll{ Instance: 1, // USB PLL instance LoopDivider: 0, // PLL loop divider, Fout=Fin*20 Src: 0, // bypass clock source, 0=OSC24M, 1=CLK1_P & CLK1_N } Usb2PllConfig = nxp.ClockConfigUsbPll{ Instance: 2, // USB PLL instance LoopDivider: 0, // PLL loop divider, Fout=Fin*20 Src: 0, // bypass clock source, 0=OSC24M, 1=CLK1_P & CLK1_N } ) // initClocks configures the core, buses, and all peripherals' clock source mux // and dividers for runtime. The clock gates for individual peripherals are all // disabled prior to configuration and must be enabled afterwards using one of // these `enable*Clocks()` functions or the respective peripheral clocks' // `Enable()` method from the "device/nxp" package. func initClocks() { // disable low-power mode so that __WFI doesn't lock up at runtime. // see: Using the MIMXRT1060/4-EVK with MCUXpresso IDE v10.3.x (v1.0.2, // 2019MAR01), chapter 14 nxp.ClockModeRun.Set() // enable and use 1MHz clock output nxp.XTALOSC24M.OSC_CONFIG2.SetBits(nxp.XTALOSC24M_OSC_CONFIG2_ENABLE_1M_Msk) nxp.XTALOSC24M.OSC_CONFIG2.ClearBits(nxp.XTALOSC24M_OSC_CONFIG2_MUX_1M_Msk) // initialize external 24 MHz clock nxp.CCM_ANALOG.MISC0_CLR.Set(nxp.CCM_ANALOG_MISC0_XTAL_24M_PWD_Msk) // power for !nxp.XTALOSC24M.LOWPWR_CTRL.HasBits(nxp.XTALOSC24M_LOWPWR_CTRL_XTALOSC_PWRUP_STAT_Msk) { } nxp.CCM_ANALOG.MISC0_SET.Set(nxp.CCM_ANALOG_MISC0_OSC_XTALOK_EN_Msk) // detect freq for !nxp.CCM_ANALOG.MISC0.HasBits(nxp.CCM_ANALOG_MISC0_OSC_XTALOK_Msk) { } nxp.CCM_ANALOG.MISC0_CLR.Set(nxp.CCM_ANALOG_MISC0_OSC_XTALOK_EN_Msk) // initialize internal RC OSC 24 MHz, and switch clock source to external OSC nxp.XTALOSC24M.LOWPWR_CTRL.SetBits(nxp.XTALOSC24M_LOWPWR_CTRL_RC_OSC_EN_Msk) nxp.XTALOSC24M.LOWPWR_CTRL_CLR.Set(nxp.XTALOSC24M_LOWPWR_CTRL_CLR_OSC_SEL_Msk) // set oscillator ready counter value nxp.CCM.CCR.Set((nxp.CCM.CCR.Get() & ^uint32(nxp.CCM_CCR_OSCNT_Msk)) | ((127 << nxp.CCM_CCR_OSCNT_Pos) & nxp.CCM_CCR_OSCNT_Msk)) // set PERIPH2_CLK and PERIPH to provide stable clock before PLLs initialed nxp.MuxIpPeriphClk2.Mux(1) // PERIPH_CLK2 select OSC24M nxp.MuxIpPeriph.Mux(1) // PERIPH select PERIPH_CLK2 // set VDD_SOC to 1.275V, necessary to config AHB to 600 MHz nxp.DCDC.REG3.Set((nxp.DCDC.REG3.Get() & ^uint32(nxp.DCDC_REG3_TRG_Msk)) | ((13 << nxp.DCDC_REG3_TRG_Pos) & nxp.DCDC_REG3_TRG_Msk)) // wait until DCDC_STS_DC_OK bit is asserted for !nxp.DCDC.REG0.HasBits(nxp.DCDC_REG0_STS_DC_OK_Msk) { } nxp.DivIpAhb.Div(0) // divide AHB_PODF (DIV1) nxp.ClockIpAdc1.Enable(false) // disable ADC nxp.ClockIpAdc2.Enable(false) // nxp.ClockIpXbar1.Enable(false) // disable XBAR nxp.ClockIpXbar2.Enable(false) // nxp.ClockIpXbar3.Enable(false) // nxp.DivIpIpg.Div(3) // divide IPG_PODF (DIV4) nxp.DivIpArm.Div(1) // divide ARM_PODF (DIV2) nxp.DivIpPeriphClk2.Div(0) // divide PERIPH_CLK2_PODF (DIV1) nxp.ClockIpGpt1.Enable(false) // disable GPT/PIT nxp.ClockIpGpt1S.Enable(false) // nxp.ClockIpGpt2.Enable(false) // nxp.ClockIpGpt2S.Enable(false) // nxp.ClockIpPit.Enable(false) // nxp.DivIpPerclk.Div(0) // divide PERCLK_PODF (DIV1) nxp.ClockIpUsdhc1.Enable(false) // disable USDHC1 nxp.DivIpUsdhc1.Div(1) // divide USDHC1_PODF (DIV2) nxp.MuxIpUsdhc1.Mux(1) // USDHC1 select PLL2_PFD0 nxp.ClockIpUsdhc2.Enable(false) // disable USDHC2 nxp.DivIpUsdhc2.Div(1) // divide USDHC2_PODF (DIV2) nxp.MuxIpUsdhc2.Mux(1) // USDHC2 select PLL2_PFD0 nxp.ClockIpSemc.Enable(false) // disable SEMC nxp.DivIpSemc.Div(1) // divide SEMC_PODF (DIV2) nxp.MuxIpSemcAlt.Mux(0) // SEMC_ALT select PLL2_PFD2 nxp.MuxIpSemc.Mux(1) // SEMC select SEMC_ALT if false { // TODO: external flash is on this bus, configured via DCD block nxp.ClockIpFlexSpi.Enable(false) // disable FLEXSPI nxp.DivIpFlexSpi.Div(0) // divide FLEXSPI_PODF (DIV1) nxp.MuxIpFlexSpi.Mux(2) // FLEXSPI select PLL2_PFD2 } nxp.ClockIpFlexSpi2.Enable(false) // disable FLEXSPI2 nxp.DivIpFlexSpi2.Div(0) // divide FLEXSPI2_PODF (DIV1) nxp.MuxIpFlexSpi2.Mux(0) // FLEXSPI2 select PLL2_PFD2 nxp.ClockIpCsi.Enable(false) // disable CSI nxp.DivIpCsi.Div(1) // divide CSI_PODF (DIV2) nxp.MuxIpCsi.Mux(0) // CSI select OSC24M nxp.ClockIpLpspi1.Enable(false) // disable LPSPI nxp.ClockIpLpspi2.Enable(false) // nxp.ClockIpLpspi3.Enable(false) // nxp.ClockIpLpspi4.Enable(false) // nxp.DivIpLpspi.Div(3) // divide LPSPI_PODF (DIV4) nxp.MuxIpLpspi.Mux(2) // LPSPI select PLL2 nxp.ClockIpTrace.Enable(false) // disable TRACE nxp.DivIpTrace.Div(3) // divide TRACE_PODF (DIV4) nxp.MuxIpTrace.Mux(0) // TRACE select PLL2_MAIN nxp.ClockIpSai1.Enable(false) // disable SAI1 nxp.DivIpSai1Pre.Div(3) // divide SAI1_CLK_PRED (DIV4) nxp.DivIpSai1.Div(1) // divide SAI1_CLK_PODF (DIV2) nxp.MuxIpSai1.Mux(0) // SAI1 select PLL3_PFD2 nxp.ClockIpSai2.Enable(false) // disable SAI2 nxp.DivIpSai2Pre.Div(3) // divide SAI2_CLK_PRED (DIV4) nxp.DivIpSai2.Div(1) // divide SAI2_CLK_PODF (DIV2) nxp.MuxIpSai2.Mux(0) // SAI2 select PLL3_PFD2 nxp.ClockIpSai3.Enable(false) // disable SAI3 nxp.DivIpSai3Pre.Div(3) // divide SAI3_CLK_PRED (DIV4) nxp.DivIpSai3.Div(1) // divide SAI3_CLK_PODF (DIV2) nxp.MuxIpSai3.Mux(0) // SAI3 select PLL3_PFD2 nxp.ClockIpLpi2c1.Enable(false) // disable LPI2C nxp.ClockIpLpi2c2.Enable(false) // nxp.ClockIpLpi2c3.Enable(false) // nxp.ClockIpLpi2c4.Enable(false) // nxp.DivIpLpi2c.Div(0) // divide LPI2C_CLK_PODF (DIV1) nxp.MuxIpLpi2c.Mux(1) // LPI2C select OSC nxp.ClockIpCan1.Enable(false) // disable CAN nxp.ClockIpCan2.Enable(false) // nxp.ClockIpCan3.Enable(false) // nxp.ClockIpCan1S.Enable(false) // nxp.ClockIpCan2S.Enable(false) // nxp.ClockIpCan3S.Enable(false) // nxp.DivIpCan.Div(1) // divide CAN_CLK_PODF (DIV2) nxp.MuxIpCan.Mux(2) // CAN select PLL3_SW_80M nxp.ClockIpLpuart1.Enable(false) // disable UART nxp.ClockIpLpuart2.Enable(false) // nxp.ClockIpLpuart3.Enable(false) // nxp.ClockIpLpuart4.Enable(false) // nxp.ClockIpLpuart5.Enable(false) // nxp.ClockIpLpuart6.Enable(false) // nxp.ClockIpLpuart7.Enable(false) // nxp.ClockIpLpuart8.Enable(false) // nxp.DivIpUart.Div(0) // divide UART_CLK_PODF (DIV1) nxp.MuxIpUart.Mux(1) // UART select OSC nxp.ClockIpLcdPixel.Enable(false) // disable LCDIF nxp.DivIpLcdifPre.Div(1) // divide LCDIF_PRED (DIV2) nxp.DivIpLcdif.Div(3) // divide LCDIF_CLK_PODF (DIV4) nxp.MuxIpLcdifPre.Mux(5) // LCDIF_PRE select PLL3_PFD1 nxp.ClockIpSpdif.Enable(false) // disable SPDIF nxp.DivIpSpdif0Pre.Div(1) // divide SPDIF0_CLK_PRED (DIV2) nxp.DivIpSpdif0.Div(7) // divide SPDIF0_CLK_PODF (DIV8) nxp.MuxIpSpdif.Mux(3) // SPDIF select PLL3_SW nxp.ClockIpFlexio1.Enable(false) // disable FLEXIO1 nxp.DivIpFlexio1Pre.Div(1) // divide FLEXIO1_CLK_PRED (DIV2) nxp.DivIpFlexio1.Div(7) // divide FLEXIO1_CLK_PODF (DIV8) nxp.MuxIpFlexio1.Mux(3) // FLEXIO1 select PLL3_SW nxp.ClockIpFlexio2.Enable(false) // disable FLEXIO2 nxp.DivIpFlexio2Pre.Div(1) // divide FLEXIO2_CLK_PRED (DIV2) nxp.DivIpFlexio2.Div(7) // divide FLEXIO2_CLK_PODF (DIV8) nxp.MuxIpFlexio2.Mux(3) // FLEXIO2 select PLL3_SW nxp.MuxIpPll3Sw.Mux(0) // PLL3_SW select PLL3_MAIN ArmPllConfig.Configure() // init ARM PLL // SYS PLL (PLL2) @ 528 MHz // PFD0 = 396 MHz -> USDHC1/USDHC2(DIV2)=198 MHz // PFD1 = 594 MHz -> (currently unused) // PFD2 = 327.72 MHz -> SEMC(DIV2)=163.86 MHz, FlexSPI/FlexSPI2=327.72 MHz // PFD3 = 454.73 MHz -> (currently unused) SysPllConfig.Configure(24, 16, 29, 16) // init SYS PLL and PFDs // USB1 PLL (PLL3) @ 480 MHz // PFD0 -> (currently unused) // PFD1 -> (currently unused) // PFD2 -> (currently unused) // PFD3 -> (currently unused) Usb1PllConfig.Configure() // init USB1 PLL and PFDs Usb2PllConfig.Configure() // init USB2 PLL nxp.MuxIpPrePeriph.Mux(3) // PRE_PERIPH select ARM_PLL nxp.MuxIpPeriph.Mux(0) // PERIPH select PRE_PERIPH nxp.MuxIpPeriphClk2.Mux(1) // PERIPH_CLK2 select OSC nxp.MuxIpPerclk.Mux(1) // PERCLK select OSC // set LVDS1 clock source nxp.CCM_ANALOG.MISC1.Set((nxp.CCM_ANALOG.MISC1.Get() & ^uint32(nxp.CCM_ANALOG_MISC1_LVDS1_CLK_SEL_Msk)) | ((0 << nxp.CCM_ANALOG_MISC1_LVDS1_CLK_SEL_Pos) & nxp.CCM_ANALOG_MISC1_LVDS1_CLK_SEL_Msk)) // set CLOCK_OUT1 divider nxp.CCM.CCOSR.Set((nxp.CCM.CCOSR.Get() & ^uint32(nxp.CCM_CCOSR_CLKO1_DIV_Msk)) | ((0 << nxp.CCM_CCOSR_CLKO1_DIV_Pos) & nxp.CCM_CCOSR_CLKO1_DIV_Msk)) // set CLOCK_OUT1 source nxp.CCM.CCOSR.Set((nxp.CCM.CCOSR.Get() & ^uint32(nxp.CCM_CCOSR_CLKO1_SEL_Msk)) | ((1 << nxp.CCM_CCOSR_CLKO1_SEL_Pos) & nxp.CCM_CCOSR_CLKO1_SEL_Msk)) // set CLOCK_OUT2 divider nxp.CCM.CCOSR.Set((nxp.CCM.CCOSR.Get() & ^uint32(nxp.CCM_CCOSR_CLKO2_DIV_Msk)) | ((0 << nxp.CCM_CCOSR_CLKO2_DIV_Pos) & nxp.CCM_CCOSR_CLKO2_DIV_Msk)) // set CLOCK_OUT2 source nxp.CCM.CCOSR.Set((nxp.CCM.CCOSR.Get() & ^uint32(nxp.CCM_CCOSR_CLKO2_SEL_Msk)) | ((18 << nxp.CCM_CCOSR_CLKO2_SEL_Pos) & nxp.CCM_CCOSR_CLKO2_SEL_Msk)) nxp.CCM.CCOSR.ClearBits(nxp.CCM_CCOSR_CLK_OUT_SEL_Msk) // set CLK_OUT1 drives CLK_OUT nxp.CCM.CCOSR.SetBits(nxp.CCM_CCOSR_CLKO1_EN_Msk) // enable CLK_OUT1 nxp.CCM.CCOSR.SetBits(nxp.CCM_CCOSR_CLKO2_EN_Msk) // enable CLK_OUT2 nxp.ClockIpIomuxcGpr.Enable(false) // disable IOMUXC_GPR nxp.ClockIpIomuxc.Enable(false) // disable IOMUXC // set GPT1 High frequency reference clock source nxp.IOMUXC_GPR.GPR5.ClearBits(nxp.IOMUXC_GPR_GPR5_VREF_1M_CLK_GPT1_Msk) // set GPT2 High frequency reference clock source nxp.IOMUXC_GPR.GPR5.ClearBits(nxp.IOMUXC_GPR_GPR5_VREF_1M_CLK_GPT2_Msk) nxp.ClockIpGpio1.Enable(false) // disable GPIO nxp.ClockIpGpio2.Enable(false) // nxp.ClockIpGpio3.Enable(false) // nxp.ClockIpGpio4.Enable(false) // } func enableTimerClocks() { nxp.ClockIpGpt1.Enable(true) // enable GPT/PIT nxp.ClockIpGpt1S.Enable(true) // nxp.ClockIpGpt2.Enable(true) // nxp.ClockIpGpt2S.Enable(true) // nxp.ClockIpPit.Enable(true) // } func enablePinClocks() { nxp.ClockIpIomuxcGpr.Enable(true) // enable IOMUXC nxp.ClockIpIomuxc.Enable(true) // nxp.ClockIpGpio1.Enable(true) // enable GPIO nxp.ClockIpGpio2.Enable(true) // nxp.ClockIpGpio3.Enable(true) // nxp.ClockIpGpio4.Enable(true) // } func enablePeripheralClocks() { nxp.ClockIpAdc1.Enable(true) // enable ADC nxp.ClockIpAdc2.Enable(true) // nxp.ClockIpXbar1.Enable(true) // enable XBAR nxp.ClockIpXbar2.Enable(true) // nxp.ClockIpXbar3.Enable(true) // nxp.ClockIpUsdhc1.Enable(true) // enable USDHC nxp.ClockIpUsdhc2.Enable(true) // nxp.ClockIpSemc.Enable(true) // enable SEMC nxp.ClockIpFlexSpi2.Enable(true) // enable FLEXSPI2 nxp.ClockIpLpspi1.Enable(true) // enable LPSPI nxp.ClockIpLpspi2.Enable(true) // nxp.ClockIpLpspi3.Enable(true) // nxp.ClockIpLpspi4.Enable(true) // nxp.ClockIpLpi2c1.Enable(true) // enable LPI2C nxp.ClockIpLpi2c2.Enable(true) // nxp.ClockIpLpi2c3.Enable(true) // nxp.ClockIpLpi2c4.Enable(true) // nxp.ClockIpCan1.Enable(true) // enable CAN nxp.ClockIpCan2.Enable(true) // nxp.ClockIpCan3.Enable(true) // nxp.ClockIpCan1S.Enable(true) // nxp.ClockIpCan2S.Enable(true) // nxp.ClockIpCan3S.Enable(true) // nxp.ClockIpLpuart1.Enable(true) // enable UART nxp.ClockIpLpuart2.Enable(true) // nxp.ClockIpLpuart3.Enable(true) // nxp.ClockIpLpuart4.Enable(true) // nxp.ClockIpLpuart5.Enable(true) // nxp.ClockIpLpuart6.Enable(true) // nxp.ClockIpLpuart7.Enable(true) // nxp.ClockIpLpuart8.Enable(true) // nxp.ClockIpFlexio1.Enable(true) // enable FLEXIO nxp.ClockIpFlexio2.Enable(true) // } ================================================ FILE: src/runtime/runtime_mimxrt1062_mpu.go ================================================ //go:build mimxrt1062 package runtime import ( "device/nxp" ) func initCache() { nxp.MPU.Enable(false) // add Default [0] region to deny access to whole address space to workaround // speculative prefetch. Refer to Arm errata 1013783-B for more details. // [0] Default {OVERLAY}: 4 GiB, -access, @device, -exec, -share, -cache, -buffer, -subregion nxp.MPU.SetRBAR(0, 0x00000000) nxp.MPU.SetRASR(nxp.RGNSZ_4GB, nxp.PERM_NONE, nxp.EXTN_DEVICE, false, false, false, false, false) // [1] Peripherals {OVERLAY}: 64 MiB, +ACCESS, @device, +EXEC, -share, -cache, -buffer, -subregion nxp.MPU.SetRBAR(1, 0x40000000) nxp.MPU.SetRASR(nxp.RGNSZ_64MB, nxp.PERM_FULL, nxp.EXTN_DEVICE, true, false, false, false, false) // [2] RAM {OVERLAY}: 1 GiB, +ACCESS, @device, +EXEC, -share, -cache, -buffer, -subregion nxp.MPU.SetRBAR(2, 0x00000000) nxp.MPU.SetRASR(nxp.RGNSZ_1GB, nxp.PERM_FULL, nxp.EXTN_DEVICE, true, false, false, false, false) // [3] ITCM: 512 KiB, +ACCESS, #NORMAL, +EXEC, -share, -cache, -buffer, -subregion nxp.MPU.SetRBAR(3, 0x00000000) nxp.MPU.SetRASR(nxp.RGNSZ_512KB, nxp.PERM_FULL, nxp.EXTN_NORMAL, true, false, false, false, false) // [4] DTCM: 512 KiB, +ACCESS, #NORMAL, +EXEC, -share, -cache, -buffer, -subregion nxp.MPU.SetRBAR(4, 0x20000000) nxp.MPU.SetRASR(nxp.RGNSZ_512KB, nxp.PERM_FULL, nxp.EXTN_NORMAL, true, false, false, false, false) // [5] RAM (AXI): 512 KiB, +ACCESS, #NORMAL, +EXEC, -share, +CACHE, +BUFFER, -subregion nxp.MPU.SetRBAR(5, 0x20200000) nxp.MPU.SetRASR(nxp.RGNSZ_512KB, nxp.PERM_FULL, nxp.EXTN_NORMAL, true, false, true, true, false) // [6] FlexSPI: 512 MiB, +ACCESS, #NORMAL, +EXEC, -share, +CACHE, +BUFFER, -subregion nxp.MPU.SetRBAR(6, 0x70000000) nxp.MPU.SetRASR(nxp.RGNSZ_512MB, nxp.PERM_FULL, nxp.EXTN_NORMAL, true, false, true, true, false) // [7] QSPI flash: 2 MiB, +ACCESS, #NORMAL, +EXEC, -share, +CACHE, +BUFFER, -subregion nxp.MPU.SetRBAR(7, 0x60000000) nxp.MPU.SetRASR(nxp.RGNSZ_2MB, nxp.PERM_FULL, nxp.EXTN_NORMAL, true, false, true, true, false) nxp.MPU.Enable(true) } ================================================ FILE: src/runtime/runtime_mimxrt1062_time.go ================================================ //go:build mimxrt1062 package runtime import ( "device/arm" "device/nxp" "runtime/interrupt" "runtime/volatile" "unsafe" ) const ( lastCycle = SYSTICK_FREQ/1000 - 1 cyclesPerMicro = CORE_FREQ / 1000000 ) const ( pitFreq = OSC_FREQ // PIT/GPT are muxed to 24 MHz OSC pitCyclesPerMicro = pitFreq / 1000000 pitSleepTimer = 0 // x4 32-bit PIT timers [0..3] ) var ( tickCount volatile.Register64 cycleCount volatile.Register32 pitActive volatile.Register32 pitTimeout interrupt.Interrupt ) var ( // debug exception and monitor control DEM_CR = (*volatile.Register32)(unsafe.Pointer(uintptr(0xe000edfc))) DWT_CR = (*volatile.Register32)(unsafe.Pointer(uintptr(0xe0001000))) DWT_CYCCNT = (*volatile.Register32)(unsafe.Pointer(uintptr(0xe0001004))) ) func ticksToNanoseconds(ticks timeUnit) int64 { return int64(ticks) * 1000 } func nanosecondsToTicks(ns int64) timeUnit { return timeUnit(ns / 1000) } func initSysTick() { const ( traceEnable = 0x01000000 // enable debugging & monitoring blocks cycleCountEnable = 0x00000001 // cycle count register ) // disable SysTick if already running if arm.SYST.SYST_CSR.HasBits(arm.SYST_CSR_ENABLE_Msk) { arm.SYST.SYST_CSR.ClearBits(arm.SYST_CSR_ENABLE_Msk) } // zeroize the counter tickCount.Set(0) arm.SYST.SYST_RVR.Set(lastCycle) arm.SYST.SYST_CVR.Set(0) arm.SYST.SYST_CSR.Set(arm.SYST_CSR_TICKINT | arm.SYST_CSR_ENABLE) // set SysTick and PendSV priority to 32 nxp.SystemControl.SHPR3.Set((0x20 << nxp.SCB_SHPR3_PRI_15_Pos) | (0x20 << nxp.SCB_SHPR3_PRI_14_Pos)) // turn on cycle counter DEM_CR.SetBits(traceEnable) DWT_CR.SetBits(cycleCountEnable) cycleCount.Set(DWT_CYCCNT.Get()) // enable PIT, disable counters nxp.PIT.MCR.Set(0) for i := range nxp.PIT.TIMER { nxp.PIT.TIMER[i].TCTRL.Set(0) } // register sleep timer interrupt pitTimeout = interrupt.New(nxp.IRQ_PIT, timerWake) pitTimeout.SetPriority(0x21) pitTimeout.Enable() } func initRTC() { if !nxp.SNVS.LPCR.HasBits(nxp.SNVS_LPCR_SRTC_ENV) { // if SRTC isn't running, start it with default Jan 1, 2019 nxp.SNVS.LPSRTCLR.Set(uint32((0x5c2aad80 << 15) & 0xFFFFFFFF)) nxp.SNVS.LPSRTCMR.Set(uint32(0x5c2aad80 >> 17)) nxp.SNVS.LPCR.SetBits(nxp.SNVS_LPCR_SRTC_ENV) } } //go:export SysTick_Handler func tick() { tickCount.Set(tickCount.Get() + 1) cycleCount.Set(DWT_CYCCNT.Get()) } func ticks() timeUnit { mask := arm.DisableInterrupts() tick := tickCount.Get() cycs := cycleCount.Get() curr := DWT_CYCCNT.Get() arm.EnableInterrupts(mask) var diff uint32 if curr < cycs { // cycle counter overflow/rollover occurred diff = (0xFFFFFFFF - cycs) + curr } else { diff = curr - cycs } frac := uint64(diff*0xFFFFFFFF/cyclesPerMicro) >> 32 if frac > 1000 { frac = 1000 } return timeUnit(1000*tick + frac) } func sleepTicks(duration timeUnit) { curr := ticks() last := curr + duration // 64-bit overflow unlikely for curr < last { cycles := timeUnit((last - curr) / pitCyclesPerMicro) if cycles > 0xFFFFFFFF { cycles = 0xFFFFFFFF } if !timerSleep(uint32(cycles)) { return // return early due to interrupt } curr = ticks() } } func timerSleep(cycles uint32) bool { pitActive.Set(1) nxp.PIT.TIMER[pitSleepTimer].LDVAL.Set(cycles) nxp.PIT.TIMER[pitSleepTimer].TCTRL.Set(nxp.PIT_TIMER_TCTRL_TIE) // enable interrupts nxp.PIT.TIMER[pitSleepTimer].TCTRL.SetBits(nxp.PIT_TIMER_TCTRL_TEN) // start timer for { //arm.Asm("wfi") // TODO: causes hardfault! why? if pitActive.Get() == 0 { return true } if hasScheduler { break // some other interrupt occurred and needs servicing } } timerWake(interrupt.Interrupt{}) // clear and disable timer return false } func timerWake(interrupt.Interrupt) { pitActive.Set(0) // TFLGn[TIF] are set to 1 when a timeout occurs on the associated timer, and // are cleared to 0 by writing a 1 to the corresponding TFLGn[TIF]. nxp.PIT.TIMER[pitSleepTimer].TFLG.Set(nxp.PIT_TIMER_TFLG_TIF) // clear interrupt flag nxp.PIT.TIMER[pitSleepTimer].TCTRL.Set(0) // disable timer/interrupt enable flags } ================================================ FILE: src/runtime/runtime_nintendoswitch.S ================================================ // Macro for writing less code .macro FUNC name .section .text.\name, "ax", %progbits .global \name .type \name, %function .align 2 \name: .endm FUNC armGetSystemTick mrs x0, cntpct_el0 ret // Horizon System Calls // https://switchbrew.org/wiki/SVC FUNC svcSetHeapSize str x0, [sp, #-16]! svc 0x1 ldr x2, [sp], #16 str x1, [x2] ret FUNC svcExitProcess svc 0x7 ret FUNC svcSleepThread svc 0xB ret FUNC svcOutputDebugString svc 0x27 ret FUNC svcGetInfo str x0, [sp, #-16]! svc 0x29 ldr x2, [sp], #16 str x1, [x2] ret ================================================ FILE: src/runtime/runtime_nintendoswitch.go ================================================ //go:build nintendoswitch package runtime import "unsafe" const ( // Handles infoTypeTotalMemorySize = 6 // Total amount of memory available for process. infoTypeUsedMemorySize = 7 // Amount of memory currently used by process. currentProcessHandle = 0xFFFF8001 // Pseudo handle for the current process. // Types of config Entry envEntryTypeEndOfList = 0 // Entry list terminator. envEntryTypeMainThreadHandle = 1 // Provides the handle to the main thread. envEntryTypeOverrideHeap = 3 // Provides heap override information. // Default heap size allocated by libnx defaultHeapSize = 0x2000000 * 16 debugInit = false ) //go:extern _saved_return_address var savedReturnAddress uintptr //export __stack_top var stackTop uintptr //go:extern _context var context uintptr //go:extern _main_thread var mainThread uintptr var ( heapStart = uintptr(0) heapEnd = uintptr(0) usedRam = uint64(0) totalRam = uint64(0) totalHeap = uint64(0) ) func preinit() { // Unsafe to use heap here setupEnv() setupHeap() } // Entry point for Go. Initialize all packages and call main.main(). // //export main func main() { preinit() run() // Call exit to correctly finish the program // Without this, the application crashes at start, not sure why for { exit(0) } } // sleepTicks sleeps for the specified system ticks func sleepTicks(d timeUnit) { svcSleepThread(uint64(ticksToNanoseconds(d))) } // armTicksToNs converts cpu ticks to nanoseconds // Nintendo Switch CPU ticks has a fixed rate at 19200000 // It is basically 52 ns per tick // The formula 625 / 12 is equivalent to 1e9 / 19200000 func ticksToNanoseconds(tick timeUnit) int64 { return int64(tick * 625 / 12) } func nanosecondsToTicks(ns int64) timeUnit { return timeUnit(12 * ns / 625) } func ticks() timeUnit { return timeUnit(ticksToNanoseconds(timeUnit(getArmSystemTick()))) } // timeOffset is how long the monotonic clock started after the Unix epoch. It // should be a positive integer under normal operation or zero when it has not // been set. var timeOffset int64 //go:linkname now time.now func now() (sec int64, nsec int32, mono int64) { mono = nanotime() sec = (mono + timeOffset) / (1000 * 1000 * 1000) nsec = int32((mono + timeOffset) - sec*(1000*1000*1000)) return } var stdoutBuffer = make([]byte, 120) var position = 0 func putchar(c byte) { if c == '\n' || position >= len(stdoutBuffer) { svcOutputDebugString(&stdoutBuffer[0], uint64(position)) position = 0 return } stdoutBuffer[position] = c position++ } func buffered() int { return 0 } func getchar() byte { return 0 } func abort() { for { exit(1) } } //export write func libc_write(fd int32, buf *byte, count int) int { // TODO: Proper handling write for i := 0; i < count; i++ { putchar(*buf) buf = (*byte)(unsafe.Add(unsafe.Pointer(buf), 1)) } return count } // exit checks if a savedReturnAddress were provided by the launcher // if so, calls the nxExit which restores the stack and returns to launcher // otherwise just calls systemcall exit func exit(code int) { if savedReturnAddress == 0 { svcExitProcess(code) return } nxExit(code, stackTop, savedReturnAddress) } type configEntry struct { Key uint32 Flags uint32 Value [2]uint64 } func setupEnv() { if debugInit { println("Saved Return Address:", savedReturnAddress) println("Context:", context) println("Main Thread Handle:", mainThread) } // See https://switchbrew.org/w/index.php?title=Homebrew_ABI // Here we parse only the required configs for initializing if context != 0 { ptr := context entry := (*configEntry)(unsafe.Pointer(ptr)) for entry.Key != envEntryTypeEndOfList { switch entry.Key { case envEntryTypeOverrideHeap: if debugInit { println("Got heap override") } heapStart = uintptr(entry.Value[0]) heapEnd = heapStart + uintptr(entry.Value[1]) case envEntryTypeMainThreadHandle: mainThread = uintptr(entry.Value[0]) default: if entry.Flags&1 > 0 { // Mandatory but not parsed runtimePanic("mandatory config entry not parsed") } } ptr += unsafe.Sizeof(configEntry{}) entry = (*configEntry)(unsafe.Pointer(ptr)) } } // Fetch used / total RAM for allocating HEAP svcGetInfo(&totalRam, infoTypeTotalMemorySize, currentProcessHandle, 0) svcGetInfo(&usedRam, infoTypeUsedMemorySize, currentProcessHandle, 0) } func setupHeap() { if heapStart != 0 { if debugInit { print("Heap already overridden by hblauncher") } // Already overridden return } if debugInit { print("No heap override. Using normal initialization") } size := uint32(defaultHeapSize) if totalRam > usedRam+0x200000 { // Get maximum possible heap size = uint32(totalRam-usedRam-0x200000) & ^uint32(0x1FFFFF) } if size < defaultHeapSize { size = defaultHeapSize } if debugInit { println("Trying to allocate", size, "bytes of heap") } svcSetHeapSize(&heapStart, uint64(size)) if heapStart == 0 { runtimePanic("failed to allocate heap") } totalHeap = uint64(size) heapEnd = heapStart + uintptr(size) if debugInit { println("Heap Start", heapStart) println("Heap End ", heapEnd) println("Total Heap", totalHeap) } } // growHeap tries to grow the heap size. It returns true if it succeeds, false // otherwise. func growHeap() bool { // Growing the heap is unimplemented. return false } // getHeapBase returns the start address of the heap // this is externally linked by gonx func getHeapBase() uintptr { return heapStart } // getHeapEnd returns the end address of the heap // this is externally linked by gonx func getHeapEnd() uintptr { return heapEnd } //go:extern __data_start var dataStartSymbol [0]byte //go:extern __data_end var dataEndSymbol [0]byte //go:extern __bss_start var bssStartSymbol [0]byte //go:extern __bss_end var bssEndSymbol [0]byte // Find global variables. // The linker script provides __*_start and __*_end symbols that can be used to // scan the given sections. They are already aligned so don't need to be // manually aligned here. func findGlobals(found func(start, end uintptr)) { dataStart := uintptr(unsafe.Pointer(&dataStartSymbol)) dataEnd := uintptr(unsafe.Pointer(&dataEndSymbol)) found(dataStart, dataEnd) bssStart := uintptr(unsafe.Pointer(&bssStartSymbol)) bssEnd := uintptr(unsafe.Pointer(&bssEndSymbol)) found(bssStart, bssEnd) } // getContextPtr returns the hblauncher context // this is externally linked by gonx func getContextPtr() uintptr { return context } // getMainThreadHandle returns the main thread handler if any // this is externally linked by gonx func getMainThreadHandle() uintptr { return mainThread } //export armGetSystemTick func getArmSystemTick() int64 // nxExit exits the program to homebrew launcher // //export __nx_exit func nxExit(code int, stackTop uintptr, exitFunction uintptr) // Horizon System Calls // svcSetHeapSize Set the process heap to a given size. It can both extend and shrink the heap. // svc 0x01 // //export svcSetHeapSize func svcSetHeapSize(addr *uintptr, length uint64) uint64 // svcExitProcess Exits the current process. // svc 0x07 // //export svcExitProcess func svcExitProcess(code int) // svcSleepThread Sleeps the current thread for the specified amount of time. // svc 0x0B // //export svcSleepThread func svcSleepThread(nanos uint64) // svcOutputDebugString Outputs debug text, if used during debugging. // svc 0x27 // //export svcOutputDebugString func svcOutputDebugString(str *uint8, size uint64) uint64 // svcGetInfo Retrieves information about the system, or a certain kernel object. // svc 0x29 // //export svcGetInfo func svcGetInfo(output *uint64, id0 uint32, handle uint32, id1 uint64) uint64 func hardwareRand() (n uint64, ok bool) { // TODO: see whether there is a RNG and use it. return 0, false } func libc_errno_location() *int32 { // CGo is unavailable, so this function should be unreachable. runtimePanic("runtime: no cgo errno") return nil } ================================================ FILE: src/runtime/runtime_nrf.go ================================================ //go:build nrf package runtime import ( "device/arm" "device/nrf" "machine" "runtime/interrupt" "runtime/volatile" ) //go:linkname systemInit SystemInit func systemInit() //export Reset_Handler func main() { if nrf.FPUPresent { arm.SCB.CPACR.Set(0) // disable FPU if it is enabled } systemInit() preinit() run() exit(0) } func init() { machine.InitSerial() initLFCLK() initRTC() } func initLFCLK() { if machine.HasLowFrequencyCrystal { nrf.CLOCK.LFCLKSRC.Set(nrf.CLOCK_LFCLKSTAT_SRC_Xtal) } nrf.CLOCK.TASKS_LFCLKSTART.Set(1) for nrf.CLOCK.EVENTS_LFCLKSTARTED.Get() == 0 { } nrf.CLOCK.EVENTS_LFCLKSTARTED.Set(0) } func initRTC() { nrf.RTC1.TASKS_START.Set(1) intr := interrupt.New(nrf.IRQ_RTC1, func(intr interrupt.Interrupt) { if nrf.RTC1.EVENTS_COMPARE[0].Get() != 0 { nrf.RTC1.EVENTS_COMPARE[0].Set(0) nrf.RTC1.INTENCLR.Set(nrf.RTC_INTENSET_COMPARE0) nrf.RTC1.EVENTS_COMPARE[0].Set(0) rtc_wakeup.Set(1) } if nrf.RTC1.EVENTS_OVRFLW.Get() != 0 { nrf.RTC1.EVENTS_OVRFLW.Set(0) rtcOverflows.Set(rtcOverflows.Get() + 1) } }) nrf.RTC1.INTENSET.Set(nrf.RTC_INTENSET_OVRFLW) intr.SetPriority(0xc0) // low priority intr.Enable() } func putchar(c byte) { machine.Serial.WriteByte(c) } func getchar() byte { for machine.Serial.Buffered() == 0 { Gosched() } v, _ := machine.Serial.ReadByte() return v } func buffered() int { return machine.Serial.Buffered() } func sleepTicks(d timeUnit) { for d != 0 { ticks := uint32(d) & 0x7fffff // 23 bits (to be on the safe side) if d > 0x7fffff { ticks = 0x7fffff } rtc_sleep(ticks) d -= timeUnit(ticks) } } var rtcOverflows volatile.Register32 // number of times the RTC wrapped around // ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds. func ticksToNanoseconds(ticks timeUnit) int64 { // The following calculation is actually the following, but with both sides // reduced to reduce the risk of overflow: // ticks * 1e9 / 32768 return int64(ticks) * 1953125 / 64 } // nanosecondsToTicks converts nanoseconds to RTC ticks (running at 32768Hz). func nanosecondsToTicks(ns int64) timeUnit { // The following calculation is actually the following, but with both sides // reduced to reduce the risk of overflow: // ns * 32768 / 1e9 return timeUnit(ns * 64 / 1953125) } // Monotonically increasing number of ticks since start. func ticks() timeUnit { // For some ways of capturing the time atomically, see this thread: // https://www.eevblog.com/forum/microcontrollers/correct-timing-by-timer-overflow-count/msg749617/#msg749617 // Here, instead of re-reading the counter register if an overflow has been // detected, we simply try again because that results in (slightly) smaller // code and is perhaps easier to prove correct. for { mask := interrupt.Disable() counter := uint32(nrf.RTC1.COUNTER.Get()) overflows := rtcOverflows.Get() hasOverflow := nrf.RTC1.EVENTS_OVRFLW.Get() != 0 interrupt.Restore(mask) if hasOverflow { // There was an overflow. Try again. continue } // The counter is 24 bits in size, so the number of overflows form the // upper 32 bits (together 56 bits, which covers 71493 years at // 32768kHz: I'd argue good enough for most purposes). return timeUnit(overflows)<<24 + timeUnit(counter) } } var rtc_wakeup volatile.Register8 func rtc_sleep(ticks uint32) { nrf.RTC1.INTENSET.Set(nrf.RTC_INTENSET_COMPARE0) rtc_wakeup.Set(0) if ticks == 1 { // Race condition (even in hardware) at ticks == 1. // TODO: fix this in a better way by detecting it, like the manual // describes. ticks = 2 } nrf.RTC1.CC[0].Set((nrf.RTC1.COUNTER.Get() + ticks) & 0x00ffffff) for rtc_wakeup.Get() == 0 { waitForEvents() } } ================================================ FILE: src/runtime/runtime_nrf52840.go ================================================ //go:build nrf && nrf52840 package runtime // This package needs to be present so that the machine package can go:linkname // EnableUSBCDC from it. import _ "machine/usb/cdc" ================================================ FILE: src/runtime/runtime_nrf_bare.go ================================================ //go:build nrf && !softdevice package runtime import "device/arm" func waitForEvents() { arm.Asm("wfe") } ================================================ FILE: src/runtime/runtime_nrf_softdevice.go ================================================ //go:build nrf && softdevice package runtime import ( "device/arm" "device/nrf" ) //export sd_app_evt_wait func sd_app_evt_wait() // This is a global variable to avoid a heap allocation in waitForEvents. var softdeviceEnabled uint8 // Check whether the SoftDevice is currently enabled. // This function is also called from the machine package, so the signature has // to stay consistent. func isSoftDeviceEnabled() bool { // First check whether the SoftDevice is enabled. Unfortunately, // sd_app_evt_wait cannot be called when the SoftDevice is not enabled. arm.SVCall1(0x12, &softdeviceEnabled) // sd_softdevice_is_enabled return softdeviceEnabled != 0 } func waitForEvents() { // Call into the SoftDevice to sleep. This is necessary here because a // normal wfe will not put the chip in low power mode (it still consumes // 500µA-1mA). It is really needed to call sd_app_evt_wait for low power // consumption. if isSoftDeviceEnabled() { // Now pick the appropriate SVCall number. Hopefully they won't change // in the future with a different SoftDevice version. if nrf.Device == "nrf51" { // sd_app_evt_wait: SOC_SVC_BASE_NOT_AVAILABLE + 29 arm.SVCall0(0x2B + 29) } else if nrf.Device == "nrf52" || nrf.Device == "nrf52840" || nrf.Device == "nrf52833" { // sd_app_evt_wait: SOC_SVC_BASE_NOT_AVAILABLE + 21 arm.SVCall0(0x2C + 21) } else { sd_app_evt_wait() } } else { // SoftDevice is disabled so we can sleep normally. arm.Asm("wfe") } } ================================================ FILE: src/runtime/runtime_nxpmk66f18.go ================================================ // Derivative work of Teensyduino Core Library // http://www.pjrc.com/teensy/ // Copyright (c) 2017 PJRC.COM, LLC. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // 1. The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // 2. If the Software is incorporated into a build system that allows // selection among a list of target devices, then similar target // devices manufactured by PJRC.COM must be included in the list of // target devices and selectable in the same manner. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS // BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. //go:build nxp && mk66f18 package runtime import ( "device/arm" "device/nxp" "machine" ) const ( watchdogUnlockSequence1 = 0xC520 watchdogUnlockSequence2 = 0xD928 _DEFAULT_FTM_MOD = 61440 - 1 _DEFAULT_FTM_PRESCALE = 1 ) const ( _SIM_SOPT2_IRC48SEL = 3 << nxp.SIM_SOPT2_PLLFLLSEL_Pos _SMC_PMCTRL_HSRUN = 3 << nxp.SMC_PMCTRL_RUNM_Pos _SMC_PMSTAT_HSRUN = 0x80 << nxp.SMC_PMSTAT_PMSTAT_Pos ) //go:export Reset_Handler func main() { initSystem() arm.Asm("CPSIE i") initInternal() run() exit(0) } func initSystem() { // from: ResetHandler nxp.WDOG.UNLOCK.Set(watchdogUnlockSequence1) nxp.WDOG.UNLOCK.Set(watchdogUnlockSequence2) arm.Asm("nop") arm.Asm("nop") // TODO: hook for overriding? 'startupEarlyHook' nxp.WDOG.STCTRLH.Set(nxp.WDOG_STCTRLH_ALLOWUPDATE) // enable clocks to always-used peripherals nxp.SIM.SCGC3.Set(nxp.SIM_SCGC3_ADC1 | nxp.SIM_SCGC3_FTM2 | nxp.SIM_SCGC3_FTM3) nxp.SIM.SCGC5.Set(0x00043F82) // clocks active to all GPIO nxp.SIM.SCGC6.Set(nxp.SIM_SCGC6_RTC | nxp.SIM_SCGC6_FTM0 | nxp.SIM_SCGC6_FTM1 | nxp.SIM_SCGC6_ADC0 | nxp.SIM_SCGC6_FTF) nxp.LMEM.PCCCR.Set(0x85000003) // release I/O pins hold, if we woke up from VLLS mode if nxp.PMC.REGSC.HasBits(nxp.PMC_REGSC_ACKISO) { nxp.PMC.REGSC.SetBits(nxp.PMC_REGSC_ACKISO) } // since this is a write once register, make it visible to all F_CPU's // so we can into other sleep modes in the future at any speed nxp.SMC.PMPROT.Set(nxp.SMC_PMPROT_AHSRUN | nxp.SMC_PMPROT_AVLP | nxp.SMC_PMPROT_ALLS | nxp.SMC_PMPROT_AVLLS) preinit() // copy the vector table to RAM default all interrupts to medium priority level // for (i=0; i < NVIC_NUM_INTERRUPTS + 16; i++) _VectorsRam[i] = _VectorsFlash[i]; for i := uint32(0); i <= nxp.IRQ_max; i++ { arm.SetPriority(i, 128) } // SCB_VTOR = (uint32_t)_VectorsRam; // use vector table in RAM // hardware always starts in FEI mode // C1[CLKS] bits are written to 00 // C1[IREFS] bit is written to 1 // C6[PLLS] bit is written to 0 // MCG_SC[FCDIV] defaults to divide by two for internal ref clock // I tried changing MSG_SC to divide by 1, it didn't work for me // enable capacitors for crystal nxp.OSC.CR.Set(nxp.OSC_CR_SC8P | nxp.OSC_CR_SC2P | nxp.OSC_CR_ERCLKEN) // enable osc, 8-32 MHz range, low power mode nxp.MCG.C2.Set(uint8((2 << nxp.MCG_C2_RANGE_Pos) | nxp.MCG_C2_EREFS)) // switch to crystal as clock source, FLL input = 16 MHz / 512 nxp.MCG.C1.Set(uint8((2 << nxp.MCG_C1_CLKS_Pos) | (4 << nxp.MCG_C1_FRDIV_Pos))) // wait for crystal oscillator to begin for !nxp.MCG.S.HasBits(nxp.MCG_S_OSCINIT0) { } // wait for FLL to use oscillator for nxp.MCG.S.HasBits(nxp.MCG_S_IREFST) { } // wait for MCGOUT to use oscillator for (nxp.MCG.S.Get() & nxp.MCG_S_CLKST_Msk) != (2 << nxp.MCG_S_CLKST_Pos) { } // now in FBE mode // C1[CLKS] bits are written to 10 // C1[IREFS] bit is written to 0 // C1[FRDIV] must be written to divide xtal to 31.25-39 kHz // C6[PLLS] bit is written to 0 // C2[LP] is written to 0 // we need faster than the crystal, turn on the PLL (F_CPU > 120000000) nxp.SMC.PMCTRL.Set(_SMC_PMCTRL_HSRUN) // enter HSRUN mode for nxp.SMC.PMSTAT.Get() != _SMC_PMSTAT_HSRUN { } // wait for HSRUN nxp.MCG.C5.Set((1 << nxp.MCG_C5_PRDIV_Pos)) nxp.MCG.C6.Set(nxp.MCG_C6_PLLS | (29 << nxp.MCG_C6_VDIV_Pos)) // wait for PLL to start using xtal as its input for !nxp.MCG.S.HasBits(nxp.MCG_S_PLLST) { } // wait for PLL to lock for !nxp.MCG.S.HasBits(nxp.MCG_S_LOCK0) { } // now we're in PBE mode // now program the clock dividers // config divisors: 180 MHz core, 60 MHz bus, 25.7 MHz flash, USB = IRC48M nxp.SIM.CLKDIV1.Set((0 << nxp.SIM_CLKDIV1_OUTDIV1_Pos) | (2 << nxp.SIM_CLKDIV1_OUTDIV2_Pos) | (0 << nxp.SIM_CLKDIV1_OUTDIV3_Pos) | (6 << nxp.SIM_CLKDIV1_OUTDIV4_Pos)) nxp.SIM.CLKDIV2.Set((0 << nxp.SIM_CLKDIV2_USBDIV_Pos)) // switch to PLL as clock source, FLL input = 16 MHz / 512 nxp.MCG.C1.Set((0 << nxp.MCG_C1_CLKS_Pos) | (4 << nxp.MCG_C1_FRDIV_Pos)) // wait for PLL clock to be used for (nxp.MCG.S.Get() & nxp.MCG_S_CLKST_Msk) != (3 << nxp.MCG_S_CLKST_Pos) { } // now we're in PEE mode // trace is CPU clock, CLKOUT=OSCERCLK0 // USB uses IRC48 nxp.SIM.SOPT2.Set(nxp.SIM_SOPT2_USBSRC | _SIM_SOPT2_IRC48SEL | nxp.SIM_SOPT2_TRACECLKSEL | (6 << nxp.SIM_SOPT2_CLKOUTSEL_Pos)) // If the RTC oscillator isn't enabled, get it started. For Teensy 3.6 // we don't do this early. See comment above about slow rising power. if !nxp.RTC.CR.HasBits(nxp.RTC_CR_OSCE) { nxp.RTC.SR.Set(0) nxp.RTC.CR.Set(nxp.RTC_CR_SC16P | nxp.RTC_CR_SC4P | nxp.RTC_CR_OSCE) } // initialize the SysTick counter initSysTick() } func initInternal() { // from: _init_Teensyduino_internal_ // arm.EnableIRQ(nxp.IRQ_PORTA) // arm.EnableIRQ(nxp.IRQ_PORTB) // arm.EnableIRQ(nxp.IRQ_PORTC) // arm.EnableIRQ(nxp.IRQ_PORTD) // arm.EnableIRQ(nxp.IRQ_PORTE) nxp.FTM0.CNT.Set(0) nxp.FTM0.MOD.Set(_DEFAULT_FTM_MOD) nxp.FTM0.C0SC.Set(0x28) // MSnB:MSnA = 10, ELSnB:ELSnA = 10 nxp.FTM0.C1SC.Set(0x28) nxp.FTM0.C2SC.Set(0x28) nxp.FTM0.C3SC.Set(0x28) nxp.FTM0.C4SC.Set(0x28) nxp.FTM0.C5SC.Set(0x28) nxp.FTM0.C6SC.Set(0x28) nxp.FTM0.C7SC.Set(0x28) nxp.FTM3.C0SC.Set(0x28) nxp.FTM3.C1SC.Set(0x28) nxp.FTM3.C2SC.Set(0x28) nxp.FTM3.C3SC.Set(0x28) nxp.FTM3.C4SC.Set(0x28) nxp.FTM3.C5SC.Set(0x28) nxp.FTM3.C6SC.Set(0x28) nxp.FTM3.C7SC.Set(0x28) nxp.FTM0.SC.Set((1 << nxp.FTM_SC_CLKS_Pos) | (_DEFAULT_FTM_PRESCALE << nxp.FTM_SC_PS_Pos)) nxp.FTM1.CNT.Set(0) nxp.FTM1.MOD.Set(_DEFAULT_FTM_MOD) nxp.FTM1.C0SC.Set(0x28) nxp.FTM1.C1SC.Set(0x28) nxp.FTM1.SC.Set((1 << nxp.FTM_SC_CLKS_Pos) | (_DEFAULT_FTM_PRESCALE << nxp.FTM_SC_PS_Pos)) // causes a data bus error for unknown reasons // nxp.FTM2.CNT.Set(0) // nxp.FTM2.MOD.Set(_DEFAULT_FTM_MOD) // nxp.FTM2.C0SC.Set(0x28) // nxp.FTM2.C1SC.Set(0x28) // nxp.FTM2.SC.Set((1 << nxp.FTM_SC_CLKS_Pos) | (_DEFAULT_FTM_PRESCALE << nxp.FTM_SC_PS_Pos)) nxp.FTM3.CNT.Set(0) nxp.FTM3.MOD.Set(_DEFAULT_FTM_MOD) nxp.FTM3.C0SC.Set(0x28) nxp.FTM3.C1SC.Set(0x28) nxp.FTM3.SC.Set((1 << nxp.FTM_SC_CLKS_Pos) | (_DEFAULT_FTM_PRESCALE << nxp.FTM_SC_PS_Pos)) nxp.SIM.SCGC2.SetBits(nxp.SIM_SCGC2_TPM1) nxp.SIM.SOPT2.SetBits((2 << nxp.SIM_SOPT2_TPMSRC_Pos)) nxp.TPM1.CNT.Set(0) nxp.TPM1.MOD.Set(32767) nxp.TPM1.C0SC.Set(0x28) nxp.TPM1.C1SC.Set(0x28) nxp.TPM1.SC.Set((1 << nxp.FTM_SC_CLKS_Pos) | (0 << nxp.FTM_SC_PS_Pos)) // configure the sleep timer initSleepTimer() // analog_init(); } func putchar(c byte) { machine.PutcharUART(machine.UART0, c) } func getchar() byte { // dummy, TODO return 0 } func buffered() int { // dummy, TODO return 0 } func exit(code int) { abort() } func abort() { println("!!! ABORT !!!") m := arm.DisableInterrupts() arm.Asm("mov r12, #1") arm.Asm("msr basepri, r12") // only execute interrupts of priority 0 nxp.SystemControl.SHPR3.ClearBits(nxp.SystemControl_SHPR3_PRI_15_Msk) // set systick to priority 0 arm.EnableInterrupts(m) machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput}) var v bool for { machine.LED.Set(v) v = !v t := millisSinceBoot() for millisSinceBoot()-t < 60 { arm.Asm("wfi") } // keep polling some communication while in fault // mode, so we don't completely die. // machine.PollUSB(&machine.USB0) machine.PollUART(machine.UART0) machine.PollUART(machine.UART1) machine.PollUART(machine.UART2) } } func waitForEvents() { arm.Asm("wfe") } ================================================ FILE: src/runtime/runtime_rp2.go ================================================ //go:build rp2040 || rp2350 package runtime import ( "device/arm" "device/rp" "internal/task" "machine" _ "machine/usb/cdc" "runtime/interrupt" "runtime/volatile" "unsafe" ) const numCPU = 2 const numSpinlocks = 32 // machineTicks is provided by package machine. func machineTicks() uint64 // machineLightSleep is provided by package machine. func machineLightSleep(uint64) // ticks returns the number of ticks (microseconds) elapsed since power up. func ticks() timeUnit { t := machineTicks() return timeUnit(t) } func ticksToNanoseconds(ticks timeUnit) int64 { return int64(ticks) * 1000 } func nanosecondsToTicks(ns int64) timeUnit { return timeUnit(ns / 1000) } func sleepTicks(d timeUnit) { if hasScheduler { // With scheduler, sleepTicks may return early if an interrupt or // event fires - so scheduler can schedule any go routines now // eligible to run machineLightSleep(uint64(d)) return } // Busy loop sleepUntil := ticks() + d for ticks() < sleepUntil { } } // Currently sleeping core, or 0xff. // Must only be accessed with the scheduler lock held. var sleepingCore uint8 = 0xff // Return whether another core is sleeping. // May only be called with the scheduler lock held. func hasSleepingCore() bool { return sleepingCore != 0xff } // Almost identical to sleepTicks, except that it will unlock/lock the scheduler // while sleeping and is interruptible by interruptSleepTicksMulticore. // This may only be called with the scheduler lock held. func sleepTicksMulticore(d timeUnit) { sleepingCore = uint8(currentCPU()) // Note: interruptSleepTicksMulticore will be able to interrupt this, since // it executes the "sev" instruction which would make sleepTicks return // immediately without sleeping. Even if it happens while configuring the // sleep operation. schedulerLock.Unlock() sleepTicks(d) schedulerLock.Lock() sleepingCore = 0xff } // Interrupt an ongoing call to sleepTicksMulticore on another core. func interruptSleepTicksMulticore(wakeup timeUnit) { arm.Asm("sev") } // Number of cores that are currently in schedulerUnlockAndWait. // It is possible for both cores to be sleeping, if the program is waiting for // an interrupt (or is deadlocked). var waitingCore uint8 // Put the scheduler to sleep, since there are no tasks to run. // This will unlock the scheduler lock, and must be called with the scheduler // lock held. func schedulerUnlockAndWait() { waitingCore++ schedulerLock.Unlock() arm.Asm("wfe") schedulerLock.Lock() waitingCore-- } // Wake another core, if one is sleeping. Must be called with the scheduler lock // held. func schedulerWake() { if waitingCore != 0 { arm.Asm("sev") } } // Return the current core number: 0 or 1. func currentCPU() uint32 { return rp.SIO.CPUID.Get() } // Start the secondary cores for this chip. // On the RP2040/RP2350, there is only one other core to start. func startSecondaryCores() { // Start the second core of the RP2040/RP2350. // See sections 2.8.2 and 5.3 in the datasheets for RP2040 and RP2350 respectively. seq := 0 for { cmd := core1StartSequence[seq] if cmd == 0 { multicore_fifo_drain() arm.Asm("sev") } multicore_fifo_push_blocking(cmd) response := multicore_fifo_pop_blocking() if cmd != response { seq = 0 continue } seq = seq + 1 if seq >= len(core1StartSequence) { break } } // Enable the FIFO interrupt for the GC stop the world phase. // We can only do this after we don't need the FIFO anymore for starting the // second core. intr := interrupt.New(sioIrqFifoProc0, func(intr interrupt.Interrupt) { switch rp.SIO.FIFO_RD.Get() { case 1: gcInterruptHandler(0) } }) intr.Enable() intr.SetPriority(0xff) } var core1StartSequence = [...]uint32{ 0, 0, 1, uint32(uintptr(unsafe.Pointer(&__isr_vector))), uint32(uintptr(unsafe.Pointer(&stack1TopSymbol))), uint32(exportedFuncPtr(runCore1)), } //go:extern __isr_vector var __isr_vector [0]uint32 //go:extern _stack1_top var stack1TopSymbol [0]uint32 // The function that is started on the second core. // //export tinygo_runCore1 func runCore1() { // Clear sticky bit that seems to have been set while starting this core. rp.SIO.FIFO_ST.Set(rp.SIO_FIFO_ST_ROE) // Enable the FIFO interrupt, mainly used for the stop-the-world phase of // the GC. // Use the lowest possible priority (highest priority value), so that other // interrupts can still happen while the GC is running. intr := interrupt.New(sioIrqFifoProc1, func(intr interrupt.Interrupt) { switch rp.SIO.FIFO_RD.Get() { case 1: gcInterruptHandler(1) } }) intr.Enable() intr.SetPriority(0xff) // Now start running the scheduler on this core. schedulerLock.Lock() scheduler(false) schedulerLock.Unlock() // The main function returned. exit(0) } // The below multicore_fifo_* functions have been translated from the Raspberry // Pi Pico SDK. func multicore_fifo_rvalid() bool { return rp.SIO.FIFO_ST.Get()&rp.SIO_FIFO_ST_VLD != 0 } func multicore_fifo_wready() bool { return rp.SIO.FIFO_ST.Get()&rp.SIO_FIFO_ST_RDY != 0 } func multicore_fifo_drain() { for multicore_fifo_rvalid() { rp.SIO.FIFO_RD.Get() } } func multicore_fifo_push_blocking(data uint32) { for !multicore_fifo_wready() { } rp.SIO.FIFO_WR.Set(data) arm.Asm("sev") } func multicore_fifo_pop_blocking() uint32 { for !multicore_fifo_rvalid() { arm.Asm("wfe") } return rp.SIO.FIFO_RD.Get() } // Value used to communicate between the GC core and the other (paused) cores. var gcSignalWait volatile.Register8 // The GC interrupted this core for the stop-the-world phase. // This function handles that, and only returns after the stop-the-world phase // ended. func gcInterruptHandler(hartID uint32) { // Let the GC know we're ready. gcScanState.Add(1) arm.Asm("sev") // Wait until we get a signal to start scanning. for gcSignalWait.Get() == 0 { arm.Asm("wfe") } gcSignalWait.Set(0) // Scan the stack(s) of this core. scanCurrentStack() if !task.OnSystemStack() { // Mark system stack. markRoots(task.SystemStack(), coreStackTop(hartID)) } // Signal we've finished scanning. gcScanState.Store(1) arm.Asm("sev") // Wait until we get a signal that the stop-the-world phase has ended. for gcSignalWait.Get() == 0 { arm.Asm("wfe") } gcSignalWait.Set(0) // Signal we received the signal and are going to exit the interrupt. gcScanState.Add(1) arm.Asm("sev") } // Pause the given core by sending it an interrupt. func gcPauseCore(core uint32) { rp.SIO.FIFO_WR.Set(1) } // Signal the given core that it can resume one step. // This is called twice after gcPauseCore: the first time to scan the stack of // the core, and the second time to end the stop-the-world phase. func gcSignalCore(core uint32) { gcSignalWait.Set(1) arm.Asm("sev") } // Returns the stack top (highest address) of the system stack of the given // core. func coreStackTop(core uint32) uintptr { switch core { case 0: return uintptr(unsafe.Pointer(&stackTopSymbol)) case 1: return uintptr(unsafe.Pointer(&stack1TopSymbol)) default: runtimePanic("unexpected core") return 0 } } // These spinlocks are needed by the runtime. var ( printLock = spinLock{id: 20} schedulerLock = spinLock{id: 21} atomicsLock = spinLock{id: 22} futexLock = spinLock{id: 23} ) func resetSpinLocks() { for i := uint8(0); i < numSpinlocks; i++ { l := &spinLock{id: i} l.spinlock().Set(0) } } // A hardware spinlock, one of the 32 spinlocks defined in the SIO peripheral. type spinLock struct { id uint8 } // Return the spinlock register: rp.SIO.SPINLOCKx func (l *spinLock) spinlock() *volatile.Register32 { return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&rp.SIO.SPINLOCK0), l.id*4)) } func (l *spinLock) Lock() { // Wait for the lock to be available. spinlock := l.spinlock() for spinlock.Get() == 0 { arm.Asm("wfe") } } func (l *spinLock) Unlock() { l.spinlock().Set(0) arm.Asm("sev") } // Wait until a signal is received, indicating that it can resume from the // spinloop. func spinLoopWait() { arm.Asm("wfe") } func waitForEvents() { arm.Asm("wfe") } func putchar(c byte) { machine.Serial.WriteByte(c) } func getchar() byte { for machine.Serial.Buffered() == 0 { Gosched() } v, _ := machine.Serial.ReadByte() return v } func buffered() int { return machine.Serial.Buffered() } // machineInit is provided by package machine. func machineInit() func init() { machineInit() machine.InitSerial() } func prerun() { // Reset spinlocks before the full machineInit() so the scheduler doesn't // hang waiting for schedulerLock after a soft reset. resetSpinLocks() } //export Reset_Handler func main() { preinit() prerun() run() exit(0) } ================================================ FILE: src/runtime/runtime_rp2040.go ================================================ //go:build rp2040 package runtime import ( "device/rp" ) const ( sioIrqFifoProc0 = rp.IRQ_SIO_IRQ_PROC0 sioIrqFifoProc1 = rp.IRQ_SIO_IRQ_PROC1 ) ================================================ FILE: src/runtime/runtime_rp2350.go ================================================ //go:build rp2350 package runtime import ( "device/rp" ) const ( // On RP2040 each core has a different IRQ number: SIO_IRQ_PROC0 and SIO_IRQ_PROC1. // On RP2350 both cores share the same irq number (SIO_IRQ_PROC) just with a // different SIO interrupt output routed to that IRQ input on each core. // https://www.raspberrypi.com/documentation/pico-sdk/high_level.html#group_pico_multicore_1ga1413ebfa65114c6f408f4675897ac5ee sioIrqFifoProc0 = rp.IRQ_SIO_IRQ_FIFO sioIrqFifoProc1 = rp.IRQ_SIO_IRQ_FIFO ) ================================================ FILE: src/runtime/runtime_stm32.go ================================================ //go:build stm32 package runtime import "device/arm" //export Reset_Handler func main() { preinit() run() exit(0) } func waitForEvents() { arm.Asm("wfe") } ================================================ FILE: src/runtime/runtime_stm32_timers.go ================================================ //go:build stm32 package runtime // This file implements a common implementation of implementing 'ticks' and // 'sleep' for STM32 devices. The implementation uses a single timer. The // timer's PWM frequency (controlled by PSC and ARR) are configured for // periodic interrupts at 100Hz (TICK_INTR_PERIOD_NS). The PWM counter // register is used for fine-grained resolution (down to ~150ns) with an // Output Comparator used for fine-grained sleeps. import ( "device/stm32" "machine" "runtime/interrupt" "runtime/volatile" ) type timerInfo struct { EnableRegister *volatile.Register32 EnableFlag uint32 Device *stm32.TIM_Type } const ( // All STM32 do a constant 16ns per tick. This keeps time // conversion between ticks and ns fast (shift operation) // at the expense of more complex logic when getting current // time (which is already slow due to interfacing with hardware) NS_PER_TICK = 16 // For very short sleeps a busy loop is used to avoid race // conditions where a trigger would take longer to setup than // the sleep duration. MAX_BUSY_LOOP_NS = 10e3 // 10us // The period of tick interrupts in nanoseconds TICK_INTR_PERIOD_NS = 10e6 // 10ms = 100Hz // The number of ticks that happen per overflow interrupt TICK_PER_INTR = TICK_INTR_PERIOD_NS / NS_PER_TICK ) var ( // Tick count since boot tickCount volatile.Register64 // The timer used for counting ticks tickTimer *machine.TIM // The max counter value (fractional part) countMax uint32 ) func ticksToNanoseconds(ticks timeUnit) int64 { return int64(ticks) * NS_PER_TICK } func nanosecondsToTicks(ns int64) timeUnit { return timeUnit(ns / NS_PER_TICK) } // number of ticks since start. // //go:linkname ticks runtime.ticks func ticks() timeUnit { // For some ways of capturing the time atomically, see this thread: // https://www.eevblog.com/forum/microcontrollers/correct-timing-by-timer-overflow-count/msg749617/#msg749617 // Here, instead of re-reading the counter register if an overflow has been // detected, we simply try again because that results in smaller code. for { mask := interrupt.Disable() counter := tickTimer.Count() overflows := uint64(tickCount.Get()) hasOverflow := tickTimer.Device.SR.HasBits(stm32.TIM_SR_UIF) interrupt.Restore(mask) if hasOverflow { continue } return timeUnit(overflows*TICK_PER_INTR + countToTicks(counter)) } } // // -- Ticks --- // // Enable the timer used to count ticks. // // For precise sleeps use a timer with at least one OutputCompare // channel otherwise minimum reliable sleep resolution is bounded // by TICK_INTR_PERIOD_NS. // // Typically avoid TIM6 / TIM7 as they often do not include an // output comparator. func initTickTimer(tim *machine.TIM) { tickTimer = tim tickTimer.Configure(machine.PWMConfig{Period: TICK_INTR_PERIOD_NS}) countMax = tickTimer.Top() tickTimer.SetWraparoundInterrupt(handleTick) } func handleTick() { // increment tick count tickCount.Set(tickCount.Get() + 1) } // // --- Sleep --- // func sleepTicks(d timeUnit) { // If there is a scheduler, we sleep until any kind of CPU event up to // a maximum of the requested sleep duration. // // The scheduler will call again if there is nothing to do and a further // sleep is required. if hasScheduler { timerSleep(uint64(d)) return } // There's no scheduler, so we sleep until at least the requested number // of ticks has passed. For short sleeps, this forms a busy loop since // timerSleep will return immediately. end := ticks() + d for ticks() < end { timerSleep(uint64(d)) } } // timerSleep sleeps for 'at most' ticks, but possibly less. func timerSleep(ticks uint64) { // If the sleep is super-small (<10us), busy loop by returning // to the scheduler (if any). This avoids a busy loop here // that might delay tasks from being scheduled triggered by // an interrupt (e.g. channels). if ticksToNanoseconds(timeUnit(ticks)) < MAX_BUSY_LOOP_NS { return } // If the sleep is long, the tick interrupt will occur before // the sleep expires, so just use that. This routine will be // called again if the sleep is incomplete. if ticks >= TICK_PER_INTR { waitForEvents() return } // Sleeping for less than one tick interrupt, now see if the // next tick interrupt will occur before the sleep expires. If // so, use that interrupt. (this routine will be called // again if sleep is incomplete) cnt := tickTimer.Count() ticksUntilOverflow := countToTicks(countMax - cnt) if ticksUntilOverflow <= ticks { waitForEvents() return } // The sleep is now known to be: // - More than a few CPU cycles // - Less than one interrupt period // - Expiring before the next interrupt // // Setup a PWM channel to trigger an interrupt. // NOTE: ticks is known to be < MAX_UINT32 at this point. tickTimer.SetMatchInterrupt(0, handleSleep) tickTimer.Set(0, cnt+ticksToCount(ticks)) // Wait till either the timer or some other event wakes // up the CPU waitForEvents() // In case it was not the sleep interrupt that woke the // CPU, disable the sleep interrupt now. tickTimer.Unset(0) } func handleSleep(ch uint8) { // Disable the sleep interrupt tickTimer.Unset(0) } func countToTicks(count uint32) uint64 { return (uint64(count) * TICK_PER_INTR) / uint64(countMax) } func ticksToCount(ticks uint64) uint32 { return uint32((ticks * uint64(countMax)) / TICK_PER_INTR) } ================================================ FILE: src/runtime/runtime_stm32f103.go ================================================ //go:build stm32 && stm32f103 package runtime import ( "device/stm32" "machine" ) func init() { initCLK() machine.InitSerial() initTickTimer(&machine.TIM4) } func putchar(c byte) { machine.Serial.WriteByte(c) } func getchar() byte { for machine.Serial.Buffered() == 0 { Gosched() } v, _ := machine.Serial.ReadByte() return v } func buffered() int { return machine.Serial.Buffered() } // initCLK sets clock to 72MHz using HSE 8MHz crystal w/ PLL X 9 (8MHz x 9 = 72MHz). func initCLK() { stm32.FLASH.ACR.SetBits(stm32.FLASH_ACR_LATENCY_WS2) // Two wait states, per datasheet stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PPRE1_Div2 << stm32.RCC_CFGR_PPRE1_Pos) // prescale PCLK1 = HCLK/2 stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PPRE2_Div1 << stm32.RCC_CFGR_PPRE2_Pos) // prescale PCLK2 = HCLK/1 stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_ADCPRE_Div6 << stm32.RCC_CFGR_ADCPRE_Pos) // prescale ADCCLK = PCLK2/6 stm32.RCC.CR.SetBits(stm32.RCC_CR_HSEON) // enable HSE clock // wait for the HSEREADY flag for !stm32.RCC.CR.HasBits(stm32.RCC_CR_HSERDY) { } stm32.RCC.CR.SetBits(stm32.RCC_CR_HSION) // enable HSI clock // wait for the HSIREADY flag for !stm32.RCC.CR.HasBits(stm32.RCC_CR_HSIRDY) { } stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PLLSRC) // set PLL source to HSE stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PLLMUL_Mul9 << stm32.RCC_CFGR_PLLMUL_Pos) // multiply by 9 stm32.RCC.CR.SetBits(stm32.RCC_CR_PLLON) // enable the PLL // wait for the PLLRDY flag for !stm32.RCC.CR.HasBits(stm32.RCC_CR_PLLRDY) { } stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_SW_PLL) // set clock source to pll // wait for PLL to be CLK for !stm32.RCC.CFGR.HasBits(stm32.RCC_CFGR_SWS_PLL << stm32.RCC_CFGR_SWS_Pos) { } } ================================================ FILE: src/runtime/runtime_stm32f4.go ================================================ //go:build stm32f4 && (stm32f407 || stm32f469) package runtime import ( "device/stm32" "machine" ) func init() { initCLK() machine.InitSerial() initTickTimer(&machine.TIM2) } func putchar(c byte) { machine.Serial.WriteByte(c) } func getchar() byte { for machine.Serial.Buffered() == 0 { Gosched() } v, _ := machine.Serial.ReadByte() return v } func buffered() int { return machine.Serial.Buffered() } func initCLK() { // Reset clock registers // Set HSION stm32.RCC.CR.SetBits(stm32.RCC_CR_HSION) for !stm32.RCC.CR.HasBits(stm32.RCC_CR_HSIRDY) { } // Reset CFGR stm32.RCC.CFGR.Set(0x00000000) // Reset HSEON, CSSON and PLLON stm32.RCC.CR.ClearBits(stm32.RCC_CR_HSEON | stm32.RCC_CR_CSSON | stm32.RCC_CR_PLLON) // Reset PLLCFGR stm32.RCC.PLLCFGR.Set(0x24003010) // Reset HSEBYP stm32.RCC.CR.ClearBits(stm32.RCC_CR_HSEBYP) // Disable all interrupts stm32.RCC.CIR.Set(0x00000000) // Set up the clock var startupCounter uint32 = 0 // Enable HSE stm32.RCC.CR.Set(stm32.RCC_CR_HSEON) // Wait till HSE is ready and if timeout is reached exit for { startupCounter++ if stm32.RCC.CR.HasBits(stm32.RCC_CR_HSERDY) || (startupCounter == HSE_STARTUP_TIMEOUT) { break } } if stm32.RCC.CR.HasBits(stm32.RCC_CR_HSERDY) { // Enable high performance mode, configure maximum system frequency. stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_PWREN) stm32.PWR.CR.SetBits(0x4000) // PWR_CR_VOS // HCLK = SYSCLK / 1 stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_HPRE_Div1 << stm32.RCC_CFGR_HPRE_Pos) // PCLK2 = HCLK / 2 stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PPRE2_Div2 << stm32.RCC_CFGR_PPRE2_Pos) // PCLK1 = HCLK / 4 stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PPRE1_Div4 << stm32.RCC_CFGR_PPRE1_Pos) // Configure the main PLL stm32.RCC.PLLCFGR.Set(PLL_CFGR) // Enable main PLL stm32.RCC.CR.SetBits(stm32.RCC_CR_PLLON) // Wait till the main PLL is ready for (stm32.RCC.CR.Get() & stm32.RCC_CR_PLLRDY) == 0 { } // Configure Flash prefetch, Instruction cache, Data cache and wait state stm32.FLASH.ACR.Set(stm32.FLASH_ACR_ICEN | stm32.FLASH_ACR_DCEN | (5 << stm32.FLASH_ACR_LATENCY_Pos)) // Select the main PLL as system clock source stm32.RCC.CFGR.ClearBits(stm32.RCC_CFGR_SW_Msk) stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_SW_PLL << stm32.RCC_CFGR_SW_Pos) for (stm32.RCC.CFGR.Get() & stm32.RCC_CFGR_SWS_Msk) != (stm32.RCC_CFGR_SWS_PLL << stm32.RCC_CFGR_SWS_Pos) { } } else { // If HSE failed to start up, the application will have wrong clock configuration for { } } // Enable the CCM RAM clock stm32.RCC.AHB1ENR.SetBits(1 << 20) } ================================================ FILE: src/runtime/runtime_stm32f405.go ================================================ //go:build stm32f405 package runtime import ( "device/stm32" "machine" ) const ( // +----------------------+ // | Clock Settings | // +-------------+--------+ // | HSE | 12mhz | // | SYSCLK | 168mhz | // | HCLK | 168mhz | // | APB1(PCLK1) | 42mhz | // | APB2(PCLK2) | 84mhz | // +-------------+--------+ HCLK_FREQ_HZ = 168000000 PCLK1_FREQ_HZ = HCLK_FREQ_HZ / 4 PCLK2_FREQ_HZ = HCLK_FREQ_HZ / 2 ) const ( PWR_SCALE1 = 1 << stm32.PWR_CSR_VOSRDY_Pos // max value of HCLK = 168 MHz PWR_SCALE2 = 0 // max value of HCLK = 144 MHz PLL_SRC_HSE = 1 << stm32.RCC_PLLCFGR_PLLSRC_Pos // use HSE for PLL and PLLI2S PLL_SRC_HSI = 0 // use HSI for PLL and PLLI2S PLL_DIV_M = 6 << stm32.RCC_PLLCFGR_PLLM_Pos PLL_MLT_N = 168 << stm32.RCC_PLLCFGR_PLLN_Pos PLL_DIV_P = ((2 >> 1) - 1) << stm32.RCC_PLLCFGR_PLLP_Pos PLL_DIV_Q = 7 << stm32.RCC_PLLCFGR_PLLQ_Pos SYSCLK_SRC_PLL = stm32.RCC_CFGR_SW_PLL << stm32.RCC_CFGR_SW_Pos SYSCLK_STAT_PLL = stm32.RCC_CFGR_SWS_PLL << stm32.RCC_CFGR_SWS_Pos RCC_DIV_PCLK1 = stm32.RCC_CFGR_PPRE1_Div4 << stm32.RCC_CFGR_PPRE1_Pos // HCLK / 4 RCC_DIV_PCLK2 = stm32.RCC_CFGR_PPRE2_Div2 << stm32.RCC_CFGR_PPRE2_Pos // HCLK / 2 RCC_DIV_HCLK = stm32.RCC_CFGR_HPRE_Div1 << stm32.RCC_CFGR_HPRE_Pos // SYSCLK / 1 CLK_CCM_RAM = 1 << 20 ) const ( // +-----------------------------------+ // | Voltage range = 2.7V - 3.6V | // +----------------+------------------+ // | Wait states | System Bus | // | (WS, LATENCY) | HCLK (MHz) | // +----------------+------------------+ // | 0 WS, 1 cycle | 0 < HCLK ≤ 30 | // | 1 WS, 2 cycles | 30 < HCLK ≤ 60 | // | 2 WS, 3 cycles | 60 < HCLK ≤ 90 | // | 3 WS, 4 cycles | 90 < HCLK ≤ 120 | // | 4 WS, 5 cycles | 120 < HCLK ≤ 150 | // | 5 WS, 6 cycles | 150 < HCLK ≤ 168 | // +----------------+------------------+ FLASH_LATENCY = 5 << stm32.FLASH_ACR_LATENCY_Pos // 5 WS (6 CPU cycles) // instruction cache, data cache, and prefetch FLASH_OPTIONS = stm32.FLASH_ACR_ICEN | stm32.FLASH_ACR_DCEN | stm32.FLASH_ACR_PRFTEN ) func init() { initOSC() // configure oscillators initCLK() initCOM() initTickTimer(&machine.TIM3) } func initOSC() { // enable voltage regulator stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_PWREN) stm32.PWR.CR.SetBits(PWR_SCALE1) // enable HSE stm32.RCC.CR.Set(stm32.RCC_CR_HSEON) for !stm32.RCC.CR.HasBits(stm32.RCC_CR_HSERDY) { } // Since the main-PLL configuration parameters cannot be changed once PLL is // enabled, it is recommended to configure PLL before enabling it (selection // of the HSI or HSE oscillator as PLL clock source, and configuration of // division factors M, N, P, and Q). // disable PLL and wait for it to reset stm32.RCC.CR.ClearBits(stm32.RCC_CR_PLLON) for stm32.RCC.CR.HasBits(stm32.RCC_CR_PLLRDY) { } // set HSE as PLL source and configure clock divisors stm32.RCC.PLLCFGR.Set(PLL_SRC_HSE | PLL_DIV_M | PLL_MLT_N | PLL_DIV_P | PLL_DIV_Q) // enable PLL and wait for it to sync stm32.RCC.CR.SetBits(stm32.RCC_CR_PLLON) for !stm32.RCC.CR.HasBits(stm32.RCC_CR_PLLRDY) { } } func initCLK() { // After reset, the CPU clock frequency is 16 MHz and 0 wait state (WS) is // configured in the FLASH_ACR register. // // It is highly recommended to use the following software sequences to tune // the number of wait states needed to access the Flash memory with the CPU // frequency. // // 1. Program the new number of wait states to the LATENCY bits in the // FLASH_ACR register // 2. Check that the new number of wait states is taken into account to access // the Flash memory by reading the FLASH_ACR register // 3. Modify the CPU clock source by writing the SW bits in the RCC_CFGR // register // 4. If needed, modify the CPU clock prescaler by writing the HPRE bits in // RCC_CFGR // 5. Check that the new CPU clock source or/and the new CPU clock prescaler // value is/are taken into account by reading the clock source status (SWS // bits) or/and the AHB prescaler value (HPRE bits), respectively, in the // RCC_CFGR register. // configure instruction/data caching, prefetch, and flash access wait states stm32.FLASH.ACR.Set(FLASH_OPTIONS | FLASH_LATENCY) for !stm32.FLASH.ACR.HasBits(FLASH_LATENCY) { // verify new wait states } // After a system reset, the HSI oscillator is selected as the system clock. // When a clock source is used directly or through PLL as the system clock, it // is not possible to stop it. // // A switch from one clock source to another occurs only if the target clock // source is ready (clock stable after startup delay or PLL locked). If a // clock source that is not yet ready is selected, the switch occurs when the // clock source is ready. Status bits in the RCC clock control register // (RCC_CR) indicate which clock(s) is (are) ready and which clock is // currently used as the system clock. // set CPU clock source to PLL stm32.RCC.CFGR.SetBits(SYSCLK_SRC_PLL) // update PCKL1/2 and HCLK divisors stm32.RCC.CFGR.SetBits(RCC_DIV_PCLK1 | RCC_DIV_PCLK2 | RCC_DIV_HCLK) // verify system clock source is ready for !stm32.RCC.CFGR.HasBits(SYSCLK_STAT_PLL) { } // enable the CCM RAM clock stm32.RCC.AHB1ENR.SetBits(CLK_CCM_RAM) } func initCOM() { if machine.NUM_UART_INTERFACES > 0 { machine.InitSerial() } } func putchar(c byte) { machine.Serial.WriteByte(c) } func getchar() byte { for machine.Serial.Buffered() == 0 { Gosched() } v, _ := machine.Serial.ReadByte() return v } func buffered() int { return machine.Serial.Buffered() } ================================================ FILE: src/runtime/runtime_stm32f407.go ================================================ //go:build stm32f4 && stm32f407 package runtime import "device/stm32" /* clock settings +-------------+--------+ | HSE | 8mhz | | SYSCLK | 168mhz | | HCLK | 168mhz | | APB2(PCLK2) | 84mhz | | APB1(PCLK1) | 42mhz | +-------------+--------+ */ const ( HSE_STARTUP_TIMEOUT = 0x0500 // PLL Options - See RM0090 Reference Manual pg. 95 PLL_M = 8 // PLL_VCO = (HSE_VALUE or HSI_VLAUE / PLL_M) * PLL_N PLL_N = 336 PLL_P = 2 // SYSCLK = PLL_VCO / PLL_P PLL_Q = 7 // USB OTS FS, SDIO and RNG Clock = PLL_VCO / PLL_Q PLL_CFGR = PLL_M | (PLL_N << stm32.RCC_PLLCFGR_PLLN_Pos) | (((PLL_P >> 1) - 1) << stm32.RCC_PLLCFGR_PLLP_Pos) | (1 << stm32.RCC_PLLCFGR_PLLSRC_Pos) | (PLL_Q << stm32.RCC_PLLCFGR_PLLQ_Pos) ) ================================================ FILE: src/runtime/runtime_stm32f469.go ================================================ //go:build stm32f4 && stm32f469 package runtime import "device/stm32" /* clock settings +-------------+--------+ | HSE | 8mhz | | SYSCLK | 180mhz | | HCLK | 180mhz | | APB2(PCLK2) | 90mhz | | APB1(PCLK1) | 45mhz | +-------------+--------+ */ const ( HSE_STARTUP_TIMEOUT = 0x0500 // PLL Options - See RM0386 Reference Manual pg. 148 PLL_M = 8 // PLL_VCO = (HSE_VALUE or HSI_VALUE / PLL_M) * PLL_N PLL_N = 360 PLL_P = 2 // SYSCLK = PLL_VCO / PLL_P PLL_Q = 7 // USB OTS FS, SDIO and RNG Clock = PLL_VCO / PLL_Q PLL_R = 6 // DSI PLL_CFGR = PLL_M | (PLL_N << stm32.RCC_PLLCFGR_PLLN_Pos) | (((PLL_P >> 1) - 1) << stm32.RCC_PLLCFGR_PLLP_Pos) | (1 << stm32.RCC_PLLCFGR_PLLSRC_Pos) | (PLL_Q << stm32.RCC_PLLCFGR_PLLQ_Pos) | (PLL_R << stm32.RCC_PLLCFGR_PLLR_Pos) ) ================================================ FILE: src/runtime/runtime_stm32f7x2.go ================================================ //go:build stm32 && stm32f7x2 package runtime import ( "device/stm32" "machine" ) /* clock settings +-------------+--------+ | HSE | 8mhz | | SYSCLK | 216mhz | | HCLK | 216mhz | | APB1(PCLK1) | 27mhz | | APB2(PCLK2) | 108mhz | +-------------+--------+ */ const ( HSE_STARTUP_TIMEOUT = 0x0500 PLL_M = 4 PLL_N = 216 PLL_P = 2 PLL_Q = 2 ) func init() { initCLK() machine.InitSerial() initTickTimer(&machine.TIM3) } func putchar(c byte) { machine.Serial.WriteByte(c) } func getchar() byte { for machine.Serial.Buffered() == 0 { Gosched() } v, _ := machine.Serial.ReadByte() return v } func buffered() int { return machine.Serial.Buffered() } func initCLK() { // PWR_CLK_ENABLE stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_PWREN) _ = stm32.RCC.APB1ENR.Get() // PWR_VOLTAGESCALING_CONFIG stm32.PWR.CR1.ReplaceBits(0x3< 7 { stm32.FLASH.ACR.ReplaceBits(7, stm32.FLASH_ACR_LATENCY_Msk, 0) } // Set APB1 and APB2 clocks (0x1800 = DIV8, 0x1000 = DIV2) stm32.RCC.CFGR.ReplaceBits(0x1800, stm32.RCC_CFGR_PPRE1_Msk, 0) stm32.RCC.CFGR.ReplaceBits(0x1000<<3, stm32.RCC_CFGR_PPRE2_Msk, 0) } func initOsc() { // Enable HSE, wait until ready stm32.RCC.CR.SetBits(stm32.RCC_CR_HSEON) for !stm32.RCC.CR.HasBits(stm32.RCC_CR_HSERDY) { } // Disable the PLL, wait until disabled stm32.RCC.CR.ClearBits(stm32.RCC_CR_PLLON) for stm32.RCC.CR.HasBits(stm32.RCC_CR_PLLRDY) { } // Configure the PLL stm32.RCC.PLLCFGR.Set(0x20000000 | (1 << stm32.RCC_PLLCFGR_PLLSRC_Pos) | // 1 = HSE PLL_M | (PLL_N << stm32.RCC_PLLCFGR_PLLN_Pos) | (((PLL_P >> 1) - 1) << stm32.RCC_PLLCFGR_PLLP_Pos) | (PLL_Q << stm32.RCC_PLLCFGR_PLLQ_Pos)) // Enable the PLL, wait until ready stm32.RCC.CR.SetBits(stm32.RCC_CR_PLLON) for !stm32.RCC.CR.HasBits(stm32.RCC_CR_PLLRDY) { } } ================================================ FILE: src/runtime/runtime_stm32l0.go ================================================ //go:build stm32l0 package runtime import ( "device/stm32" "machine" ) const ( RCC_SYSCLK_DIV1 = 0 // Needs SVD update (should be stm32.RCC_SYSCLK_DIV1) ) func putchar(c byte) { machine.Serial.WriteByte(c) } func getchar() byte { for machine.Serial.Buffered() == 0 { Gosched() } v, _ := machine.Serial.ReadByte() return v } func buffered() int { return machine.Serial.Buffered() } func initCLK() { // Set Power Regulator to enable max performance (1.8V) stm32.PWR.CR.ReplaceBits(1< getFlashLatency() { setFlashLatency(FlashLatency) for getFlashLatency() != FlashLatency { } } // HCLK stm32.RCC.CFGR.ReplaceBits(RCC_SYSCLK_DIV1, stm32.RCC_CFGR_HPRE_Msk, 0) // Use PLL As System clock stm32.RCC.CFGR.ReplaceBits(stm32.RCC_CFGR_SWS_PLL, stm32.RCC_CFGR_SW_Msk, 0) for stm32.RCC.CFGR.Get()&stm32.RCC_CFGR_SW_Msk != stm32.RCC_CFGR_SWS_PLL { } // Set prescalers so half system clock (PCLKx = HCLK/2) stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PPRE1_Div2 << stm32.RCC_CFGR_PPRE1_Pos) stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PPRE2_Div2 << stm32.RCC_CFGR_PPRE2_Pos) } func getFlashLatency() uint32 { return stm32.FLASH.ACR.Get() & stm32.Flash_ACR_LATENCY_Msk } func setFlashLatency(l uint32) { stm32.FLASH.ACR.ReplaceBits(l, stm32.Flash_ACR_LATENCY_Msk, 0) } ================================================ FILE: src/runtime/runtime_stm32l0x1.go ================================================ //go:build stm32l0x1 package runtime import ( "device/stm32" "machine" ) const ( FlashLatency = stm32.Flash_ACR_LATENCY_WS1 ) func init() { initCLK() machine.InitSerial() initTickTimer(&machine.TIM21) } ================================================ FILE: src/runtime/runtime_stm32l0x2.go ================================================ //go:build stm32l0x2 package runtime import ( "device/stm32" "machine" ) const ( FlashLatency = stm32.Flash_ACR_LATENCY_WS1 ) func init() { initCLK() machine.InitSerial() initTickTimer(&machine.TIM3) } ================================================ FILE: src/runtime/runtime_stm32l4.go ================================================ //go:build stm32 && stm32l4 package runtime import ( "device/stm32" "machine" ) const ( PWR_CR1_VOS_0 = 1 << stm32.PWR_CR1_VOS_Pos PWR_CR1_VOS_1 = 2 << stm32.PWR_CR1_VOS_Pos PWR_REGULATOR_VOLTAGE_SCALE1 = PWR_CR1_VOS_0 PWR_REGULATOR_VOLTAGE_SCALE2 = PWR_CR1_VOS_1 FLASH_LATENCY_0 = 0 FLASH_LATENCY_1 = 1 FLASH_LATENCY_2 = 2 FLASH_LATENCY_3 = 3 FLASH_LATENCY_4 = 4 RCC_PLLP_DIV2 = 2 RCC_PLLP_DIV7 = 7 RCC_PLLQ_DIV2 = 2 RCC_PLLR_DIV2 = 2 RCC_CFGR_SWS_MSI = 0x0 RCC_CFGR_SWS_PLL = 0xC RCC_PLLSOURCE_MSI = 1 RCC_PLL_SYSCLK = stm32.RCC_PLLCFGR_PLLREN ) type arrtype = uint32 func init() { initCLK() machine.InitSerial() initTickTimer(&machine.TIM15) } func putchar(c byte) { machine.Serial.WriteByte(c) } func getchar() byte { for machine.Serial.Buffered() == 0 { Gosched() } v, _ := machine.Serial.ReadByte() return v } func buffered() int { return machine.Serial.Buffered() } func initCLK() { // PWR_CLK_ENABLE stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_PWREN) _ = stm32.RCC.APB1ENR1.Get() // Disable Backup domain protection if !stm32.PWR.CR1.HasBits(stm32.PWR_CR1_DBP) { stm32.PWR.CR1.SetBits(stm32.PWR_CR1_DBP) for !stm32.PWR.CR1.HasBits(stm32.PWR_CR1_DBP) { } } // Set LSE Drive to LOW stm32.RCC.BDCR.ReplaceBits(0, stm32.RCC_BDCR_LSEDRV_Msk, 0) // Initialize the High-Speed External Oscillator initOsc() // PWR_VOLTAGESCALING_CONFIG stm32.PWR.CR1.ReplaceBits(0, stm32.PWR_CR1_VOS_Msk, 0) _ = stm32.PWR.CR1.Get() // Set flash wait states (min 5 latency units) based on clock if (stm32.FLASH.ACR.Get() & 0xF) < 5 { stm32.FLASH.ACR.ReplaceBits(5, 0xF, 0) } // Ensure HCLK does not exceed max during transition stm32.RCC.CFGR.ReplaceBits(8< 5 { stm32.FLASH.ACR.ReplaceBits(5, 0xF, 0) } // Set APB1 and APB2 clocks (0 = DIV1) stm32.RCC.CFGR.ReplaceBits(0, stm32.RCC_CFGR_PPRE1_Msk, 0) stm32.RCC.CFGR.ReplaceBits(0, stm32.RCC_CFGR_PPRE2_Msk, 0) } func initOsc() { sysclkSource := stm32.RCC.CFGR.Get() & stm32.RCC_CFGR_SWS_Msk pllConfig := stm32.RCC.PLLCFGR.Get() & stm32.RCC_PLLCFGR_PLLSRC_Msk // Enable MSI, adjusting flash latency if sysclkSource == RCC_CFGR_SWS_MSI || (sysclkSource == RCC_CFGR_SWS_PLL && pllConfig == RCC_PLLSOURCE_MSI) { if MSIRANGE > getMSIRange() { setFlashLatencyFromMSIRange(MSIRANGE) setMSIFreq(MSIRANGE, 0) } else { setMSIFreq(MSIRANGE, 0) if sysclkSource == RCC_CFGR_SWS_MSI { setFlashLatencyFromMSIRange(MSIRANGE) } } } else { stm32.RCC.CR.SetBits(stm32.RCC_CR_MSION) for !stm32.RCC.CR.HasBits(stm32.RCC_CR_MSIRDY) { } setMSIFreq(MSIRANGE, 0) } // Enable LSE, wait until ready stm32.RCC.BDCR.SetBits(stm32.RCC_BDCR_LSEON) for !stm32.RCC.BDCR.HasBits(stm32.RCC_BDCR_LSEON) { } // Disable the PLL, wait until disabled stm32.RCC.CR.ClearBits(stm32.RCC_CR_PLLON) for stm32.RCC.CR.HasBits(stm32.RCC_CR_PLLRDY) { } // Configure the PLL stm32.RCC.PLLCFGR.ReplaceBits( (1)| // 1 = RCC_PLLSOURCE_MSI (PLL_M-1)<>1)-1)<>1)-1)<> stm32.RCC_CR_MSIRANGE_Pos } return (stm32.RCC.CSR.Get() & stm32.RCC_CSR_MSISRANGE_Msk) >> stm32.RCC_CSR_MSISRANGE_Pos } func setMSIFreq(r uint32, calibration uint32) { stm32.RCC.CR.SetBits(stm32.RCC_CR_MSIRGSEL) stm32.RCC.CR.ReplaceBits(r< stm32.RCC_CR_MSIRANGE_Range16M { if r > stm32.RCC_CR_MSIRANGE_Range32M { latency = FLASH_LATENCY_2 } else { latency = FLASH_LATENCY_1 } } } else if r > stm32.RCC_CR_MSIRANGE_Range16M { latency = FLASH_LATENCY_3 } else { if r == stm32.RCC_CR_MSIRANGE_Range16M { latency = FLASH_LATENCY_2 } else if r == stm32.RCC_CR_MSIRANGE_Range8M { latency = FLASH_LATENCY_1 } } stm32.FLASH.ACR.ReplaceBits(latency, stm32.Flash_ACR_LATENCY_Msk, 0) } func pwrIsClkEnabled() bool { return stm32.RCC.APB1ENR1.HasBits(stm32.RCC_APB1ENR1_PWREN) } func pwrClkEnable() { stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_PWREN) } func pwrClkDisable() { stm32.RCC.APB1ENR1.ClearBits(stm32.RCC_APB1ENR1_PWREN) } func pwrExGetVoltageRange() uint32 { return stm32.PWR.CR1.Get() & stm32.PWR_CR1_VOS_Msk } ================================================ FILE: src/runtime/runtime_stm32l4x2.go ================================================ //go:build stm32 && stm32l4x2 package runtime import ( "device/stm32" ) /* clock settings +-------------+-----------+ | LSE | 32.768khz | | SYSCLK | 80mhz | | HCLK | 80mhz | | APB1(PCLK1) | 80mhz | | APB2(PCLK2) | 80mhz | +-------------+-----------+ */ const ( HSE_STARTUP_TIMEOUT = 0x0500 PLL_M = 1 PLL_N = 40 PLL_P = RCC_PLLP_DIV7 PLL_Q = RCC_PLLQ_DIV2 PLL_R = RCC_PLLR_DIV2 MSIRANGE = stm32.RCC_CR_MSIRANGE_Range4M ) ================================================ FILE: src/runtime/runtime_stm32l4x5.go ================================================ //go:build stm32 && stm32l4x5 package runtime import ( "device/stm32" ) /* clock settings +-------------+-----------+ | LSE | 32.768khz | | SYSCLK | 120mhz | | HCLK | 120mhz | | APB1(PCLK1) | 120mhz | | APB2(PCLK2) | 120mhz | +-------------+-----------+ */ const ( HSE_STARTUP_TIMEOUT = 0x0500 PLL_M = 1 PLL_N = 60 PLL_P = RCC_PLLP_DIV2 PLL_Q = RCC_PLLQ_DIV2 PLL_R = RCC_PLLR_DIV2 MSIRANGE = stm32.RCC_CR_MSIRANGE_Range4M ) ================================================ FILE: src/runtime/runtime_stm32l4x6.go ================================================ //go:build stm32 && stm32l4x6 package runtime import ( "device/stm32" ) /* clock settings +-------------+-----------+ | LSE | 32.768khz | | SYSCLK | 80mhz | | HCLK | 80mhz | | APB1(PCLK1) | 80mhz | | APB2(PCLK2) | 80mhz | +-------------+-----------+ */ const ( HSE_STARTUP_TIMEOUT = 0x0500 PLL_M = 1 PLL_N = 40 PLL_P = RCC_PLLP_DIV7 PLL_Q = RCC_PLLQ_DIV2 PLL_R = RCC_PLLR_DIV2 MSIRANGE = stm32.RCC_CR_MSIRANGE_Range4M ) ================================================ FILE: src/runtime/runtime_stm32l5x2.go ================================================ //go:build stm32 && stm32l5x2 package runtime import ( "device/stm32" "machine" ) /* clock settings +-------------+-----------+ | LSE | 32.768khz | | SYSCLK | 110mhz | | HCLK | 110mhz | | APB1(PCLK1) | 110mhz | | APB2(PCLK2) | 110mhz | +-------------+-----------+ */ const ( HSE_STARTUP_TIMEOUT = 0x0500 PLL_M = 1 PLL_N = 55 PLL_P = 7 // RCC_PLLP_DIV7 PLL_Q = 2 // RCC_PLLQ_DIV2 PLL_R = 2 // RCC_PLLR_DIV2 ) func init() { initCLK() machine.InitSerial() initTickTimer(&machine.TIM16) } func putchar(c byte) { machine.Serial.WriteByte(c) } func getchar() byte { for machine.Serial.Buffered() == 0 { Gosched() } v, _ := machine.Serial.ReadByte() return v } func buffered() int { return machine.Serial.Buffered() } func initCLK() { // PWR_CLK_ENABLE stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_PWREN) _ = stm32.RCC.APB1ENR1.Get() // PWR_VOLTAGESCALING_CONFIG stm32.PWR.CR1.ReplaceBits(0, stm32.PWR_CR1_VOS_Msk, 0) _ = stm32.PWR.CR1.Get() // Initialize the High-Speed External Oscillator initOsc() // Set flash wait states (min 5 latency units) based on clock if (stm32.FLASH.ACR.Get() & 0xF) < 5 { stm32.FLASH.ACR.ReplaceBits(5, 0xF, 0) } // Ensure HCLK does not exceed max during transition stm32.RCC.CFGR.ReplaceBits(8< 5 { stm32.FLASH.ACR.ReplaceBits(5, 0xF, 0) } // Set APB1 and APB2 clocks (0 = DIV1) stm32.RCC.CFGR.ReplaceBits(0, stm32.RCC_CFGR_PPRE1_Msk, 0) stm32.RCC.CFGR.ReplaceBits(0, stm32.RCC_CFGR_PPRE2_Msk, 0) } func initOsc() { // Enable HSI, wait until ready stm32.RCC.CR.SetBits(stm32.RCC_CR_HSION) for !stm32.RCC.CR.HasBits(stm32.RCC_CR_HSIRDY) { } // Disable Backup domain protection if !stm32.PWR.CR1.HasBits(stm32.PWR_CR1_DBP) { stm32.PWR.CR1.SetBits(stm32.PWR_CR1_DBP) for !stm32.PWR.CR1.HasBits(stm32.PWR_CR1_DBP) { } } // Set LSE Drive to LOW stm32.RCC.BDCR.ReplaceBits(0, stm32.RCC_BDCR_LSEDRV_Msk, 0) // Enable LSE, wait until ready stm32.RCC.BDCR.SetBits(stm32.RCC_BDCR_LSEON) for !stm32.RCC.BDCR.HasBits(stm32.RCC_BDCR_LSEON) { } // Ensure LSESYS disabled stm32.RCC.BDCR.ClearBits(stm32.RCC_BDCR_LSESYSEN) for stm32.RCC.BDCR.HasBits(stm32.RCC_BDCR_LSESYSEN) { } // Enable HSI48, wait until ready stm32.RCC.CRRCR.SetBits(stm32.RCC_CRRCR_HSI48ON) for !stm32.RCC.CRRCR.HasBits(stm32.RCC_CRRCR_HSI48ON) { } // Disable the PLL, wait until disabled stm32.RCC.CR.ClearBits(stm32.RCC_CR_PLLON) for stm32.RCC.CR.HasBits(stm32.RCC_CR_PLLRDY) { } // Configure the PLL stm32.RCC.PLLCFGR.ReplaceBits( (1)| // 1 = RCC_PLLSOURCE_MSI (PLL_M-1)<>1)-1)<>1)-1)<= putcharBufferSize { putcharIOVec.bufLen = putcharPosition fd_write(stdout, &putcharIOVec, 1, &putcharNWritten) putcharPosition = 0 } } func getchar() byte { // dummy, TODO return 0 } func buffered() int { // dummy, TODO return 0 } //go:linkname now time.now func now() (sec int64, nsec int32, mono int64) { mono = nanotime() sec = mono / (1000 * 1000 * 1000) nsec = int32(mono - sec*(1000*1000*1000)) return } // Abort executes the wasm 'unreachable' instruction. func abort() { trap() } //go:linkname syscall_Exit syscall.Exit func syscall_Exit(code int) { // Flush stdio buffers. __stdio_exit() // Exit the program. proc_exit(uint32(code)) } func mainReturnExit() { syscall_Exit(0) } // TinyGo does not yet support any form of parallelism on WebAssembly, so these // can be left empty. //go:linkname procPin sync/atomic.runtime_procPin func procPin() { } //go:linkname procUnpin sync/atomic.runtime_procUnpin func procUnpin() { } func hardwareRand() (n uint64, ok bool) { n |= uint64(libc_arc4random()) n |= uint64(libc_arc4random()) << 32 return n, true } // uint32_t arc4random(void); // //export arc4random func libc_arc4random() uint32 // int *__errno_location(void); // //export __errno_location func libc_errno_location() *int32 ================================================ FILE: src/runtime/runtime_tinygowasm_unknown.go ================================================ //go:build wasm_unknown package runtime const ( stdout = 1 ) func putchar(c byte) { } func getchar() byte { // dummy, TODO return 0 } func buffered() int { // dummy, TODO return 0 } //go:linkname now time.now func now() (sec int64, nsec int32, mono int64) { return 0, 0, 0 } // Abort executes the wasm 'unreachable' instruction. func abort() { trap() } //go:linkname syscall_Exit syscall.Exit func syscall_Exit(code int) { // Because this is the "unknown" target we can't call an exit function. // But we also can't just return since the program will likely expect this // function to never return. So we panic instead. runtimePanic("unsupported: syscall.Exit") } // There is not yet any support for any form of parallelism on WebAssembly, so these // can be left empty. //go:linkname procPin sync/atomic.runtime_procPin func procPin() { } //go:linkname procUnpin sync/atomic.runtime_procUnpin func procUnpin() { } func hardwareRand() (n uint64, ok bool) { return 0, false } func libc_errno_location() *int32 { // CGo is unavailable, so this function should be unreachable. runtimePanic("runtime: no cgo errno") return nil } ================================================ FILE: src/runtime/runtime_tinygowasmp2.go ================================================ //go:build wasip2 package runtime import ( "internal/cm" exit "internal/wasi/cli/v0.2.0/exit" stdout "internal/wasi/cli/v0.2.0/stdout" monotonicclock "internal/wasi/clocks/v0.2.0/monotonic-clock" wallclock "internal/wasi/clocks/v0.2.0/wall-clock" random "internal/wasi/random/v0.2.0/random" ) const putcharBufferSize = 120 // Using global variables to avoid heap allocation. var ( putcharStdout = stdout.GetStdout() putcharBuffer = [putcharBufferSize]byte{} putcharPosition uint = 0 ) func putchar(c byte) { putcharBuffer[putcharPosition] = c putcharPosition++ if c == '\n' || putcharPosition >= putcharBufferSize { list := cm.NewList(&putcharBuffer[0], putcharPosition) putcharStdout.BlockingWriteAndFlush(list) // error return ignored; can't do anything anyways putcharPosition = 0 } } func getchar() byte { // dummy, TODO return 0 } func buffered() int { // dummy, TODO return 0 } //go:linkname now time.now func now() (sec int64, nsec int32, mono int64) { now := wallclock.Now() sec = int64(now.Seconds) nsec = int32(now.Nanoseconds) mono = int64(monotonicclock.Now()) return } // Abort executes the wasm 'unreachable' instruction. func abort() { trap() } //go:linkname syscall_Exit syscall.Exit func syscall_Exit(code int) { exit.Exit(code != 0) } func mainReturnExit() { // WASIp2 does not use _start, instead it uses _initialize and a custom // WASIp2-specific main function. So this should never be called in // practice. runtimePanic("unreachable: _start was called") } // TinyGo does not yet support any form of parallelism on WebAssembly, so these // can be left empty. //go:linkname procPin sync/atomic.runtime_procPin func procPin() { } //go:linkname procUnpin sync/atomic.runtime_procUnpin func procUnpin() { } func hardwareRand() (n uint64, ok bool) { return random.GetRandomU64(), true } func libc_errno_location() *int32 { // CGo is unavailable, so this function should be unreachable. runtimePanic("runtime: no cgo errno") return nil } ================================================ FILE: src/runtime/runtime_tkey.go ================================================ //go:build tkey // This file implements target-specific things for the TKey. package runtime import ( "device/tkey" "machine" "runtime/volatile" ) //export main func main() { preinit() initPeripherals() run() exit(0) } // initPeripherals configures peripherals the way the runtime expects them. func initPeripherals() { // prescaler value that results in 0.00001-second timer-ticks. // given an 18 MHz processor, a millisecond is about 18,000 cycles. tkey.TIMER.PRESCALER.Set(18 * machine.MHz / 100000) machine.InitSerial() } func putchar(c byte) { machine.Serial.WriteByte(c) } func getchar() byte { for machine.Serial.Buffered() == 0 { Gosched() } v, _ := machine.Serial.ReadByte() return v } func buffered() int { return machine.Serial.Buffered() } var timestamp volatile.Register32 // ticks returns the current value of the timer in ticks. func ticks() timeUnit { return timeUnit(timestamp.Get()) } // sleepTicks sleeps for at least the duration d. func sleepTicks(d timeUnit) { target := uint32(ticks() + d) tkey.TIMER.TIMER.Set(uint32(d)) tkey.TIMER.CTRL.SetBits(tkey.TK1_MMIO_TIMER_CTRL_START) for tkey.TIMER.STATUS.Get() != 0 { } timestamp.Set(target) } ================================================ FILE: src/runtime/runtime_tkey_baremetal.go ================================================ //go:build tkey && !qemu package runtime import "device/riscv" // ticksToNanoseconds converts ticks (at 18MHz) to 10 µs. func ticksToNanoseconds(ticks timeUnit) int64 { return int64(ticks) * 10000 } // nanosecondsToTicks converts 10 µs to ticks (at 18MHz). func nanosecondsToTicks(ns int64) timeUnit { return timeUnit(ns / 10000) } func exit(code int) { abort() } func abort() { // Force illegal instruction to halt CPU riscv.Asm("unimp") } ================================================ FILE: src/runtime/runtime_unix.c ================================================ //go:build none // This file is included on Darwin and Linux (despite the //go:build line above). #define _GNU_SOURCE #define _XOPEN_SOURCE #include #include #include #include #include void tinygo_handle_fatal_signal(int sig, uintptr_t addr); static void signal_handler(int sig, siginfo_t *info, void *context) { ucontext_t* uctx = context; uintptr_t addr = 0; #if __APPLE__ #if __arm64__ addr = uctx->uc_mcontext->__ss.__pc; #elif __x86_64__ addr = uctx->uc_mcontext->__ss.__rip; #else #error unknown architecture #endif #elif __linux__ // Note: this can probably be simplified using the MC_PC macro in musl, // but this works for now. #if __arm__ addr = uctx->uc_mcontext.arm_pc; #elif __i386__ addr = uctx->uc_mcontext.gregs[REG_EIP]; #elif __x86_64__ addr = uctx->uc_mcontext.gregs[REG_RIP]; #else // aarch64, mips, maybe others addr = uctx->uc_mcontext.pc; #endif #else #error unknown platform #endif tinygo_handle_fatal_signal(sig, addr); } void tinygo_register_fatal_signals(void) { struct sigaction act = { 0 }; // SA_SIGINFO: we want the 2 extra parameters // SA_RESETHAND: only catch the signal once (the handler will re-raise the signal) act.sa_flags = SA_SIGINFO | SA_RESETHAND; act.sa_sigaction = &signal_handler; // Register the signal handler for common issues. There are more signals, // which can be added if needed. sigaction(SIGBUS, &act, NULL); sigaction(SIGILL, &act, NULL); sigaction(SIGSEGV, &act, NULL); } ================================================ FILE: src/runtime/runtime_unix.go ================================================ //go:build darwin || (linux && !baremetal && !wasip1 && !wasm_unknown && !wasip2 && !nintendoswitch) package runtime import ( "internal/futex" "internal/task" "math/bits" "sync/atomic" "tinygo" "unsafe" ) //export write func libc_write(fd int32, buf unsafe.Pointer, count uint) int //export usleep func usleep(usec uint) int //export pause func pause() int32 // void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); // Note: off_t is defined as int64 because: // - musl (used on Linux) always defines it as int64 // - darwin is practically always 64-bit anyway // //export mmap func mmap(addr unsafe.Pointer, length uintptr, prot, flags, fd int, offset int64) unsafe.Pointer //export abort func abort() //export exit func exit(code int) //export raise func raise(sig int32) //export clock_gettime func libc_clock_gettime(clk_id int32, ts *timespec) //export __clock_gettime64 func libc_clock_gettime64(clk_id int32, ts *timespec) // Portable (64-bit) variant of clock_gettime. func clock_gettime(clk_id int32, ts *timespec) { if TargetBits == 32 { // This is a 32-bit architecture (386, arm, etc). // We would like to use the 64-bit version of this function so that // binaries will continue to run after Y2038. // For more information: // - https://musl.libc.org/time64.html // - https://sourceware.org/glibc/wiki/Y2038ProofnessDesign libc_clock_gettime64(clk_id, ts) } else { // This is a 64-bit architecture (amd64, arm64, etc). // Use the regular variant, because it already fixes the Y2038 problem // by using 64-bit integer types. libc_clock_gettime(clk_id, ts) } } // Note: tv_sec and tv_nsec normally vary in size by platform. However, we're // using the time64 variant (see clock_gettime above), so the formats are the // same between 32-bit and 64-bit architectures. // There is one issue though: on big-endian systems, tv_nsec would be incorrect. // But we don't support big-endian systems yet (as of 2021) so this is fine. type timespec struct { tv_sec int64 // time_t with time64 support (always 64-bit) tv_nsec int64 // unsigned 64-bit integer on all time64 platforms } // Highest address of the stack of the main thread. var stackTop uintptr // Entry point for Go. Initialize all packages and call main.main(). // //export main func main(argc int32, argv *unsafe.Pointer) int { if needsStaticHeap { // Allocate area for the heap if the GC needs it. allocateHeap() } // Store argc and argv for later use. main_argc = argc main_argv = argv // Register some fatal signals, so that we can print slightly better error // messages. tinygo_register_fatal_signals() // Obtain the initial stack pointer right before calling the run() function. // The run function has been moved to a separate (non-inlined) function so // that the correct stack pointer is read. stackTop = getCurrentStackPointer() runMain() // For libc compatibility. return 0 } var ( main_argc int32 main_argv *unsafe.Pointer args []string ) //go:linkname os_runtime_args os.runtime_args func os_runtime_args() []string { if args == nil { // Make args slice big enough so that it can store all command line // arguments. args = make([]string, main_argc) // Initialize command line parameters. argv := main_argv for i := 0; i < int(main_argc); i++ { // Convert the C string to a Go string. length := strlen(*argv) arg := (*_string)(unsafe.Pointer(&args[i])) arg.length = length arg.ptr = (*byte)(*argv) // This is the Go equivalent of "argv++" in C. argv = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(argv), unsafe.Sizeof(argv))) } } return args } // Must be a separate function to get the correct stack pointer. // //go:noinline func runMain() { run() } //export tinygo_register_fatal_signals func tinygo_register_fatal_signals() // Print fatal errors when they happen, including the instruction location. // With the particular formatting below, `tinygo run` can extract the location // where the signal happened and try to show the source location based on DWARF // information. // //export tinygo_handle_fatal_signal func tinygo_handle_fatal_signal(sig int32, addr uintptr) { if panicStrategy() == tinygo.PanicStrategyTrap { trap() } // Print signal including the faulting instruction. if addr != 0 { printstring("panic: runtime error at ") printptr(addr) } else { printstring("panic: runtime error") } printstring(": caught signal ") switch sig { case sig_SIGBUS: println("SIGBUS") case sig_SIGILL: println("SIGILL") case sig_SIGSEGV: println("SIGSEGV") default: println(sig) } // TODO: it might be interesting to also print the invalid address for // SIGSEGV and SIGBUS. // Do *not* abort here, instead raise the same signal again. The signal is // registered with SA_RESETHAND which means it executes only once. So when // we raise the signal again below, the signal isn't handled specially but // is handled in the default way (probably exiting the process, maybe with a // core dump). raise(sig) } //go:extern environ var environ *unsafe.Pointer //go:linkname syscall_runtime_envs syscall.runtime_envs func syscall_runtime_envs() []string { // Count how many environment variables there are. env := environ numEnvs := 0 for *env != nil { numEnvs++ env = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(env), unsafe.Sizeof(environ))) } // Create a string slice of all environment variables. // This requires just a single heap allocation. env = environ envs := make([]string, 0, numEnvs) for *env != nil { ptr := *env length := strlen(ptr) s := _string{ ptr: (*byte)(ptr), length: length, } envs = append(envs, *(*string)(unsafe.Pointer(&s))) env = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(env), unsafe.Sizeof(environ))) } return envs } func putchar(c byte) { buf := [1]byte{c} libc_write(1, unsafe.Pointer(&buf[0]), 1) } func ticksToNanoseconds(ticks timeUnit) int64 { // The OS API works in nanoseconds so no conversion necessary. return int64(ticks) } func nanosecondsToTicks(ns int64) timeUnit { // The OS API works in nanoseconds so no conversion necessary. return timeUnit(ns) } func sleepTicks(d timeUnit) { until := ticks() + d for { // Sleep for the given amount of time. // If a signal arrived before going to sleep, or during the sleep, the // sleep will exit early. signalFutex.WaitUntil(0, uint64(ticksToNanoseconds(d))) // Check whether there was a signal before or during the call to // WaitUntil. if signalFutex.Swap(0) != 0 { if checkSignals() && hasScheduler { // We got a signal, so return to the scheduler. // (If there is no scheduler, there is no other goroutine that // might need to run now). return } } // Set duration (in next loop iteration) to the remaining time. d = until - ticks() if d <= 0 { return } } } func getTime(clock int32) uint64 { ts := timespec{} clock_gettime(clock, &ts) return uint64(ts.tv_sec)*1000*1000*1000 + uint64(ts.tv_nsec) } // Return monotonic time in nanoseconds. func monotime() uint64 { return getTime(clock_MONOTONIC_RAW) } func ticks() timeUnit { return timeUnit(monotime()) } //go:linkname now time.now func now() (sec int64, nsec int32, mono int64) { ts := timespec{} clock_gettime(clock_REALTIME, &ts) sec = int64(ts.tv_sec) nsec = int32(ts.tv_nsec) mono = nanotime() return } //go:linkname syscall_Exit syscall.Exit func syscall_Exit(code int) { exit(code) } // TinyGo does not yet support any form of parallelism on an OS, so these can be // left empty. //go:linkname procPin sync/atomic.runtime_procPin func procPin() { } //go:linkname procUnpin sync/atomic.runtime_procUnpin func procUnpin() { } var heapSize uintptr = 128 * 1024 // small amount to start var heapMaxSize uintptr var heapStart, heapEnd uintptr func allocateHeap() { // Allocate a large chunk of virtual memory. Because it is virtual, it won't // really be allocated in RAM. Memory will only be allocated when it is // first touched. heapMaxSize = 1 * 1024 * 1024 * 1024 // 1GB for the entire heap for { addr := mmap(nil, heapMaxSize, flag_PROT_READ|flag_PROT_WRITE, flag_MAP_PRIVATE|flag_MAP_ANONYMOUS, -1, 0) if addr == unsafe.Pointer(^uintptr(0)) { // Heap was too big to be mapped by mmap. Reduce the maximum size. // We might want to make this a bit smarter than simply halving the // heap size. // This can happen on 32-bit systems. heapMaxSize /= 2 if heapMaxSize < 4096 { runtimePanic("cannot allocate heap memory") } continue } heapStart = uintptr(addr) heapEnd = heapStart + heapSize break } } // growHeap tries to grow the heap size. It returns true if it succeeds, false // otherwise. func growHeap() bool { if heapSize == heapMaxSize { // Already at the max. If we run out of memory, we should consider // increasing heapMaxSize on 64-bit systems. return false } // Grow the heap size used by the program. heapSize = (heapSize * 4 / 3) &^ 4095 // grow by around 33% if heapSize > heapMaxSize { heapSize = heapMaxSize } setHeapEnd(heapStart + heapSize) return true } // Indicate whether signals have been registered. var hasSignals bool // Futex for the signal handler. // The value is 0 when there are no new signals, or 1 when there are unhandled // signals and the main thread doesn't know about it yet. // When a signal arrives, the futex value is changed to 1 and if it was 0 // before, all waiters are awoken. // When a wait exits, the value is changed to 0 and if it wasn't 0 before, the // signals are checked. var signalFutex futex.Futex // Mask of signals that have been received. The signal handler atomically ORs // signals into this value. var receivedSignals atomic.Uint32 //go:linkname signal_enable os/signal.signal_enable func signal_enable(s uint32) { if s >= 32 { // TODO: to support higher signal numbers, we need to turn // receivedSignals into a uint32 array. runtimePanicAt(returnAddress(0), "unsupported signal number") } // This is intentonally a non-atomic store. This is safe, since hasSignals // is only used in waitForEvents which is only called when there's a // scheduler (and therefore there is no parallelism). hasSignals = true // It's easier to implement this function in C. tinygo_signal_enable(s) } //go:linkname signal_ignore os/signal.signal_ignore func signal_ignore(s uint32) { if s >= 32 { // TODO: to support higher signal numbers, we need to turn // receivedSignals into a uint32 array. runtimePanicAt(returnAddress(0), "unsupported signal number") } tinygo_signal_ignore(s) } //go:linkname signal_disable os/signal.signal_disable func signal_disable(s uint32) { if s >= 32 { // TODO: to support higher signal numbers, we need to turn // receivedSignals into a uint32 array. runtimePanicAt(returnAddress(0), "unsupported signal number") } tinygo_signal_disable(s) } //go:linkname signal_waitUntilIdle os/signal.signalWaitUntilIdle func signal_waitUntilIdle() { // Wait until signal_recv has processed all signals. for receivedSignals.Load() != 0 { // TODO: this becomes a busy loop when using threads. // We might want to pause until signal_recv has no more incoming signals // to process. Gosched() } } //export tinygo_signal_enable func tinygo_signal_enable(s uint32) //export tinygo_signal_ignore func tinygo_signal_ignore(s uint32) //export tinygo_signal_disable func tinygo_signal_disable(s uint32) // void tinygo_signal_handler(int sig); // //export tinygo_signal_handler func tinygo_signal_handler(s int32) { // The following loop is equivalent to the following: // // receivedSignals.Or(uint32(1) << uint32(s)) // // TODO: use this instead of a loop once we drop support for Go 1.22. for { mask := uint32(1) << uint32(s) val := receivedSignals.Load() swapped := receivedSignals.CompareAndSwap(val, val|mask) if swapped { break } } // Notify the main thread that there was a signal. // This will exit the call to Wait or WaitUntil early. if signalFutex.Swap(1) == 0 { // Changed from 0 to 1, so there may have been a waiting goroutine. // This could be optimized to avoid a syscall when there are no waiting // goroutines. signalFutex.WakeAll() } } // Task waiting for a signal to arrive, or nil if it is running or there are no // signals. var signalRecvWaiter atomic.Pointer[task.Task] //go:linkname signal_recv os/signal.signal_recv func signal_recv() uint32 { // Function called from os/signal to get the next received signal. for { val := receivedSignals.Load() if val == 0 { // There are no signals to receive. Sleep until there are. if signalRecvWaiter.Swap(task.Current()) != nil { // We expect only a single goroutine to call signal_recv. runtimePanic("signal_recv called concurrently") } task.Pause() continue } // Extract the lowest numbered signal number from receivedSignals. num := uint32(bits.TrailingZeros32(val)) // Atomically clear the signal number from receivedSignals. // TODO: use atomic.Uint32.And once we drop support for Go 1.22 instead // of this loop, like so: // // receivedSignals.And(^(uint32(1) << num)) // for { newVal := val &^ (1 << num) swapped := receivedSignals.CompareAndSwap(val, newVal) if swapped { break } val = receivedSignals.Load() } return num } } // Reactivate the goroutine waiting for signals, if there are any. // Return true if it was reactivated (and therefore the scheduler should run // again), and false otherwise. func checkSignals() bool { if receivedSignals.Load() != 0 { if waiter := signalRecvWaiter.Swap(nil); waiter != nil { scheduleTask(waiter) return true } } return false } func waitForEvents() { if hasSignals { // Wait as long as the futex value is 0. // This can happen either before or during the call to Wait. // This can be optimized: if the value is nonzero we don't need to do a // futex wait syscall and can instead immediately call checkSignals. signalFutex.Wait(0) // Check for signals that arrived before or during the call to Wait. // If there are any signals, the value is 0. if signalFutex.Swap(0) != 0 { checkSignals() } } else { // The program doesn't use signals, so this is a deadlock. runtimePanic("deadlocked: no event source") } } ================================================ FILE: src/runtime/runtime_wasip1.go ================================================ //go:build wasip1 package runtime import ( "unsafe" ) // libc constructors // //export __wasm_call_ctors func __wasm_call_ctors() // Read the command line arguments from WASI. // For example, they can be passed to a program with wasmtime like this: // // wasmtime run ./program.wasm arg1 arg2 func init() { __wasm_call_ctors() } var args []string //go:linkname os_runtime_args os.runtime_args func os_runtime_args() []string { if args == nil { // Read the number of args (argc) and the buffer size required to store // all these args (argv). var argc, argv_buf_size uint32 args_sizes_get(&argc, &argv_buf_size) if argc == 0 { return nil } // Obtain the command line arguments argsSlice := make([]unsafe.Pointer, argc) buf := make([]byte, argv_buf_size) args_get(&argsSlice[0], unsafe.Pointer(&buf[0])) // Convert the array of C strings to an array of Go strings. args = make([]string, argc) for i, cstr := range argsSlice { length := strlen(cstr) argString := _string{ length: length, ptr: (*byte)(cstr), } args[i] = *(*string)(unsafe.Pointer(&argString)) } } return args } func ticksToNanoseconds(ticks timeUnit) int64 { return int64(ticks) } func nanosecondsToTicks(ns int64) timeUnit { return timeUnit(ns) } const timePrecisionNanoseconds = 1000 // TODO: how can we determine the appropriate `precision`? var ( sleepTicksSubscription = __wasi_subscription_t{ userData: 0, u: __wasi_subscription_u_t{ tag: __wasi_eventtype_t_clock, u: __wasi_subscription_clock_t{ id: 0, timeout: 0, precision: timePrecisionNanoseconds, flags: 0, }, }, } sleepTicksResult = __wasi_event_t{} sleepTicksNEvents uint32 ) func sleepTicks(d timeUnit) { sleepTicksSubscription.u.u.timeout = uint64(d) poll_oneoff(&sleepTicksSubscription, &sleepTicksResult, 1, &sleepTicksNEvents) } func ticks() timeUnit { var nano uint64 clock_time_get(0, timePrecisionNanoseconds, &nano) return timeUnit(nano) } // Implementations of WASI APIs //go:wasmimport wasi_snapshot_preview1 args_get func args_get(argv *unsafe.Pointer, argv_buf unsafe.Pointer) (errno uint16) //go:wasmimport wasi_snapshot_preview1 args_sizes_get func args_sizes_get(argc *uint32, argv_buf_size *uint32) (errno uint16) //go:wasmimport wasi_snapshot_preview1 clock_time_get func clock_time_get(clockid uint32, precision uint64, time *uint64) (errno uint16) //go:wasmimport wasi_snapshot_preview1 poll_oneoff func poll_oneoff(in *__wasi_subscription_t, out *__wasi_event_t, nsubscriptions uint32, nevents *uint32) (errno uint16) type __wasi_eventtype_t = uint8 const ( __wasi_eventtype_t_clock __wasi_eventtype_t = 0 // TODO: __wasi_eventtype_t_fd_read __wasi_eventtype_t = 1 // TODO: __wasi_eventtype_t_fd_write __wasi_eventtype_t = 2 ) type ( // https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#-subscription-record __wasi_subscription_t struct { userData uint64 u __wasi_subscription_u_t } __wasi_subscription_u_t struct { tag __wasi_eventtype_t // TODO: support fd_read/fd_write event u __wasi_subscription_clock_t } // https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#-subscription_clock-record __wasi_subscription_clock_t struct { id uint32 timeout uint64 precision uint64 flags uint16 } ) type ( // https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#-event-record __wasi_event_t struct { userData uint64 errno uint16 eventType __wasi_eventtype_t // only used for fd_read or fd_write events // TODO: support fd_read/fd_write event _ struct { nBytes uint64 flags uint16 } } ) ================================================ FILE: src/runtime/runtime_wasip2.go ================================================ //go:build wasip2 package runtime import ( "unsafe" "internal/wasi/cli/v0.2.0/environment" wasiclirun "internal/wasi/cli/v0.2.0/run" monotonicclock "internal/wasi/clocks/v0.2.0/monotonic-clock" "internal/cm" ) func init() { wasiclirun.Exports.Run = func() cm.BoolResult { callMain() return false } } var args []string //go:linkname os_runtime_args os.runtime_args func os_runtime_args() []string { if args == nil { args = environment.GetArguments().Slice() } return args } //export cabi_realloc func cabi_realloc(ptr, oldsize, align, newsize unsafe.Pointer) unsafe.Pointer { return realloc(ptr, uintptr(newsize)) } func ticksToNanoseconds(ticks timeUnit) int64 { return int64(ticks) } func nanosecondsToTicks(ns int64) timeUnit { return timeUnit(ns) } func sleepTicks(d timeUnit) { p := monotonicclock.SubscribeDuration(monotonicclock.Duration(d)) p.Block() } func ticks() timeUnit { return timeUnit(monotonicclock.Now()) } ================================================ FILE: src/runtime/runtime_wasm_js.go ================================================ //go:build wasm && !wasip1 package runtime var handleEvent func() //go:linkname setEventHandler syscall/js.setEventHandler func setEventHandler(fn func()) { handleEvent = fn } // We use 1ns per tick, to simplify things. // It would probably be fine to use 1µs per tick, since performance.now only // promises a resolution of 5µs, but 1ns makes the conversions here a bit more // straightforward (since nothing needs to be converted). func ticksToNanoseconds(ticks timeUnit) int64 { return int64(ticks) } func nanosecondsToTicks(ns int64) timeUnit { return timeUnit(ns) } // This function is called by the scheduler. // Schedule a call to runtime.scheduler, do not actually sleep. // //go:wasmimport gojs runtime.sleepTicks func sleepTicks(d timeUnit) //go:wasmimport gojs runtime.ticks func ticks() timeUnit ================================================ FILE: src/runtime/runtime_wasm_js_scheduler.go ================================================ //go:build wasm && !wasi && !scheduler.none && !wasip1 && !wasip2 && !wasm_unknown package runtime //export resume func resume() { go func() { handleEvent() }() scheduler(false) } //export go_scheduler func go_scheduler() { scheduler(false) } ================================================ FILE: src/runtime/runtime_wasm_unknown.go ================================================ //go:build wasm_unknown package runtime // TODO: this is essentially reactor mode wasm. So we might want to support // -buildmode=c-shared (and default to it). // libc constructors // //export __wasm_call_ctors func __wasm_call_ctors() func init() { __wasm_call_ctors() } func ticksToNanoseconds(ticks timeUnit) int64 { return int64(ticks) } func nanosecondsToTicks(ns int64) timeUnit { return timeUnit(ns) } func sleepTicks(d timeUnit) { } func ticks() timeUnit { return timeUnit(0) } func mainReturnExit() { // Don't exit explicitly here. We can't (there is no environment with an // exit call) but also it's not needed. We can just let _start and main.main // return to the caller. } ================================================ FILE: src/runtime/runtime_wasmentry.go ================================================ //go:build tinygo.wasm package runtime // Entry points for WebAssembly modules, and runtime support for // //go:wasmexport: runtime.wasmExport* function calls are inserted by the // compiler for //go:wasmexport support. import ( "internal/task" "unsafe" ) // This is the _start entry point, when using -buildmode=default. func wasmEntryCommand() { // These need to be initialized early so that the heap can be initialized. initializeCalled = true heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) run() if mainExited { // To make sure wasm_exec.js knows that we've exited, exit explicitly. mainReturnExit() } } // This is the _initialize entry point, when using -buildmode=c-shared. func wasmEntryReactor() { // This function is called before any //go:wasmexport functions are called // to initialize everything. It must not block. initializeCalled = true // Initialize the heap. heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) initRand() initHeap() if hasScheduler { // A package initializer might do funky stuff like start a goroutine and // wait until it completes, so we have to run package initializers in a // goroutine. go func() { initAll() }() scheduler(true) } else { // There are no goroutines (except for the main one, if you can call it // that), so we can just run all the package initializers. initAll() } } // This is the _start entry point, when using -buildmode=wasi-legacy. func wasmEntryLegacy() { // These need to be initialized early so that the heap can be initialized. initializeCalled = true heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) run() } // Whether the runtime was initialized by a call to _initialize or _start. var initializeCalled bool func wasmExportCheckRun() { switch { case !initializeCalled: runtimePanic("//go:wasmexport function called before runtime initialization") case mainExited: runtimePanic("//go:wasmexport function called after main.main returned") } } // Called from within a //go:wasmexport wrapper (the one that's exported from // the wasm module) after the goroutine has been queued. Just run the scheduler, // and check that the goroutine finished when the scheduler is idle (as required // by the //go:wasmexport proposal). // // This function is not called when the scheduler is disabled. func wasmExportRun(done *bool) { scheduler(true) if !*done { runtimePanic("//go:wasmexport function did not finish") } } // Called from the goroutine wrapper for the //go:wasmexport function. It just // signals to the runtime that the //go:wasmexport call has finished, and can // switch back to the wasmExportRun function. // // This function is not called when the scheduler is disabled. func wasmExportExit() { // Signal to the scheduler that it should return, since this call to a // //go:wasmexport function has exited. schedulerExit = true task.Pause() // TODO: we could cache the allocated stack so we don't have to keep // allocating a new stack on every //go:wasmexport call. } ================================================ FILE: src/runtime/runtime_windows.go ================================================ package runtime import "unsafe" //export abort func abort() //export exit func libc_exit(code int) //export putchar func libc_putchar(c int) int //export VirtualAlloc func _VirtualAlloc(lpAddress unsafe.Pointer, dwSize uintptr, flAllocationType, flProtect uint32) unsafe.Pointer //export QueryUnbiasedInterruptTime func _QueryUnbiasedInterruptTime(UnbiasedTime *uint64) bool // The parameter is really a LPFILETIME, but *uint64 should be compatible. // //export GetSystemTimeAsFileTime func _GetSystemTimeAsFileTime(lpSystemTimeAsFileTime *uint64) //export LoadLibraryExW func _LoadLibraryExW(lpLibFileName *uint16, hFile uintptr, dwFlags uint32) uintptr //export Sleep func _Sleep(milliseconds uint32) const _LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800 //export GetProcAddress func getProcAddress(handle uintptr, procname *byte) uintptr //export _configure_narrow_argv func _configure_narrow_argv(int32) int32 //export __p___argc func __p___argc() *int32 //export __p___argv func __p___argv() **unsafe.Pointer type startupInfo struct { newMode int32 } //export __getmainargs func __getmainargs(argc *int32, argv, env **unsafe.Pointer, doWildcard int, startInfo *startupInfo) int32 var performanceFrequency int64 //export mainCRTStartup func mainCRTStartup() int { preinit() // Obtain the (constant) performance frequency when needed. if GOARCH == "386" { _QueryPerformanceFrequency(&performanceFrequency) } // Obtain the initial stack pointer right before calling the run() function. // The run function has been moved to a separate (non-inlined) function so // that the correct stack pointer is read. stackTop = getCurrentStackPointer() runMain() // Exit via exit(0) instead of returning. This matches // mingw-w64-crt/crt/crtexe.c, which exits using exit(0) instead of // returning the return value. // Exiting this way (instead of returning) also fixes an issue where not all // output would be sent to stdout before exit. // See: https://github.com/tinygo-org/tinygo/pull/4589 libc_exit(0) // Unreachable, since we've already exited. But we need to return something // here to make this valid Go code. return 0 } // Must be a separate function to get the correct stack pointer. // //go:noinline func runMain() { run() } var args []string //go:linkname os_runtime_args os.runtime_args func os_runtime_args() []string { if args == nil { // Obtain argc/argv from the environment. var argc int32 var argv *unsafe.Pointer if GOARCH == "386" { // MSVCRT.DLL var env *unsafe.Pointer startInfo := startupInfo{newMode: 0} __getmainargs(&argc, &argv, &env, 1, &startInfo) } else { // UCRT _configure_narrow_argv(2) argc = *__p___argc() argv = *__p___argv() } // Make args slice big enough so that it can store all command line // arguments. args = make([]string, argc) // Initialize command line parameters. for i := 0; i < int(argc); i++ { // Convert the C string to a Go string. length := strlen(*argv) arg := (*_string)(unsafe.Pointer(&args[i])) arg.length = length arg.ptr = (*byte)(*argv) // This is the Go equivalent of "argv++" in C. argv = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(argv), unsafe.Sizeof(argv))) } } return args } func putchar(c byte) { libc_putchar(int(c)) } var heapSize uintptr = 128 * 1024 // small amount to start var heapMaxSize uintptr var heapStart, heapEnd uintptr func preinit() { // Allocate a large chunk of virtual memory. Because it is virtual, it won't // really be allocated in RAM. Memory will only be allocated when it is // first touched. heapMaxSize = 1 * 1024 * 1024 * 1024 // 1GB for the entire heap const ( MEM_COMMIT = 0x00001000 MEM_RESERVE = 0x00002000 PAGE_READWRITE = 0x04 ) heapStart = uintptr(_VirtualAlloc(nil, heapMaxSize, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE)) heapEnd = heapStart + heapSize } var stackTop uintptr func ticksToNanoseconds(ticks timeUnit) int64 { // Interrupt time count works in units of 100 nanoseconds. return int64(ticks) * 100 } func nanosecondsToTicks(ns int64) timeUnit { // Interrupt time count works in units of 100 nanoseconds. return timeUnit(ns) / 100 } func sleepTicks(d timeUnit) { // Calculate milliseconds from ticks (which have a resolution of 100ns), // rounding up. milliseconds := int64(d+9_999) / 10_000 for milliseconds != 0 { duration := uint32(milliseconds) _Sleep(duration) milliseconds -= int64(duration) } } //export QueryPerformanceFrequency func _QueryPerformanceFrequency(*int64) bool //export QueryPerformanceCounter func _QueryPerformanceCounter(*int64) bool func ticks() timeUnit { if GOARCH == "386" { // Unfortunately QueryUnbiasedInterruptTime is only available starting // with Windows 7. // Obtain counter (that runs at a fixed frequency). var counter int64 _QueryPerformanceCounter(&counter) // Convert this counter to ticks of 100ns (just like // QueryUnbiasedInterruptTime). // (We could also change the definition of ticks on GOOS=386 but that // seems messy). return timeUnit((counter * 10000000) / performanceFrequency) } else { var unbiasedTime uint64 _QueryUnbiasedInterruptTime(&unbiasedTime) return timeUnit(unbiasedTime) } } //go:linkname now time.now func now() (sec int64, nsec int32, mono int64) { // Get the current time in Windows "file time" format. var time uint64 _GetSystemTimeAsFileTime(&time) // Convert file time to Unix time. // According to the documentation: // > Contains a 64-bit value representing the number of 100-nanosecond // > intervals since January 1, 1601 (UTC). // We'll convert it to 100 nanosecond intervals starting at 1970. const ( // number of 100-nanosecond intervals in a second intervalsPerSecond = 10_000_000 secondsPerDay = 60 * 60 * 24 // Number of days between the Windows epoch (1 january 1601) and the // Unix epoch (1 january 1970). Source: // https://www.wolframalpha.com/input/?i=days+between+1+january+1601+and+1+january+1970 days = 134774 ) time -= days * secondsPerDay * intervalsPerSecond // Convert the time (in 100ns units) to sec/nsec/mono as expected by the // time package. sec = int64(time / intervalsPerSecond) nsec = int32((time - (uint64(sec) * intervalsPerSecond)) * 100) mono = ticksToNanoseconds(ticks()) return } //go:linkname syscall_Exit syscall.Exit func syscall_Exit(code int) { libc_exit(code) } func growHeap() bool { if heapSize == heapMaxSize { // Already at the max. If we run out of memory, we should consider // increasing heapMaxSize.. return false } // Grow the heap size used by the program. heapSize = (heapSize * 4 / 3) &^ 4095 // grow by around 33% if heapSize > heapMaxSize { heapSize = heapMaxSize } setHeapEnd(heapStart + heapSize) return true } //go:linkname syscall_loadsystemlibrary syscall.loadsystemlibrary func syscall_loadsystemlibrary(filename *uint16, absoluteFilepath *uint16) (handle, err uintptr) { handle = _LoadLibraryExW(filename, 0, _LOAD_LIBRARY_SEARCH_SYSTEM32) if handle == 0 { panic("todo: get error") } return } //go:linkname syscall_loadlibrary syscall.loadlibrary func syscall_loadlibrary(filename *uint16) (handle, err uintptr) { panic("todo: syscall.loadlibrary") } //go:linkname syscall_getprocaddress syscall.getprocaddress func syscall_getprocaddress(handle uintptr, procname *byte) (outhandle, err uintptr) { outhandle = getProcAddress(handle, procname) if outhandle == 0 { panic("todo: get error") } return } // TinyGo does not yet support any form of parallelism on Windows, so these can // be left empty. //go:linkname procPin sync/atomic.runtime_procPin func procPin() { } //go:linkname procUnpin sync/atomic.runtime_procUnpin func procUnpin() { } func hardwareRand() (n uint64, ok bool) { // Use the old RtlGenRandom, introduced in Windows XP. // See the rationale in src/crypto/rand/rand_windows.go for why we use this // one. ok = _RtlGenRandom(unsafe.Pointer(&n), 8) return } // This function is part of advapi32.dll, and is called SystemFunction036 for // some reason. It's available on Windows XP and newer. // See: https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom // //export SystemFunction036 func _RtlGenRandom(buf unsafe.Pointer, len int) bool ================================================ FILE: src/runtime/scheduler.go ================================================ package runtime import "internal/task" const schedulerDebug = false var timerQueue *timerNode // Simple logging, for debugging. func scheduleLog(msg string) { if schedulerDebug { println("---", msg) } } // Simple logging with a task pointer, for debugging. func scheduleLogTask(msg string, t *task.Task) { if schedulerDebug { println("---", msg, t) } } // Simple logging with a channel and task pointer. func scheduleLogChan(msg string, ch *channel, t *task.Task) { if schedulerDebug { println("---", msg, ch, t) } } func timerQueueAdd(tn *timerNode) { q := &timerQueue for ; *q != nil; q = &(*q).next { if tn.whenTicks() < (*q).whenTicks() { // this will finish earlier than the next - insert here break } } tn.next = *q *q = tn } func timerQueueRemove(t *timer) *timerNode { for q := &timerQueue; *q != nil; q = &(*q).next { if (*q).timer == t { scheduleLog("removed timer") n := *q *q = (*q).next return n } } scheduleLog("did not remove timer") return nil } // Goexit terminates the currently running goroutine. No other goroutines are affected. func Goexit() { panicOrGoexit(nil, panicGoexit) } //go:linkname fips_getIndicator crypto/internal/fips140.getIndicator func fips_getIndicator() uint8 { return task.Current().FipsIndicator } //go:linkname fips_setIndicator crypto/internal/fips140.setIndicator func fips_setIndicator(indicator uint8) { // This indicator is stored per goroutine. task.Current().FipsIndicator = indicator } ================================================ FILE: src/runtime/scheduler_cooperative.go ================================================ //go:build scheduler.tasks || scheduler.asyncify package runtime // This file implements the TinyGo scheduler. This scheduler is a very simple // cooperative round robin scheduler, with a runqueue that contains a linked // list of goroutines (tasks) that should be run next, in order of when they // were added to the queue (first-in, first-out). It also contains a sleep queue // with sleeping goroutines in order of when they should be re-activated. // // The scheduler is used both for the asyncify based scheduler and for the task // based scheduler. In both cases, the 'internal/task.Task' type is used to represent one // goroutine. import ( "internal/task" "runtime/interrupt" ) // On JavaScript, we can't do a blocking sleep. Instead we have to return and // queue a new scheduler invocation using setTimeout. const asyncScheduler = GOOS == "js" const hasScheduler = true // Concurrency is not parallelism. While the cooperative scheduler has // concurrency, it does not have parallelism. const hasParallelism = false // Set to true after main.main returns. var mainExited bool // Set to true when the scheduler should exit after the next switch to the // scheduler. This is a special case for //go:wasmexport. var schedulerExit bool // Queues used by the scheduler. var ( runqueue task.Queue sleepQueue *task.Task sleepQueueBaseTime timeUnit ) // deadlock is called when a goroutine cannot proceed any more, but is in theory // not exited (so deferred calls won't run). This can happen for example in code // like this, that blocks forever: // // select{} // //go:noinline func deadlock() { // call yield without requesting a wakeup task.Pause() panic("unreachable") } // Add this task to the end of the run queue. func scheduleTask(t *task.Task) { runqueue.Push(t) } func Gosched() { runqueue.Push(task.Current()) task.Pause() } // NumCPU returns the number of logical CPUs usable by the current process. func NumCPU() int { return 1 } // Add this task to the sleep queue, assuming its state is set to sleeping. func addSleepTask(t *task.Task, duration timeUnit) { if schedulerDebug { println(" set sleep:", t, duration) if t.Next != nil { panic("runtime: addSleepTask: expected next task to be nil") } } now := ticks() if sleepQueue == nil { scheduleLog(" -> sleep new queue") // set new base time sleepQueueBaseTime = now } t.Data = uint64(duration + (now - sleepQueueBaseTime)) // Add to sleep queue. q := &sleepQueue for ; *q != nil; q = &(*q).Next { if t.Data < (*q).Data { // this will finish earlier than the next - insert here break } else { // this will finish later - adjust delay t.Data -= (*q).Data } } if *q != nil { // cut delay time between this sleep task and the next (*q).Data -= t.Data } t.Next = *q *q = t } // addTimer adds the given timer node to the timer queue. It must not be in the // queue already. // This function is very similar to addSleepTask but for timerQueue instead of // sleepQueue. func addTimer(tim *timerNode) { mask := interrupt.Disable() timerQueueAdd(tim) interrupt.Restore(mask) } // removeTimer is the implementation of time.stopTimer. It removes a timer from // the timer queue, returning it if the timer is present in the timer queue. func removeTimer(tim *timer) *timerNode { mask := interrupt.Disable() n := timerQueueRemove(tim) interrupt.Restore(mask) return n } func schedulerRunQueue() *task.Queue { return &runqueue } // Run the scheduler until all tasks have finished. // There are a few special cases: // - When returnAtDeadlock is true, it also returns when there are no more // runnable goroutines. // - When using the asyncify scheduler, it returns when it has to wait // (JavaScript uses setTimeout so the scheduler must return to the JS // environment). func scheduler(returnAtDeadlock bool) { // Main scheduler loop. var now timeUnit for !mainExited { scheduleLog("") scheduleLog(" schedule") if sleepQueue != nil || timerQueue != nil { now = ticks() } // Add tasks that are done sleeping to the end of the runqueue so they // will be executed soon. if sleepQueue != nil && now-sleepQueueBaseTime >= timeUnit(sleepQueue.Data) { t := sleepQueue scheduleLogTask(" awake:", t) sleepQueueBaseTime += timeUnit(t.Data) sleepQueue = t.Next t.Next = nil runqueue.Push(t) } // Check for expired timers to trigger. if timerQueue != nil && now >= timerQueue.whenTicks() { scheduleLog("--- timer awoke") delay := ticksToNanoseconds(now - timerQueue.whenTicks()) // Pop timer from queue. tn := timerQueue timerQueue = tn.next tn.next = nil // Run the callback stored in this timer node. tn.callback(tn, delay) } t := runqueue.Pop() if t == nil { if sleepQueue == nil && timerQueue == nil { if returnAtDeadlock { return } if asyncScheduler { // JavaScript is treated specially, see below. return } waitForEvents() continue } var timeLeft timeUnit if sleepQueue != nil { timeLeft = timeUnit(sleepQueue.Data) - (now - sleepQueueBaseTime) } if timerQueue != nil { timeLeftForTimer := timerQueue.whenTicks() - now if sleepQueue == nil || timeLeftForTimer < timeLeft { timeLeft = timeLeftForTimer } } if schedulerDebug { println(" sleeping...", sleepQueue, uint(timeLeft)) for t := sleepQueue; t != nil; t = t.Next { println(" task sleeping:", t, timeUnit(t.Data)) } for tim := timerQueue; tim != nil; tim = tim.next { println("--- timer waiting:", tim, tim.whenTicks()) } } if timeLeft > 0 { sleepTicks(timeLeft) if asyncScheduler { // The sleepTicks function above only sets a timeout at // which point the scheduler will be called again. It does // not really sleep. So instead of sleeping, we return and // expect to be called again. break } } continue } // Run the given task. scheduleLogTask(" run:", t) t.Resume() // The last call to Resume() was a signal to stop the scheduler since a // //go:wasmexport function returned. if GOARCH == "wasm" && schedulerExit { schedulerExit = false // reset the signal return } } } // Pause the current task for a given time. // //go:linkname sleep time.Sleep func sleep(duration int64) { if duration <= 0 { return } addSleepTask(task.Current(), nanosecondsToTicks(duration)) task.Pause() } // run is called by the program entry point to execute the go program. // With a scheduler, init and the main function are invoked in a goroutine before starting the scheduler. func run() { initRand() initHeap() go func() { initAll() callMain() mainExited = true }() scheduler(false) } func lockAtomics() interrupt.State { return interrupt.Disable() } func unlockAtomics(mask interrupt.State) { interrupt.Restore(mask) } func printlock() { // nothing to do } func printunlock() { // nothing to do } ================================================ FILE: src/runtime/scheduler_cores.go ================================================ //go:build scheduler.cores package runtime import ( "internal/task" "runtime/interrupt" "sync/atomic" ) const hasScheduler = true const hasParallelism = true var mainExited atomic.Uint32 // True after the secondary cores have started. var secondaryCoresStarted bool // Which task is running on a given core (or nil if there is no task running on // the core). var cpuTasks [numCPU]*task.Task var ( sleepQueue *task.Task runqueue task.Queue ) func deadlock() { // Call yield without requesting a wakeup. task.Pause() trap() } // Mark the given task as ready to resume. // This is allowed even if the task isn't paused yet, but will pause soon. func scheduleTask(t *task.Task) { schedulerLock.Lock() switch t.RunState { case task.RunStatePaused: // Paused, state is saved on the stack. // Add it to the runqueue... runqueue.Push(t) // ...and wake up a sleeping core, if there is one. // (If all cores are already busy, this is a no-op). schedulerWake() case task.RunStateRunning: // Not yet paused (probably going to pause very soon), so let the // Pause() function know it can resume immediately. t.RunState = task.RunStateResuming default: if schedulerAsserts { runtimePanic("scheduler: unknown run state") } } schedulerLock.Unlock() } func addSleepTask(t *task.Task, wakeup timeUnit) { // Save the timestamp when the task should be woken up. t.Data = uint64(wakeup) // If another core is currently using the timer, make sure it wakes up at // the right time. interruptSleepTicksMulticore(wakeup) // Find the position where we should insert this task in the queue. q := &sleepQueue for { if *q == nil { // Found the end of the time queue. Insert it here, at the end. break } if timeUnit((*q).Data) > timeUnit(t.Data) { // Found a task in the queue that has a timeout before the // to-be-sleeping task. Insert our task right before. break } q = &(*q).Next } // Insert the task into the queue (this could be at the end, if *q is nil). t.Next = *q *q = t } func Gosched() { schedulerLock.Lock() runqueue.Push(task.Current()) task.PauseLocked() } // NumCPU returns the number of CPU cores on this system. func NumCPU() int { return numCPU } func addTimer(tn *timerNode) { schedulerLock.Lock() timerQueueAdd(tn) interruptSleepTicksMulticore(tn.whenTicks()) schedulerLock.Unlock() } func removeTimer(t *timer) *timerNode { schedulerLock.Lock() n := timerQueueRemove(t) schedulerLock.Unlock() return n } func schedulerRunQueue() *task.Queue { return &runqueue } // Pause the current task for a given time. // //go:linkname sleep time.Sleep func sleep(duration int64) { if duration <= 0 { return } wakeup := ticks() + nanosecondsToTicks(duration) // While the scheduler is locked: // - add this task to the sleep queue // - switch to the scheduler (only allowed while locked) // - let the scheduler handle it from there schedulerLock.Lock() addSleepTask(task.Current(), wakeup) task.PauseLocked() } // This function is called on the first core in the system. It will wake up the // other cores when ready. func run() { initRand() initHeap() go func() { // Package initializers are currently run single-threaded. // This might help with registering interrupts and such. initAll() // After package initializers have finished, start all the other cores. startSecondaryCores() secondaryCoresStarted = true // Run main.main. callMain() // main.main has exited, so the program should exit. mainExited.Store(1) }() // The scheduler must always be entered while the scheduler lock is taken. schedulerLock.Lock() scheduler(false) schedulerLock.Unlock() } func scheduler(_ bool) { for mainExited.Load() == 0 { // Check for ready-to-run tasks. if runnable := runqueue.Pop(); runnable != nil { // Resume it now. setCurrentTask(runnable) runnable.RunState = task.RunStateRunning schedulerLock.Unlock() // unlock before resuming, Pause() will lock again runnable.Resume() setCurrentTask(nil) continue } var now timeUnit if sleepQueue != nil || timerQueue != nil { now = ticks() // Check whether the first task in the sleep queue is ready to run. if sleepingTask := sleepQueue; sleepingTask != nil && now >= timeUnit(sleepingTask.Data) { // It is, pop it from the queue. sleepQueue = sleepQueue.Next sleepingTask.Next = nil // Run it now. setCurrentTask(sleepingTask) sleepingTask.RunState = task.RunStateRunning schedulerLock.Unlock() // unlock before resuming, Pause() will lock again sleepingTask.Resume() setCurrentTask(nil) continue } // Check whether a timer has expired that needs to be run. if timerQueue != nil && now >= timerQueue.whenTicks() { delay := ticksToNanoseconds(now - timerQueue.whenTicks()) // Pop timer from queue. tn := timerQueue timerQueue = tn.next tn.next = nil // Run the callback stored in this timer node. schedulerLock.Unlock() tn.callback(tn, delay) schedulerLock.Lock() continue } } // At this point, there are no runnable tasks anymore. // If another core is using the clock, let it handle the sleep queue. if hasSleepingCore() { schedulerUnlockAndWait() continue } // The timer is free to use, so check whether there are any future // tasks/timers that we can wait for. var timeLeft timeUnit if sleepingTask := sleepQueue; sleepingTask != nil { // We already checked that there is no ready-to-run sleeping task // (using the same 'now' value), so timeLeft will always be // positive. timeLeft = timeUnit(sleepingTask.Data) - now } if timerQueue != nil { // If the timer queue needs to run earlier, reduce the time we are // going to sleep. // Like with sleepQueue, we already know there is no timer ready to // run since we already checked above. timeLeftForTimer := timerQueue.whenTicks() - now if sleepQueue == nil || timeLeftForTimer < timeLeft { timeLeft = timeLeftForTimer } } if timeLeft > 0 { // Sleep for a bit until the next task or timer is ready to run. sleepTicksMulticore(timeLeft) continue } // No runnable tasks and no sleeping tasks or timers. There's nothing to // do. // Wait until something happens (like an interrupt). schedulerUnlockAndWait() } } func currentTask() *task.Task { return cpuTasks[currentCPU()] } func setCurrentTask(task *task.Task) { cpuTasks[currentCPU()] = task } func lockScheduler() { schedulerLock.Lock() } func unlockScheduler() { schedulerLock.Unlock() } func lockFutex() interrupt.State { mask := interrupt.Disable() futexLock.Lock() return mask } func unlockFutex(state interrupt.State) { futexLock.Unlock() interrupt.Restore(state) } // Use a single spinlock for atomics. This works fine, since atomics are very // short sequences of instructions. func lockAtomics() interrupt.State { mask := interrupt.Disable() atomicsLock.Lock() return mask } func unlockAtomics(mask interrupt.State) { atomicsLock.Unlock() interrupt.Restore(mask) } var systemStack [numCPU]uintptr // Implementation detail of the internal/task package. // It needs to store the system stack pointer somewhere, and needs to know how // many cores there are to do so. But it doesn't know the number of cores. Hence // why this is implemented in the runtime. func systemStackPtr() *uintptr { return &systemStack[currentCPU()] } // Color the 'print' and 'println' output according to the current CPU. // This may be helpful for debugging, but should be disabled otherwise. const cpuColoredPrint = false func printlock() { // Don't lock the print output inside an interrupt. // Locking the print output inside an interrupt can lead to a deadlock: if // the interrupt happens while the print lock is held, the interrupt won't // be able to take this lock anymore. // This isn't great, but the alternative would be to disable interrupts // while printing which seems like a worse idea to me. if !interrupt.In() { printLock.Lock() } if cpuColoredPrint { switch currentCPU() { case 1: printstring("\x1b[32m") // green case 2: printstring("\x1b[33m") // yellow case 3: printstring("\x1b[34m") // blue } } } func printunlock() { if cpuColoredPrint { if currentCPU() != 0 { printstring("\x1b[0m") // reset colored output } } if !interrupt.In() { printLock.Unlock() } } ================================================ FILE: src/runtime/scheduler_none.go ================================================ //go:build scheduler.none package runtime import ( "internal/task" "runtime/interrupt" ) const hasScheduler = false // No goroutines are allowed, so there's no parallelism anywhere. const hasParallelism = false // Set to true after main.main returns. var mainExited bool // dummy flag, not used without scheduler var schedulerExit bool // run is called by the program entry point to execute the go program. // With the "none" scheduler, init and the main function are invoked directly. func run() { initRand() initHeap() initAll() callMain() mainExited = true } //go:linkname sleep time.Sleep func sleep(duration int64) { if duration <= 0 { return } sleepTicks(nanosecondsToTicks(duration)) } func deadlock() { // The only goroutine available is deadlocked. runtimePanic("all goroutines are asleep - deadlock!") } func scheduleTask(t *task.Task) { // Pause() will panic, so this should not be reachable. } func Gosched() { // There are no other goroutines, so there's nothing to schedule. } // NumCPU returns the number of logical CPUs usable by the current process. func NumCPU() int { return 1 } func addTimer(tim *timerNode) { runtimePanic("timers not supported without a scheduler") } func removeTimer(tim *timer) *timerNode { runtimePanic("timers not supported without a scheduler") return nil } func schedulerRunQueue() *task.Queue { // This function is not actually used, it is only called when hasScheduler // is true. runtimePanic("unreachable: no runqueue without a scheduler") return nil } func scheduler(returnAtDeadlock bool) { // The scheduler should never be run when using -scheduler=none. Meaning, // this code should be unreachable. runtimePanic("unreachable: scheduler must not be called with the 'none' scheduler") } func lockAtomics() interrupt.State { return interrupt.Disable() } func unlockAtomics(mask interrupt.State) { interrupt.Restore(mask) } func printlock() { // nothing to do } func printunlock() { // nothing to do } ================================================ FILE: src/runtime/scheduler_tasks.go ================================================ //go:build scheduler.tasks package runtime var systemStack uintptr // Implementation detail of the internal/task package. // It needs to store the system stack pointer somewhere, and needs to know how // many cores there are to do so. But it doesn't know the number of cores. Hence // why this is implemented in the runtime. func systemStackPtr() *uintptr { return &systemStack } ================================================ FILE: src/runtime/scheduler_threads.go ================================================ //go:build scheduler.threads package runtime import ( "internal/task" "runtime/interrupt" ) const hasScheduler = false // not using the cooperative scheduler // We use threads, so yes there is parallelism. const hasParallelism = true var ( timerQueueLock task.PMutex timerQueueStarted bool timerFutex task.Futex ) // Because we just use OS threads, we don't need to do anything special here. We // can just initialize everything and run main.main on the main thread. func run() { initRand() initHeap() task.Init(stackTop) initAll() callMain() } // Pause the current task for a given time. // //go:linkname sleep time.Sleep func sleep(duration int64) { if duration <= 0 { return } sleepTicks(nanosecondsToTicks(duration)) } func deadlock() { // TODO: exit the thread via pthread_exit. task.Pause() } func scheduleTask(t *task.Task) { t.Resume() } func Gosched() { // Each goroutine runs in a thread, so there's not much we can do here. // There is sched_yield but it's only really intended for realtime // operation, so is probably best not to use. } // NumCPU returns the number of logical CPUs usable by the current process. func NumCPU() int { return task.NumCPU() } // Separate goroutine (thread) that runs timer callbacks when they expire. func timerRunner() { for { timerQueueLock.Lock() if timerQueue == nil { // No timer in the queue, so wait until one becomes available. val := timerFutex.Load() timerQueueLock.Unlock() timerFutex.Wait(val) continue } now := ticks() if now < timerQueue.whenTicks() { // There is a timer in the queue, but we need to wait until it // expires. // Using a futex, so that the wait is exited early when adding a new // (sooner-to-expire) timer. val := timerFutex.Load() timerQueueLock.Unlock() timeout := ticksToNanoseconds(timerQueue.whenTicks() - now) timerFutex.WaitUntil(val, uint64(timeout)) continue } // Pop timer from queue. tn := timerQueue timerQueue = tn.next tn.next = nil timerQueueLock.Unlock() // Run the callback stored in this timer node. delay := ticksToNanoseconds(now - tn.whenTicks()) tn.callback(tn, delay) } } func addTimer(tim *timerNode) { timerQueueLock.Lock() if !timerQueueStarted { timerQueueStarted = true go timerRunner() } timerQueueAdd(tim) timerFutex.Add(1) timerFutex.Wake() timerQueueLock.Unlock() } func removeTimer(tim *timer) *timerNode { timerQueueLock.Lock() n := timerQueueRemove(tim) timerQueueLock.Unlock() return n } func schedulerRunQueue() *task.Queue { // This function is not actually used, it is only called when hasScheduler // is true. So we can just return nil here. return nil } func runqueueForGC() *task.Queue { // There is only a runqueue when using the cooperative scheduler. return nil } // Lock to make sure print calls do not interleave. var printLock task.Mutex func printlock() { printLock.Lock() } func printunlock() { printLock.Unlock() } // The atomics lock isn't used as a lock for actual atomics. It is used inside // internal/task.Stack and internal/task.Queue to make sure their operations are // actually atomic. (This might not actually be needed, since the use in // sync.Cond doesn't need atomicity). var atomicsLock task.Mutex func lockAtomics() interrupt.State { atomicsLock.Lock() return 0 } func unlockAtomics(mask interrupt.State) { atomicsLock.Unlock() } ================================================ FILE: src/runtime/signal.c ================================================ //go:build none // Ignore the //go:build above. This file is manually included on Linux and // MacOS to provide os/signal support. #include #include #include #include // Signal handler in the runtime. void tinygo_signal_handler(int sig); // Enable a signal from the runtime. void tinygo_signal_enable(uint32_t sig) { struct sigaction act = { 0 }; act.sa_handler = &tinygo_signal_handler; act.sa_flags = SA_RESTART; sigaction(sig, &act, NULL); } void tinygo_signal_ignore(uint32_t sig) { struct sigaction act = { 0 }; act.sa_handler = SIG_IGN; sigaction(sig, &act, NULL); } void tinygo_signal_disable(uint32_t sig) { struct sigaction act = { 0 }; act.sa_handler = SIG_DFL; sigaction(sig, &act, NULL); } ================================================ FILE: src/runtime/signalstub.go ================================================ //go:build tinygo.wasm || baremetal package runtime // Some platforms don't support Unix signals (and never will), so we need to // stub the signal functions. //go:linkname signal_disable os/signal.signal_disable func signal_disable(uint32) {} //go:linkname signal_enable os/signal.signal_enable func signal_enable(uint32) {} //go:linkname signal_ignore os/signal.signal_ignore func signal_ignore(uint32) {} //go:linkname signal_waitUntilIdle os/signal.signalWaitUntilIdle func signal_waitUntilIdle() {} //go:linkname signal_recv os/signal.signal_recv func signal_recv() uint32 { return ^uint32(0) } ================================================ FILE: src/runtime/slice.go ================================================ package runtime // This file implements compiler builtins for slices: append() and copy(). import ( "internal/gclayout" "math/bits" "unsafe" ) // Builtin append(src, elements...) function: append elements to src and return // the modified (possibly expanded) slice. func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) { newLen := srcLen + elemsLen if elemsLen > 0 { // Allocate a new slice with capacity for elemsLen more elements, if necessary; // otherwise, reuse the passed slice. srcBuf, _, srcCap = sliceGrow(srcBuf, srcLen, srcCap, newLen, elemSize) // Append the new elements in-place. memmove(unsafe.Add(srcBuf, srcLen*elemSize), elemsBuf, elemsLen*elemSize) } return srcBuf, newLen, srcCap } // Builtin copy(dst, src) function: copy bytes from dst to src. func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr) int { // n = min(srcLen, dstLen) n := srcLen if n > dstLen { n = dstLen } memmove(dst, src, n*elemSize) return int(n) } // sliceGrow returns a new slice with space for at least newCap elements func sliceGrow(oldBuf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) { if oldCap >= newCap { // No need to grow, return the input slice. return oldBuf, oldLen, oldCap } // This can be made more memory-efficient by multiplying by some other constant, such as 1.5, // which seems to be allowed by the Go language specification (but this can be observed by // programs); however, due to memory fragmentation and the current state of the TinyGo // memory allocators, this causes some difficult to debug issues. newCap = 1 << bits.Len(uint(newCap)) var layout unsafe.Pointer // less type info here; can only go off element size if elemSize < unsafe.Sizeof(uintptr(0)) { layout = gclayout.NoPtrs.AsPtr() } buf := alloc(newCap*elemSize, layout) if oldLen > 0 { // copy any data to new slice memmove(buf, oldBuf, oldLen*elemSize) } return buf, oldLen, newCap } ================================================ FILE: src/runtime/stack.go ================================================ package runtime type Func struct { } func FuncForPC(pc uintptr) *Func { return nil } func (f *Func) Name() string { return "" } func (f *Func) FileLine(pc uintptr) (file string, line int) { return "", 0 } func Caller(skip int) (pc uintptr, file string, line int, ok bool) { return 0, "", 0, false } func Stack(buf []byte, all bool) int { return 0 } ================================================ FILE: src/runtime/string.go ================================================ package runtime // This file implements functions related to Go strings. import ( "internal/gclayout" "unsafe" ) // The underlying struct for the Go string type. type _string struct { ptr *byte length uintptr } // The iterator state for a range over a string. type stringIterator struct { byteindex uintptr } // Return true iff the strings match. // //go:nobounds func stringEqual(x, y string) bool { if len(x) != len(y) { return false } for i := 0; i < len(x); i++ { if x[i] != y[i] { return false } } return true } // Return true iff x < y. // //go:nobounds func stringLess(x, y string) bool { l := len(x) if m := len(y); m < l { l = m } for i := 0; i < l; i++ { if x[i] < y[i] { return true } if x[i] > y[i] { return false } } return len(x) < len(y) } // Add two strings together. func stringConcat(x, y _string) _string { if x.length == 0 { return y } else if y.length == 0 { return x } else { length := x.length + y.length buf := alloc(length, gclayout.NoPtrs.AsPtr()) memcpy(buf, unsafe.Pointer(x.ptr), x.length) memcpy(unsafe.Add(buf, x.length), unsafe.Pointer(y.ptr), y.length) return _string{ptr: (*byte)(buf), length: length} } } // Create a string from a []byte slice. func stringFromBytes(x struct { ptr *byte len uintptr cap uintptr }) _string { buf := alloc(x.len, gclayout.NoPtrs.AsPtr()) memcpy(buf, unsafe.Pointer(x.ptr), x.len) return _string{ptr: (*byte)(buf), length: x.len} } // Convert a string to a []byte slice. func stringToBytes(x _string) (slice struct { ptr *byte len uintptr cap uintptr }) { buf := alloc(x.length, gclayout.NoPtrs.AsPtr()) memcpy(buf, unsafe.Pointer(x.ptr), x.length) slice.ptr = (*byte)(buf) slice.len = x.length slice.cap = x.length return } // Convert a []rune slice to a string. func stringFromRunes(runeSlice []rune) (s _string) { // Count the number of characters that will be in the string. for _, r := range runeSlice { _, numBytes := encodeUTF8(r) s.length += numBytes } // Allocate memory for the string. s.ptr = (*byte)(alloc(s.length, gclayout.NoPtrs.AsPtr())) // Encode runes to UTF-8 and store the resulting bytes in the string. index := uintptr(0) for _, r := range runeSlice { array, numBytes := encodeUTF8(r) for _, c := range array[:numBytes] { *(*byte)(unsafe.Add(unsafe.Pointer(s.ptr), index)) = c index++ } } return } // Convert a string to []rune slice. func stringToRunes(s string) []rune { var n = 0 for range s { n++ } var r = make([]rune, n) n = 0 for _, e := range s { r[n] = e n++ } return r } // Create a string from a Unicode code point. func stringFromUnicode(x rune) _string { array, length := encodeUTF8(x) // Array will be heap allocated. // The heap most likely doesn't work with blocks below 4 bytes, so there's // no point in allocating a smaller buffer for the string here. return _string{ptr: (*byte)(unsafe.Pointer(&array)), length: length} } // Iterate over a string. // Returns (ok, key, value). func stringNext(s string, it *stringIterator) (bool, int, rune) { if len(s) <= int(it.byteindex) { return false, 0, 0 } i := int(it.byteindex) r, length := decodeUTF8(s, it.byteindex) it.byteindex += length return true, i, r } // Convert a Unicode code point into an array of bytes and its length. func encodeUTF8(x rune) ([4]byte, uintptr) { // https://stackoverflow.com/questions/6240055/manually-converting-unicode-codepoints-into-utf-8-and-utf-16 // Note: this code can probably be optimized (in size and speed). switch { case x <= 0x7f: return [4]byte{byte(x), 0, 0, 0}, 1 case x <= 0x7ff: b1 := 0xc0 | byte(x>>6) b2 := 0x80 | byte(x&0x3f) return [4]byte{b1, b2, 0, 0}, 2 case 0xd800 <= x && x <= 0xdfff: // utf-16 surrogates are replaced with "invalid code point" return [4]byte{0xef, 0xbf, 0xbd, 0}, 3 case x <= 0xffff: b1 := 0xe0 | byte(x>>12) b2 := 0x80 | byte((x>>6)&0x3f) b3 := 0x80 | byte((x>>0)&0x3f) return [4]byte{b1, b2, b3, 0}, 3 case x <= 0x10ffff: b1 := 0xf0 | byte(x>>18) b2 := 0x80 | byte((x>>12)&0x3f) b3 := 0x80 | byte((x>>6)&0x3f) b4 := 0x80 | byte((x>>0)&0x3f) return [4]byte{b1, b2, b3, b4}, 4 default: // Invalid Unicode code point. return [4]byte{0xef, 0xbf, 0xbd, 0}, 3 } } // Decode a single UTF-8 character from a string. // //go:nobounds func decodeUTF8(s string, index uintptr) (rune, uintptr) { remaining := uintptr(len(s)) - index // must be >= 1 before calling this function x := s[index] switch { case x&0x80 == 0x00: // 0xxxxxxx return rune(x), 1 case x&0xe0 == 0xc0: // 110xxxxx if remaining < 2 || !isContinuation(s[index+1]) { return 0xfffd, 1 } r := (rune(x&0x1f) << 6) | (rune(s[index+1]) & 0x3f) if r >= 1<<7 { // Check whether the rune really needed to be encoded as a two-byte // sequence. UTF-8 requires every rune to be encoded in the smallest // sequence possible. return r, 2 } case x&0xf0 == 0xe0: // 1110xxxx if remaining < 3 || !isContinuation(s[index+1]) || !isContinuation(s[index+2]) { return 0xfffd, 1 } r := (rune(x&0x0f) << 12) | ((rune(s[index+1]) & 0x3f) << 6) | (rune(s[index+2]) & 0x3f) if r >= 1<<11 && !(r >= 0xD800 && r <= 0xDFFF) { // Check whether the rune really needed to be encoded as a // three-byte sequence and check that this is not a Unicode // surrogate pair (which are not allowed by UTF-8). return r, 3 } case x&0xf8 == 0xf0: // 11110xxx if remaining < 4 || !isContinuation(s[index+1]) || !isContinuation(s[index+2]) || !isContinuation(s[index+3]) { return 0xfffd, 1 } r := (rune(x&0x07) << 18) | ((rune(s[index+1]) & 0x3f) << 12) | ((rune(s[index+2]) & 0x3f) << 6) | (rune(s[index+3]) & 0x3f) if r >= 1<<16 && r <= '\U0010FFFF' { // Check whether this rune really needed to be encoded as a four // byte sequence and check that the resulting rune is in the valid // range (up to at most U+10FFFF). return r, 4 } } // Failed to decode. Return the Unicode replacement character and a length of 1. return 0xfffd, 1 } // isContinuation returns true if (and only if) this is a UTF-8 continuation // byte. func isContinuation(b byte) bool { // Continuation bytes have their topmost bits set to 0b10. return b&0xc0 == 0x80 } // Functions used in CGo. // Convert a Go string to a C string. func cgo_CString(s _string) unsafe.Pointer { buf := malloc(s.length + 1) memcpy(buf, unsafe.Pointer(s.ptr), s.length) *(*byte)(unsafe.Add(buf, s.length)) = 0 // trailing 0 byte return buf } // Convert a C string to a Go string. func cgo_GoString(cstr unsafe.Pointer) _string { if cstr == nil { return _string{} } return makeGoString(cstr, strlen(cstr)) } // Convert a C data buffer to a Go string (that possibly contains 0 bytes). func cgo_GoStringN(cstr unsafe.Pointer, length uintptr) _string { return makeGoString(cstr, length) } // Make a Go string given a source buffer and a length. func makeGoString(cstr unsafe.Pointer, length uintptr) _string { s := _string{ length: length, } if s.length != 0 { buf := make([]byte, s.length) s.ptr = &buf[0] memcpy(unsafe.Pointer(s.ptr), cstr, s.length) } return s } // Convert a C data buffer to a Go byte slice. func cgo_GoBytes(ptr unsafe.Pointer, length uintptr) []byte { // Note: don't return nil if length is 0, to match the behavior of C.GoBytes // of upstream Go. buf := make([]byte, length) if length != 0 { memcpy(unsafe.Pointer(&buf[0]), ptr, uintptr(length)) } return buf } func cgo_CBytes(b []byte) unsafe.Pointer { p := malloc(uintptr(len(b))) s := unsafe.Slice((*byte)(p), len(b)) copy(s, b) return p } ================================================ FILE: src/runtime/symtab.go ================================================ package runtime type Frames struct { // } type Frame struct { PC uintptr Func *Func Function string File string Line int Entry uintptr } func CallersFrames(callers []uintptr) *Frames { return nil } func (ci *Frames) Next() (frame Frame, more bool) { return Frame{}, false } ================================================ FILE: src/runtime/sync.go ================================================ package runtime // This file contains stub implementations for internal/poll. //go:linkname semacquire internal/poll.runtime_Semacquire func semacquire(sema *uint32) { panic("todo: semacquire") } //go:linkname semrelease internal/poll.runtime_Semrelease func semrelease(sema *uint32) { panic("todo: semrelease") } ================================================ FILE: src/runtime/synctest.go ================================================ package runtime // Dummy implementation of synctest functions (we don't support synctest at the // moment). //go:linkname synctest_acquire internal/synctest.acquire func synctest_acquire() any { // Dummy: we don't support synctest. return nil } //go:linkname synctest_release internal/synctest.release func synctest_release(sg any) { // Dummy: we don't support synctest. } ================================================ FILE: src/runtime/time.go ================================================ package runtime //go:linkname time_runtimeNano time.runtimeNano func time_runtimeNano() int64 { // Note: we're ignoring sync groups here (package testing/synctest). // See: https://github.com/golang/go/issues/67434 return nanotime() } //go:linkname time_runtimeNow time.runtimeNow func time_runtimeNow() (sec int64, nsec int32, mono int64) { // Also ignoring the sync group here, like time_runtimeNano above. return now() } // timerNode is an element in a linked list of timers. type timerNode struct { next *timerNode timer *timer callback func(node *timerNode, delta int64) } // whenTicks returns the (absolute) time when this timer should trigger next. func (t *timerNode) whenTicks() timeUnit { return nanosecondsToTicks(t.timer.when) } // timerCallback is called when a timer expires. It makes sure to call the // callback in the time package and to re-add the timer to the queue if this is // a ticker (repeating timer). // This is intentionally used as a callback and not a direct call (even though a // direct call would be trivial), because otherwise a circular dependency // between scheduler, addTimer and timerQueue would form. Such a circular // dependency causes timerQueue not to get optimized away. // If timerQueue doesn't get optimized away, small programs (that don't call // time.NewTimer etc) would still pay the cost of these timers. func timerCallback(tn *timerNode, delta int64) { // Run timer function (implemented in the time package). // The seq parameter to the f function is not used in the time // package so is left zero. tn.timer.callCallback(delta) // If this is a periodic timer (a ticker), re-add it to the queue. if tn.timer.period != 0 { tn.timer.when += tn.timer.period addTimer(tn) } } //go:linkname time_runtimeIsBubbled time.runtimeIsBubbled func time_runtimeIsBubbled() bool { // We don't currently support bubbles. return false } ================================================ FILE: src/runtime/time_go122.go ================================================ //go:build !go1.23 // Portions copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package runtime // Time functions for Go 1.22 and below. type puintptr uintptr // Package time knows the layout of this structure. // If this struct changes, adjust ../time/sleep.go:/runtimeTimer. type timer struct { // If this timer is on a heap, which P's heap it is on. // puintptr rather than *p to match uintptr in the versions // of this struct defined in other packages. pp puintptr // Timer wakes up at when, and then at when+period, ... (period > 0 only) // each time calling f(arg, now) in the timer goroutine, so f must be // a well-behaved function and not block. // // when must be positive on an active timer. when int64 period int64 f func(any, uintptr) arg any seq uintptr // What to set the when field to in timerModifiedXX status. nextwhen int64 // The status field holds one of the values below. status uint32 } func (tim *timer) callCallback(delta int64) { tim.f(tim.arg, 0) } // Defined in the time package, implemented here in the runtime. // //go:linkname startTimer time.startTimer func startTimer(tim *timer) { addTimer(&timerNode{ timer: tim, callback: timerCallback, }) scheduleLog("adding timer") } //go:linkname stopTimer time.stopTimer func stopTimer(tim *timer) bool { return removeTimer(tim) != nil } //go:linkname resetTimer time.resetTimer func resetTimer(tim *timer, when int64) bool { tim.when = when n := removeTimer(tim) startTimer(tim) return n != nil } ================================================ FILE: src/runtime/time_go123.go ================================================ //go:build go1.23 package runtime import "unsafe" // Time functions for Go 1.23 and above. // This is the timer that's used internally inside the runtime. type timer struct { // When to call the timer, and the interval for the ticker. when int64 period int64 // Callback from the time package. f func(arg any, seq uintptr, delta int64) arg any } func (tim *timer) callCallback(delta int64) { tim.f(tim.arg, 0, delta) } // This is the struct used internally in the runtime. The first two fields are // the same as time.Timer and time.Ticker so it can be used as-is in the time // package. type timeTimer struct { c unsafe.Pointer // <-chan time.Time init bool timer } //go:linkname newTimer time.newTimer func newTimer(when, period int64, f func(arg any, seq uintptr, delta int64), arg any, c unsafe.Pointer) *timeTimer { tim := &timeTimer{ c: c, init: true, timer: timer{ when: when, period: period, f: f, arg: arg, }, } scheduleLog("new timer") addTimer(&timerNode{ timer: &tim.timer, callback: timerCallback, }) return tim } //go:linkname stopTimer time.stopTimer func stopTimer(tim *timeTimer) bool { return removeTimer(&tim.timer) != nil } //go:linkname resetTimer time.resetTimer func resetTimer(t *timeTimer, when, period int64) bool { t.timer.when = when t.timer.period = period n := removeTimer(&t.timer) removed := n != nil if n == nil { n = new(timerNode) } n.timer = &t.timer n.callback = timerCallback addTimer(n) return removed } ================================================ FILE: src/runtime/time_nxpmk66f18.go ================================================ // Derivative work of Teensyduino Core Library // http://www.pjrc.com/teensy/ // Copyright (c) 2017 PJRC.COM, LLC. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // 1. The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // 2. If the Software is incorporated into a build system that allows // selection among a list of target devices, then similar target // devices manufactured by PJRC.COM must be included in the list of // target devices and selectable in the same manner. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS // BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. //go:build nxp && mk66f18 package runtime import ( "device/arm" "device/nxp" "machine" "runtime/interrupt" "runtime/volatile" ) func ticksToNanoseconds(ticks timeUnit) int64 { return int64(ticks) * 1000 } func nanosecondsToTicks(ns int64) timeUnit { return timeUnit(ns / 1000) } // cyclesPerMilli-1 is used for the systick reset value. // The systick current value will be decremented on every clock cycle. // An interrupt is generated when the current value reaches 0. // A value of freq/1000 generates a tick (irq) every millisecond (1/1000 s). var cyclesPerMilli = machine.CPUFrequency() / 1000 // number of systick irqs (milliseconds) since boot var systickCount volatile.Register64 func millisSinceBoot() uint64 { return systickCount.Get() } func initSysTick() { nxp.SysTick.RVR.Set(cyclesPerMilli - 1) nxp.SysTick.CVR.Set(0) nxp.SysTick.CSR.Set(nxp.SysTick_CSR_CLKSOURCE | nxp.SysTick_CSR_TICKINT | nxp.SysTick_CSR_ENABLE) nxp.SystemControl.SHPR3.Set((32 << nxp.SystemControl_SHPR3_PRI_15_Pos) | (32 << nxp.SystemControl_SHPR3_PRI_14_Pos)) // set systick and pendsv priority to 32 } func initSleepTimer() { nxp.SIM.SCGC5.SetBits(nxp.SIM_SCGC5_LPTMR) nxp.LPTMR0.CSR.Set(nxp.LPTMR0_CSR_TIE) timerInterrupt = interrupt.New(nxp.IRQ_LPTMR0, timerWake) timerInterrupt.Enable() } //go:export SysTick_Handler func tick() { systickCount.Set(systickCount.Get() + 1) } // ticks are in microseconds func ticks() timeUnit { mask := arm.DisableInterrupts() current := nxp.SysTick.CVR.Get() // current value of the systick counter count := millisSinceBoot() // number of milliseconds since boot istatus := nxp.SystemControl.ICSR.Get() // interrupt status register arm.EnableInterrupts(mask) micros := timeUnit(count * 1000) // a tick (1ms) = 1000 us // if the systick counter was about to reset and ICSR indicates a pending systick irq, increment count if istatus&nxp.SystemControl_ICSR_PENDSTSET != 0 && current > 50 { micros += 1000 } else { cycles := cyclesPerMilli - 1 - current // number of cycles since last 1ms tick cyclesPerMicro := machine.CPUFrequency() / 1000000 micros += timeUnit(cycles / cyclesPerMicro) } return micros } // sleepTicks spins for a number of microseconds func sleepTicks(duration timeUnit) { now := ticks() end := duration + now cyclesPerMicro := machine.ClockFrequency() / 1000000 if duration <= 0 { return } nxp.LPTMR0.PSR.Set((3 << nxp.LPTMR0_PSR_PCS_Pos) | nxp.LPTMR0_PSR_PBYP) // use 16MHz clock, undivided for now < end { count := uint32(end-now) / cyclesPerMicro if count > 65535 { count = 65535 } if !timerSleep(count) { // return early due to interrupt return } now = ticks() } } var timerInterrupt interrupt.Interrupt var timerActive volatile.Register32 func timerSleep(count uint32) bool { timerActive.Set(1) nxp.LPTMR0.CMR.Set(count) // set count nxp.LPTMR0.CSR.SetBits(nxp.LPTMR0_CSR_TEN) // enable for { arm.Asm("wfi") if timerActive.Get() == 0 { return true } if hasScheduler { // bail out, as the interrupt may have awoken a goroutine break } // if there is no scheduler, block for the entire count } timerWake(timerInterrupt) return false } func timerWake(interrupt.Interrupt) { timerActive.Set(0) nxp.LPTMR0.CSR.Set(nxp.LPTMR0.CSR.Get()&^nxp.LPTMR0_CSR_TEN | nxp.LPTMR0_CSR_TCF) // clear flag and disable } ================================================ FILE: src/runtime/trace/trace.go ================================================ // Stubs for the runtime/trace package package trace import ( "context" "errors" "io" ) func Start(w io.Writer) error { return errors.New("not implemented") } func Stop() {} func NewTask(pctx context.Context, taskType string) (ctx context.Context, task *Task) { return context.TODO(), nil } type Task struct{} func (t *Task) End() {} func Log(ctx context.Context, category, message string) {} func Logf(ctx context.Context, category, format string, args ...any) {} func WithRegion(ctx context.Context, regionType string, fn func()) { fn() } func StartRegion(ctx context.Context, regionType string) *Region { return nil } type Region struct{} func (r *Region) End() {} func IsEnabled() bool { return false } ================================================ FILE: src/runtime/volatile/bitband_nxpmk66f18.go ================================================ //go:build nxp && mk66f18 package volatile import "unsafe" const registerBase = 0x40000000 const registerEnd = 0x40100000 const bitbandBase = 0x42000000 //go:inline func bitbandAddress(reg uintptr, bit uint8) uintptr { if uintptr(bit) > 32 { panic("invalid bit position") } if reg < registerBase || reg >= registerEnd { panic("register is out of range") } return (reg-registerBase)*32 + uintptr(bit)*4 + bitbandBase } // Special types that causes loads/stores to be volatile (necessary for // memory-mapped registers). type BitRegister struct { Reg uint32 } // Get returns the of the mapped register bit. It is the volatile equivalent of: // // *r.Reg // //go:inline func (r *BitRegister) Get() bool { return LoadUint32(&r.Reg) != 0 } // Set sets the mapped register bit. It is the volatile equivalent of: // // *r.Reg = 1 // //go:inline func (r *BitRegister) Set(v bool) { var i uint32 if v { i = 1 } StoreUint32(&r.Reg, i) } // Bit maps bit N of register R to the corresponding bitband address. Bit panics // if R is not an AIPS or GPIO register or if N is out of range (greater than // the number of bits in a register minus one). // //go:inline func (r *Register8) Bit(bit uint8) *BitRegister { ptr := bitbandAddress(uintptr(unsafe.Pointer(&r.Reg)), bit) return (*BitRegister)(unsafe.Pointer(ptr)) } // Bit maps bit N of register R to the corresponding bitband address. Bit panics // if R is not an AIPS or GPIO register or if N is out of range (greater than // the number of bits in a register minus one). // //go:inline func (r *Register16) Bit(bit uint8) *BitRegister { ptr := bitbandAddress(uintptr(unsafe.Pointer(&r.Reg)), bit) return (*BitRegister)(unsafe.Pointer(ptr)) } // Bit maps bit N of register R to the corresponding bitband address. Bit panics // if R is not an AIPS or GPIO register or if N is out of range (greater than // the number of bits in a register minus one). // //go:inline func (r *Register32) Bit(bit uint8) *BitRegister { ptr := bitbandAddress(uintptr(unsafe.Pointer(&r.Reg)), bit) return (*BitRegister)(unsafe.Pointer(ptr)) } ================================================ FILE: src/runtime/volatile/register.go ================================================ package volatile // This file defines Register{8,16,32,64} types, which are convenience types for // volatile register accesses. // Special types that causes loads/stores to be volatile (necessary for // memory-mapped registers). type Register8 struct { Reg uint8 } // Get returns the value in the register. It is the volatile equivalent of: // // *r.Reg // //go:inline func (r *Register8) Get() uint8 { return LoadUint8(&r.Reg) } // Set updates the register value. It is the volatile equivalent of: // // *r.Reg = value // //go:inline func (r *Register8) Set(value uint8) { StoreUint8(&r.Reg, value) } // SetBits reads the register, sets the given bits, and writes it back. It is // the volatile equivalent of: // // r.Reg |= value // //go:inline func (r *Register8) SetBits(value uint8) { StoreUint8(&r.Reg, LoadUint8(&r.Reg)|value) } // ClearBits reads the register, clears the given bits, and writes it back. It // is the volatile equivalent of: // // r.Reg &^= value // //go:inline func (r *Register8) ClearBits(value uint8) { StoreUint8(&r.Reg, LoadUint8(&r.Reg)&^value) } // HasBits reads the register and then checks to see if the passed bits are set. It // is the volatile equivalent of: // // (*r.Reg & value) > 0 // //go:inline func (r *Register8) HasBits(value uint8) bool { return (r.Get() & value) > 0 } // ReplaceBits is a helper to simplify setting multiple bits high and/or low at // once. It is the volatile equivalent of: // // r.Reg = (r.Reg & ^(mask << pos)) | value << pos // //go:inline func (r *Register8) ReplaceBits(value uint8, mask uint8, pos uint8) { StoreUint8(&r.Reg, LoadUint8(&r.Reg)&^(mask< 0 // //go:inline func (r *Register16) HasBits(value uint16) bool { return (r.Get() & value) > 0 } // ReplaceBits is a helper to simplify setting multiple bits high and/or low at // once. It is the volatile equivalent of: // // r.Reg = (r.Reg & ^(mask << pos)) | value << pos // //go:inline func (r *Register16) ReplaceBits(value uint16, mask uint16, pos uint8) { StoreUint16(&r.Reg, LoadUint16(&r.Reg)&^(mask< 0 // //go:inline func (r *Register32) HasBits(value uint32) bool { return (r.Get() & value) > 0 } // ReplaceBits is a helper to simplify setting multiple bits high and/or low at // once. It is the volatile equivalent of: // // r.Reg = (r.Reg & ^(mask << pos)) | value << pos // //go:inline func (r *Register32) ReplaceBits(value uint32, mask uint32, pos uint8) { StoreUint32(&r.Reg, LoadUint32(&r.Reg)&^(mask< 0 // //go:inline func (r *Register64) HasBits(value uint64) bool { return (r.Get() & value) > 0 } // ReplaceBits is a helper to simplify setting multiple bits high and/or low at // once. It is the volatile equivalent of: // // r.Reg = (r.Reg & ^(mask << pos)) | value << pos // //go:inline func (r *Register64) ReplaceBits(value uint64, mask uint64, pos uint8) { StoreUint64(&r.Reg, LoadUint64(&r.Reg)&^(mask< 0; k-- { runtime.Gosched() } mu.Lock() // Increment the active counter. nowActive := active.Add(1) if nowActive > 1 { // Multiple things are holding the lock at the same time. fail.Store(1) } else { // Delay a bit. for k := j; k < n; k++ { runtime.Gosched() } } // Decrement the active counter. var one = 1 active.Add(uint32(-one)) // This is completed. completed.Add(1) mu.Unlock() }() } // Wait for everything to finish. var done bool for !done { // Wait a bit for other things to run. runtime.Gosched() // Acquire the lock and check whether everything has completed. mu.Lock() done = completed.Load() == n mu.Unlock() } if fail.Load() != 0 { t.Error("lock held concurrently") } } // TestRWMutexUncontended tests locking and unlocking an RWMutex that is not shared with any other goroutines. func TestRWMutexUncontended(t *testing.T) { var mu sync.RWMutex // Lock the mutex exclusively and then unlock it. mu.Lock() mu.Unlock() // Acquire several read locks. const n = 5 for i := 0; i < n; i++ { mu.RLock() } // Release all of the read locks. for i := 0; i < n; i++ { mu.RUnlock() } // Re-acquire the lock exclusively. mu.Lock() mu.Unlock() } // TestRWMutexWriteToRead tests the transition from a write lock to a read lock while contended. func TestRWMutexWriteToRead(t *testing.T) { // Create a new RWMutex and acquire a write lock. var mu sync.RWMutex mu.Lock() const n = 3 var readAcquires uint32 var completed uint32 var unlocked uint32 var bad uint32 for i := 0; i < n; i++ { go func() { // Acquire a read lock. mu.RLock() // Verify that the write lock is supposed to be released by now. if atomic.LoadUint32(&unlocked) == 0 { // The write lock is still being held. atomic.AddUint32(&bad, 1) } // Add ourselves to the read lock counter. atomic.AddUint32(&readAcquires, 1) // Wait for everything to hold the read lock simultaneously. for atomic.LoadUint32(&readAcquires) < n { runtime.Gosched() } // Notify of completion. atomic.AddUint32(&completed, 1) // Release the read lock. mu.RUnlock() }() } // Wait a bit for the goroutines to block. for i := 0; i < 3*n; i++ { runtime.Gosched() } // Release the write lock so that the goroutines acquire read locks. atomic.StoreUint32(&unlocked, 1) mu.Unlock() // Wait for everything to complete. for atomic.LoadUint32(&completed) < n { runtime.Gosched() } // Acquire another write lock. mu.Lock() if bad != 0 { t.Error("read lock acquired while write-locked") } } // TestRWMutexReadToWrite tests the transition from a read lock to a write lock while contended. func TestRWMutexReadToWrite(t *testing.T) { // Create a new RWMutex and read-lock it several times. const n = 3 var mu sync.RWMutex var readers uint32 for i := 0; i < n; i++ { mu.RLock() readers++ } // Start a goroutine to acquire a write lock. result := ^uint32(0) go func() { // Acquire a write lock. mu.Lock() // Check for active readers. readers := atomic.LoadUint32(&readers) mu.Unlock() // Report the number of active readers. atomic.StoreUint32(&result, readers) }() // Release the read locks. for i := 0; i < n; i++ { runtime.Gosched() atomic.AddUint32(&readers, ^uint32(0)) mu.RUnlock() } // Wait for a result. var res uint32 for res == ^uint32(0) { runtime.Gosched() res = atomic.LoadUint32(&result) } if res != 0 { t.Errorf("write lock acquired while %d readers were active", res) } } func TestRWMutex(t *testing.T) { m := new(sync.RWMutex) m.Lock() if m.TryLock() { t.Fatalf("TryLock succeeded with mutex locked") } m.Unlock() if !m.TryLock() { t.Fatalf("TryLock failed with mutex unlocked") } m.Unlock() c := make(chan bool) for i := 0; i < 10; i++ { go HammerMutex(m, 1000, c) } for i := 0; i < 10; i++ { <-c } } ================================================ FILE: src/sync/once.go ================================================ package sync type Once struct { done bool m Mutex } func (o *Once) Do(f func()) { o.m.Lock() defer o.m.Unlock() if o.done { return } o.done = true f() } ================================================ FILE: src/sync/once_test.go ================================================ package sync_test import ( "sync" "testing" ) // TestOnceUncontended tests Once on a single goroutine. func TestOnceUncontended(t *testing.T) { var once sync.Once { var ran bool once.Do(func() { ran = true }) if !ran { t.Error("first call to Do did not run") } } { var ran bool once.Do(func() { ran = true }) if ran { t.Error("second call to Do ran") } } } // TestOnceConcurrent tests multiple concurrent invocations of sync.Once. func TestOnceConcurrent(t *testing.T) { var once sync.Once var mu sync.Mutex mu.Lock() var ran bool var ranTwice bool once.Do(func() { ran = true // Start a goroutine and (approximately) wait for it to enter the call to Do. var startWait sync.Mutex startWait.Lock() go func() { startWait.Unlock() once.Do(func() { ranTwice = true }) mu.Unlock() }() startWait.Lock() }) if !ran { t.Error("first call to Do did not run") } // Wait for the goroutine to finish. mu.Lock() if ranTwice { t.Error("second concurrent call to Once also ran") } } ================================================ FILE: src/sync/oncefunc.go ================================================ // Copyright 2022 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package sync // OnceFunc returns a function that invokes f only once. The returned function // may be called concurrently. // // If f panics, the returned function will panic with the same value on every call. func OnceFunc(f func()) func() { var ( once Once valid bool p any ) // Construct the inner closure just once to reduce costs on the fast path. g := func() { defer func() { p = recover() if !valid { // Re-panic immediately so on the first call the user gets a // complete stack trace into f. panic(p) } }() f() valid = true // Set only if f does not panic } return func() { once.Do(g) if !valid { panic(p) } } } // OnceValue returns a function that invokes f only once and returns the value // returned by f. The returned function may be called concurrently. // // If f panics, the returned function will panic with the same value on every call. func OnceValue[T any](f func() T) func() T { var ( once Once valid bool p any result T ) g := func() { defer func() { p = recover() if !valid { panic(p) } }() result = f() valid = true } return func() T { once.Do(g) if !valid { panic(p) } return result } } // OnceValues returns a function that invokes f only once and returns the values // returned by f. The returned function may be called concurrently. // // If f panics, the returned function will panic with the same value on every call. func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { var ( once Once valid bool p any r1 T1 r2 T2 ) g := func() { defer func() { p = recover() if !valid { panic(p) } }() r1, r2 = f() valid = true } return func() (T1, T2) { once.Do(g) if !valid { panic(p) } return r1, r2 } } ================================================ FILE: src/sync/oncefunc_test.go ================================================ // Copyright 2022 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package sync_test import ( "sync" "testing" ) // We assume that the Once.Do tests have already covered parallelism. func TestOnceFunc(t *testing.T) { calls := 0 f := sync.OnceFunc(func() { calls++ }) allocs := testing.AllocsPerRun(10, f) if calls != 1 { t.Errorf("want calls==1, got %d", calls) } if allocs != 0 { t.Errorf("want 0 allocations per call, got %v", allocs) } } func TestOnceValue(t *testing.T) { calls := 0 f := sync.OnceValue(func() int { calls++ return calls }) allocs := testing.AllocsPerRun(10, func() { f() }) value := f() if calls != 1 { t.Errorf("want calls==1, got %d", calls) } if value != 1 { t.Errorf("want value==1, got %d", value) } if allocs != 0 { t.Errorf("want 0 allocations per call, got %v", allocs) } } func TestOnceValues(t *testing.T) { calls := 0 f := sync.OnceValues(func() (int, int) { calls++ return calls, calls + 1 }) allocs := testing.AllocsPerRun(10, func() { f() }) v1, v2 := f() if calls != 1 { t.Errorf("want calls==1, got %d", calls) } if v1 != 1 || v2 != 2 { t.Errorf("want v1==1 and v2==2, got %d and %d", v1, v2) } if allocs != 0 { t.Errorf("want 0 allocations per call, got %v", allocs) } } // TODO: need to implement more complete panic handling for these tests. // func testOncePanicX(t *testing.T, calls *int, f func()) { // testOncePanicWith(t, calls, f, func(label string, p any) { // if p != "x" { // t.Fatalf("%s: want panic %v, got %v", label, "x", p) // } // }) // } // func testOncePanicWith(t *testing.T, calls *int, f func(), check func(label string, p any)) { // // Check that the each call to f panics with the same value, but the // // underlying function is only called once. // for _, label := range []string{"first time", "second time"} { // var p any // panicked := true // func() { // defer func() { // p = recover() // }() // f() // panicked = false // }() // if !panicked { // t.Fatalf("%s: f did not panic", label) // } // check(label, p) // } // if *calls != 1 { // t.Errorf("want calls==1, got %d", *calls) // } // } // func TestOnceFuncPanic(t *testing.T) { // calls := 0 // f := sync.OnceFunc(func() { // calls++ // panic("x") // }) // testOncePanicX(t, &calls, f) // } // func TestOnceValuePanic(t *testing.T) { // calls := 0 // f := sync.OnceValue(func() int { // calls++ // panic("x") // }) // testOncePanicX(t, &calls, func() { f() }) // } // func TestOnceValuesPanic(t *testing.T) { // calls := 0 // f := sync.OnceValues(func() (int, int) { // calls++ // panic("x") // }) // testOncePanicX(t, &calls, func() { f() }) // } // // func TestOnceFuncPanicNil(t *testing.T) { // calls := 0 // f := sync.OnceFunc(func() { // calls++ // panic(nil) // }) // testOncePanicWith(t, &calls, f, func(label string, p any) { // switch p.(type) { // case nil, *runtime.PanicNilError: // return // } // t.Fatalf("%s: want nil panic, got %v", label, p) // }) // } // // func TestOnceFuncGoexit(t *testing.T) { // // If f calls Goexit, the results are unspecified. But check that f doesn't // // get called twice. // calls := 0 // f := sync.OnceFunc(func() { // calls++ // runtime.Goexit() // }) // var wg sync.WaitGroup // for i := 0; i < 2; i++ { // wg.Add(1) // go func() { // defer wg.Done() // defer func() { recover() }() // f() // }() // wg.Wait() // } // if calls != 1 { // t.Errorf("want calls==1, got %d", calls) // } // } ================================================ FILE: src/sync/pool.go ================================================ package sync import "internal/task" // Pool is a very simple implementation of sync.Pool. type Pool struct { lock task.PMutex New func() interface{} items []interface{} } // Get returns an item in the pool, or the value of calling Pool.New() if there are no items. func (p *Pool) Get() interface{} { p.lock.Lock() if len(p.items) > 0 { x := p.items[len(p.items)-1] p.items = p.items[:len(p.items)-1] p.lock.Unlock() return x } p.lock.Unlock() if p.New == nil { return nil } return p.New() } // Put adds a value back into the pool. func (p *Pool) Put(x interface{}) { p.lock.Lock() p.items = append(p.items, x) p.lock.Unlock() } ================================================ FILE: src/sync/pool_test.go ================================================ package sync_test import ( "sync" "testing" ) type testItem struct { val int } func TestPool(t *testing.T) { p := sync.Pool{ New: func() interface{} { return &testItem{} }, } i1P := p.Get() if i1P == nil { t.Error("pool with New returned nil") } i1 := i1P.(*testItem) if got, want := i1.val, 0; got != want { t.Errorf("empty pool item value: got %v, want %v", got, want) } i1.val = 1 i2 := p.Get().(*testItem) if got, want := i2.val, 0; got != want { t.Errorf("empty pool item value: got %v, want %v", got, want) } i2.val = 2 p.Put(i1) i3 := p.Get().(*testItem) if got, want := i3.val, 1; got != want { t.Errorf("pool with item value: got %v, want %v", got, want) } } func TestPool_noNew(t *testing.T) { p := sync.Pool{} i1 := p.Get() if i1 != nil { t.Errorf("pool without New returned %v, want nil", i1) } } ================================================ FILE: src/sync/waitgroup.go ================================================ package sync import "internal/task" type WaitGroup struct { futex task.Futex } func (wg *WaitGroup) Add(delta int) { switch { case delta > 0: // Delta is positive. for { // Check for overflow. counter := wg.futex.Load() if uint32(delta) > (^uint32(0))-counter { panic("sync: WaitGroup counter overflowed") } // Add to the counter. if wg.futex.CompareAndSwap(counter, counter+uint32(delta)) { // Successfully added. return } } default: // Delta is negative (or zero). for { counter := wg.futex.Load() // Check for underflow. if uint32(-delta) > counter { panic("sync: negative WaitGroup counter") } // Subtract from the counter. if !wg.futex.CompareAndSwap(counter, counter-uint32(-delta)) { // Could not swap, trying again. continue } // If the counter is zero, everything is done and the waiters should // be resumed. // When there are multiple thread, there is a chance for the counter // to go to zero, WakeAll to be called, and then the counter to be // incremented again before a waiting goroutine has a chance to // check the new (zero) value. However the last increment is // explicitly given in the docs as something that should not be // done: // // > Note that calls with a positive delta that occur when the // > counter is zero must happen before a Wait. // // So we're fine here. if counter-uint32(-delta) == 0 { // TODO: this is not the most efficient implementation possible // because we wake up all waiters unconditionally, even if there // might be none. Though since the common usage is for this to // be called with at least one waiter, it's probably fine. wg.futex.WakeAll() } // Successfully swapped (and woken all waiting tasks if needed). return } } } func (wg *WaitGroup) Done() { wg.Add(-1) } func (wg *WaitGroup) Wait() { for { counter := wg.futex.Load() if counter == 0 { return // everything already finished } if wg.futex.Wait(counter) { // Successfully woken by WakeAll (in wg.Add). break } } } ================================================ FILE: src/sync/waitgroup_test.go ================================================ package sync_test import ( "sync" "testing" ) // TestWaitGroupUncontended tests the wait group from a single goroutine. func TestWaitGroupUncontended(t *testing.T) { // Check that a single add-and-done works. var wg sync.WaitGroup wg.Add(1) wg.Done() wg.Wait() // Check that mixing positive and negative counts works. wg.Add(10) wg.Add(-8) wg.Add(-1) wg.Add(0) wg.Done() wg.Wait() } // TestWaitGroup tests the typical usage of WaitGroup. func TestWaitGroup(t *testing.T) { const n = 5 var wg sync.WaitGroup wg.Add(n) for i := 0; i < n; i++ { go wg.Done() } wg.Wait() } ================================================ FILE: src/syscall/env_libc.go ================================================ //go:build nintendoswitch || wasip1 package syscall import ( "unsafe" ) func Environ() []string { // This function combines all the environment into a single allocation. // While this optimizes for memory usage and garbage collector // overhead, it does run the risk of potentially pinning a "large" // allocation if a user holds onto a single environment variable or // value. Having each variable be its own allocation would make the // trade-off in the other direction. // calculate total memory required var length uintptr var vars int for environ := libc_environ; *environ != nil; { length += libc_strlen(*environ) vars++ environ = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(environ), unsafe.Sizeof(environ))) } // allocate our backing slice for the strings b := make([]byte, length) // and the slice we're going to return envs := make([]string, 0, vars) // loop over the environment again, this time copying over the data to the backing slice for environ := libc_environ; *environ != nil; { length = libc_strlen(*environ) // construct a Go string pointing at the libc-allocated environment variable data var envVar string rawEnvVar := (*struct { ptr unsafe.Pointer length uintptr })(unsafe.Pointer(&envVar)) rawEnvVar.ptr = *environ rawEnvVar.length = length // pull off the number of bytes we need for this environment variable var bs []byte bs, b = b[:length], b[length:] // copy over the bytes to the Go heap copy(bs, envVar) // convert trimmed slice to string s := *(*string)(unsafe.Pointer(&bs)) // add s to our list of environment variables envs = append(envs, s) // environ++ environ = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(environ), unsafe.Sizeof(environ))) } return envs } func Getenv(key string) (value string, found bool) { data := cstring(key) raw := libc_getenv(&data[0]) if raw == nil { return "", false } ptr := uintptr(unsafe.Pointer(raw)) for size := uintptr(0); ; size++ { v := *(*byte)(unsafe.Pointer(ptr)) if v == 0 { src := *(*[]byte)(unsafe.Pointer(&sliceHeader{buf: raw, len: size, cap: size})) return string(src), true } ptr += unsafe.Sizeof(byte(0)) } } func Setenv(key, val string) (err error) { if len(key) == 0 { return EINVAL } for i := 0; i < len(key); i++ { if key[i] == '=' || key[i] == 0 { return EINVAL } } for i := 0; i < len(val); i++ { if val[i] == 0 { return EINVAL } } runtimeSetenv(key, val) return } func Unsetenv(key string) (err error) { runtimeUnsetenv(key) return } func Clearenv() { for _, s := range Environ() { for j := 0; j < len(s); j++ { if s[j] == '=' { Unsetenv(s[0:j]) break } } } } //go:extern environ var libc_environ *unsafe.Pointer ================================================ FILE: src/syscall/env_nonhosted.go ================================================ //go:build baremetal || js || wasm_unknown package syscall func Environ() []string { env := runtime_envs() envCopy := make([]string, len(env)) copy(envCopy, env) return envCopy } func Getenv(key string) (value string, found bool) { env := runtime_envs() for _, keyval := range env { // Split at '=' character. var k, v string for i := 0; i < len(keyval); i++ { if keyval[i] == '=' { k = keyval[:i] v = keyval[i+1:] } } if k == key { return v, true } } return "", false } func Setenv(key, val string) (err error) { // stub for now return ENOSYS } func Unsetenv(key string) (err error) { // stub for now return ENOSYS } func Clearenv() (err error) { // stub for now return ENOSYS } func runtime_envs() []string ================================================ FILE: src/syscall/env_wasip2.go ================================================ //go:build wasip2 package syscall import ( "internal/wasi/cli/v0.2.0/environment" ) var libc_envs map[string]string func populateEnvironment() { libc_envs = make(map[string]string) for _, kv := range environment.GetEnvironment().Slice() { libc_envs[kv[0]] = kv[1] } } func Environ() []string { var env []string for k, v := range libc_envs { env = append(env, k+"="+v) } return env } func Getenv(key string) (value string, found bool) { value, found = libc_envs[key] return } func Setenv(key, val string) (err error) { if len(key) == 0 { return EINVAL } for i := 0; i < len(key); i++ { if key[i] == '=' || key[i] == 0 { return EINVAL } } for i := 0; i < len(val); i++ { if val[i] == 0 { return EINVAL } } libc_envs[key] = val return nil } func Unsetenv(key string) (err error) { delete(libc_envs, key) return nil } func Clearenv() { clear(libc_envs) } ================================================ FILE: src/syscall/errno.go ================================================ package syscall import "internal/itoa" // Most code here has been copied from the Go sources: // https://github.com/golang/go/blob/go1.12/src/syscall/syscall_js.go // It has the following copyright note: // // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // An Errno is an unsigned number describing an error condition. // It implements the error interface. The zero Errno is by convention // a non-error, so code to convert from Errno to error should use: // // err = nil // if errno != 0 { // err = errno // } type Errno uintptr func (e Errno) Error() string { return "errno " + itoa.Itoa(int(e)) } func (e Errno) Temporary() bool { return e == EINTR || e == EMFILE || e.Timeout() } func (e Errno) Timeout() bool { return e == EAGAIN || e == EWOULDBLOCK || e == ETIMEDOUT } ================================================ FILE: src/syscall/errno_other.go ================================================ //go:build !js && !wasip1 && !wasip2 package syscall func (e Errno) Is(target error) bool { return false } ================================================ FILE: src/syscall/errno_wasilibc.go ================================================ //go:build wasip1 || js package syscall // Use a go:extern definition to access the errno from wasi-libc // //go:extern errno var libcErrno Errno ================================================ FILE: src/syscall/errno_wasip2.go ================================================ //go:build wasip2 package syscall // The errno for libc_wasip2.go var libcErrno Errno ================================================ FILE: src/syscall/file_emulated.go ================================================ //go:build baremetal || (wasm && !wasip1 && !wasip2) || wasm_unknown // This file emulates some file-related functions that are only available // under a real operating system. package syscall func Getwd() (string, error) { return "", nil } ================================================ FILE: src/syscall/file_hosted.go ================================================ //go:build !(baremetal || (wasm && !wasip1 && !wasip2) || wasm_unknown) // This file assumes there is a libc available that runs on a real operating // system. package syscall const pathMax = 1024 func Getwd() (string, error) { var buf [pathMax]byte s := libc_getcwd(&buf[0], uint(len(buf))) if s == nil { return "", getErrno() } n := clen(buf[:]) if n < 1 { return "", EINVAL } return string(buf[:n]), nil } // char *getcwd(char *buf, size_t size) // //export getcwd func libc_getcwd(buf *byte, size uint) *byte ================================================ FILE: src/syscall/libc_wasip2.go ================================================ //go:build wasip2 // mini libc wrapping wasi preview2 calls in a libc api package syscall import ( "unsafe" "internal/cm" "internal/wasi/cli/v0.2.0/environment" "internal/wasi/cli/v0.2.0/stderr" "internal/wasi/cli/v0.2.0/stdin" "internal/wasi/cli/v0.2.0/stdout" wallclock "internal/wasi/clocks/v0.2.0/wall-clock" "internal/wasi/filesystem/v0.2.0/preopens" "internal/wasi/filesystem/v0.2.0/types" ioerror "internal/wasi/io/v0.2.0/error" "internal/wasi/io/v0.2.0/streams" "internal/wasi/random/v0.2.0/random" ) func goString(cstr *byte) string { return unsafe.String(cstr, strlen(cstr)) } //export strlen func strlen(cstr *byte) uintptr { if cstr == nil { return 0 } ptr := unsafe.Pointer(cstr) var i uintptr for p := (*byte)(ptr); *p != 0; p = (*byte)(unsafe.Add(unsafe.Pointer(p), 1)) { i++ } return i } // ssize_t write(int fd, const void *buf, size_t count) // //export write func write(fd int32, buf *byte, count uint) int { if stream, ok := wasiStreams[fd]; ok { return writeStream(stream, buf, count, 0) } stream, ok := wasiFiles[fd] if !ok { libcErrno = EBADF return -1 } if stream.d == cm.ResourceNone { libcErrno = EBADF return -1 } n := pwrite(fd, buf, count, int64(stream.offset)) if n == -1 { return -1 } stream.offset += int64(n) return int(n) } // ssize_t read(int fd, void *buf, size_t count); // //export read func read(fd int32, buf *byte, count uint) int { if stream, ok := wasiStreams[fd]; ok { return readStream(stream, buf, count, 0) } stream, ok := wasiFiles[fd] if !ok { libcErrno = EBADF return -1 } if stream.d == cm.ResourceNone { libcErrno = EBADF return -1 } n := pread(fd, buf, count, int64(stream.offset)) if n == -1 { // error during pread return -1 } stream.offset += int64(n) return int(n) } // At the moment, each time we have a file read or write we create a new stream. Future implementations // could change the current in or out file stream lazily. We could do this by tracking input and output // offsets individually, and if they don't match the current main offset, reopen the file stream at that location. type wasiFile struct { d types.Descriptor oflag int32 // original open flags: O_RDONLY, O_WRONLY, O_RDWR offset int64 // current fd offset; updated with each read/write refs int } // Need to figure out which system calls we're using: // stdin/stdout/stderr want streams, so we use stream read/write // but for regular files we can use the descriptor and explicitly write a buffer to the offset? // The mismatch comes from trying to combine these. var wasiFiles map[int32]*wasiFile = make(map[int32]*wasiFile) func findFreeFD() int32 { var newfd int32 for wasiStreams[newfd] != nil || wasiFiles[newfd] != nil { newfd++ } return newfd } var wasiErrno ioerror.Error type wasiStream struct { in *streams.InputStream out *streams.OutputStream refs int } // This holds entries for stdin/stdout/stderr. var wasiStreams map[int32]*wasiStream func init() { sin := stdin.GetStdin() sout := stdout.GetStdout() serr := stderr.GetStderr() wasiStreams = map[int32]*wasiStream{ 0: &wasiStream{ in: &sin, refs: 1, }, 1: &wasiStream{ out: &sout, refs: 1, }, 2: &wasiStream{ out: &serr, refs: 1, }, } } func readStream(stream *wasiStream, buf *byte, count uint, offset int64) int { if stream.in == nil { // not a stream we can read from libcErrno = EBADF return -1 } if offset != 0 { libcErrno = EINVAL return -1 } libcErrno = 0 list, err, isErr := stream.in.BlockingRead(uint64(count)).Result() if isErr { if err.Closed() { libcErrno = 0 return 0 } else if err := err.LastOperationFailed(); err != nil { wasiErrno = *err libcErrno = EWASIERROR } return -1 } copy(unsafe.Slice(buf, count), list.Slice()) return int(list.Len()) } func writeStream(stream *wasiStream, buf *byte, count uint, offset int64) int { if stream.out == nil { // not a stream we can write to libcErrno = EBADF return -1 } if offset != 0 { libcErrno = EINVAL return -1 } src := unsafe.Slice(buf, count) var remaining = count // The blocking-write-and-flush call allows a maximum of 4096 bytes at a time. // We loop here by instead of doing subscribe/check-write/poll-one/write by hand. for remaining > 0 { len := uint(4096) if len > remaining { len = remaining } _, err, isErr := stream.out.BlockingWriteAndFlush(cm.ToList(src[:len])).Result() if isErr { if err.Closed() { libcErrno = 0 return 0 } else if err := err.LastOperationFailed(); err != nil { wasiErrno = *err libcErrno = EWASIERROR } return -1 } remaining -= len src = src[len:] } return int(count) } //go:linkname memcpy runtime.memcpy func memcpy(dst, src unsafe.Pointer, size uintptr) // ssize_t pread(int fd, void *buf, size_t count, off_t offset); // //export pread func pread(fd int32, buf *byte, count uint, offset int64) int { // TODO(dgryski): Need to be consistent about all these checks; EBADF/EINVAL/... ? if stream, ok := wasiStreams[fd]; ok { return readStream(stream, buf, count, offset) } streams, ok := wasiFiles[fd] if !ok { // TODO(dgryski): EINVAL? libcErrno = EBADF return -1 } if streams.d == cm.ResourceNone { libcErrno = EBADF return -1 } if streams.oflag&O_RDONLY == 0 { libcErrno = EBADF return -1 } listEOF, err, isErr := streams.d.Read(types.FileSize(count), types.FileSize(offset)).Result() if isErr { libcErrno = errorCodeToErrno(err) return -1 } list := listEOF.F0 copy(unsafe.Slice(buf, count), list.Slice()) // TODO(dgryski): EOF bool is ignored? return int(list.Len()) } // ssize_t pwrite(int fd, void *buf, size_t count, off_t offset); // //export pwrite func pwrite(fd int32, buf *byte, count uint, offset int64) int { // TODO(dgryski): Need to be consistent about all these checks; EBADF/EINVAL/... ? if stream, ok := wasiStreams[fd]; ok { return writeStream(stream, buf, count, 0) } streams, ok := wasiFiles[fd] if !ok { // TODO(dgryski): EINVAL? libcErrno = EBADF return -1 } if streams.d == cm.ResourceNone { libcErrno = EBADF return -1 } if streams.oflag&O_WRONLY == 0 { libcErrno = EBADF return -1 } n, err, isErr := streams.d.Write(cm.NewList(buf, count), types.FileSize(offset)).Result() if isErr { // TODO(dgryski): libcErrno = errorCodeToErrno(err) return -1 } return int(n) } // ssize_t lseek(int fd, off_t offset, int whence); // //export lseek func lseek(fd int32, offset int64, whence int) int64 { if _, ok := wasiStreams[fd]; ok { // can't lseek a stream libcErrno = EBADF return -1 } stream, ok := wasiFiles[fd] if !ok { libcErrno = EBADF return -1 } if stream.d == cm.ResourceNone { libcErrno = EBADF return -1 } switch whence { case 0: // SEEK_SET stream.offset = offset case 1: // SEEK_CUR stream.offset += offset case 2: // SEEK_END stat, err, isErr := stream.d.Stat().Result() if isErr { libcErrno = errorCodeToErrno(err) return -1 } stream.offset = int64(stat.Size) + offset } return int64(stream.offset) } // int close(int fd) // //export close func close(fd int32) int32 { if streams, ok := wasiStreams[fd]; ok { if streams.out != nil { // ignore any error streams.out.BlockingFlush() } if streams.refs--; streams.refs == 0 { if streams.out != nil { streams.out.ResourceDrop() streams.out = nil } if streams.in != nil { streams.in.ResourceDrop() streams.in = nil } } delete(wasiStreams, fd) return 0 } streams, ok := wasiFiles[fd] if !ok { libcErrno = EBADF return -1 } if streams.refs--; streams.refs == 0 && streams.d != cm.ResourceNone { streams.d.ResourceDrop() streams.d = 0 } delete(wasiFiles, fd) return 0 } // int dup(int fd) // //export dup func dup(fd int32) int32 { // is fd a stream? if stream, ok := wasiStreams[fd]; ok { newfd := findFreeFD() stream.refs++ wasiStreams[newfd] = stream return newfd } // is fd a file? if file, ok := wasiFiles[fd]; ok { // scan for first free file descriptor newfd := findFreeFD() file.refs++ wasiFiles[newfd] = file return newfd } // unknown file descriptor libcErrno = EBADF return -1 } // void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); // //export mmap func mmap(addr unsafe.Pointer, length uintptr, prot, flags, fd int32, offset uintptr) unsafe.Pointer { libcErrno = ENOSYS return unsafe.Pointer(^uintptr(0)) } // int munmap(void *addr, size_t length); // //export munmap func munmap(addr unsafe.Pointer, length uintptr) int32 { libcErrno = ENOSYS return -1 } // int mprotect(void *addr, size_t len, int prot); // //export mprotect func mprotect(addr unsafe.Pointer, len uintptr, prot int32) int32 { libcErrno = ENOSYS return -1 } // int chmod(const char *pathname, mode_t mode); // //export chmod func chmod(pathname *byte, mode uint32) int32 { return 0 } // int mkdir(const char *pathname, mode_t mode); // //export mkdir func mkdir(pathname *byte, mode uint32) int32 { path := goString(pathname) dir, relPath := findPreopenForPath(path) if dir.d == cm.ResourceNone { libcErrno = EACCES return -1 } _, err, isErr := dir.d.CreateDirectoryAt(relPath).Result() if isErr { libcErrno = errorCodeToErrno(err) return -1 } return 0 } // int rmdir(const char *pathname); // //export rmdir func rmdir(pathname *byte) int32 { path := goString(pathname) dir, relPath := findPreopenForPath(path) if dir.d == cm.ResourceNone { libcErrno = EACCES return -1 } _, err, isErr := dir.d.RemoveDirectoryAt(relPath).Result() if isErr { libcErrno = errorCodeToErrno(err) return -1 } return 0 } // int rename(const char *from, *to); // //export rename func rename(from, to *byte) int32 { fromPath := goString(from) fromDir, fromRelPath := findPreopenForPath(fromPath) if fromDir.d == cm.ResourceNone { libcErrno = EACCES return -1 } toPath := goString(to) toDir, toRelPath := findPreopenForPath(toPath) if toDir.d == cm.ResourceNone { libcErrno = EACCES return -1 } _, err, isErr := fromDir.d.RenameAt(fromRelPath, toDir.d, toRelPath).Result() if isErr { libcErrno = errorCodeToErrno(err) return -1 } return 0 } // int symlink(const char *from, *to); // //export symlink func symlink(from, to *byte) int32 { fromPath := goString(from) fromDir, fromRelPath := findPreopenForPath(fromPath) if fromDir.d == cm.ResourceNone { libcErrno = EACCES return -1 } toPath := goString(to) toDir, toRelPath := findPreopenForPath(toPath) if toDir.d == cm.ResourceNone { libcErrno = EACCES return -1 } if fromDir.d != toDir.d { libcErrno = EACCES return -1 } // TODO(dgryski): check fromDir == toDir? _, err, isErr := fromDir.d.SymlinkAt(fromRelPath, toRelPath).Result() if isErr { libcErrno = errorCodeToErrno(err) return -1 } return 0 } // int link(const char *from, *to); // //export link func link(from, to *byte) int32 { fromPath := goString(from) fromDir, fromRelPath := findPreopenForPath(fromPath) if fromDir.d == cm.ResourceNone { libcErrno = EACCES return -1 } toPath := goString(to) toDir, toRelPath := findPreopenForPath(toPath) if toDir.d == cm.ResourceNone { libcErrno = EACCES return -1 } if fromDir.d != toDir.d { libcErrno = EACCES return -1 } // TODO(dgryski): check fromDir == toDir? _, err, isErr := fromDir.d.LinkAt(0, fromRelPath, toDir.d, toRelPath).Result() if isErr { libcErrno = errorCodeToErrno(err) return -1 } return 0 } // int fsync(int fd); // //export fsync func fsync(fd int32) int32 { if _, ok := wasiStreams[fd]; ok { // can't sync a stream libcErrno = EBADF return -1 } streams, ok := wasiFiles[fd] if !ok { libcErrno = EBADF return -1 } if streams.d == cm.ResourceNone { libcErrno = EBADF return -1 } if streams.oflag&O_WRONLY == 0 { libcErrno = EBADF return -1 } _, err, isErr := streams.d.SyncData().Result() if isErr { libcErrno = errorCodeToErrno(err) return -1 } return 0 } // ssize_t readlink(const char *path, void *buf, size_t count); // //export readlink func readlink(pathname *byte, buf *byte, count uint) int { path := goString(pathname) dir, relPath := findPreopenForPath(path) if dir.d == cm.ResourceNone { libcErrno = EACCES return -1 } s, err, isErr := dir.d.ReadLinkAt(relPath).Result() if isErr { libcErrno = errorCodeToErrno(err) return -1 } size := uintptr(count) if size > uintptr(len(s)) { size = uintptr(len(s)) } memcpy(unsafe.Pointer(buf), unsafe.Pointer(unsafe.StringData(s)), size) return int(size) } // int unlink(const char *pathname); // //export unlink func unlink(pathname *byte) int32 { path := goString(pathname) dir, relPath := findPreopenForPath(path) if dir.d == cm.ResourceNone { libcErrno = EACCES return -1 } _, err, isErr := dir.d.UnlinkFileAt(relPath).Result() if isErr { libcErrno = errorCodeToErrno(err) return -1 } return 0 } // int getpagesize(void); // //export getpagesize func getpagesize() int { return 65536 } // int stat(const char *path, struct stat * buf); // //export stat func stat(pathname *byte, dst *Stat_t) int32 { path := goString(pathname) dir, relPath := findPreopenForPath(path) if dir.d == cm.ResourceNone { libcErrno = EACCES return -1 } stat, err, isErr := dir.d.StatAt(0, relPath).Result() if isErr { libcErrno = errorCodeToErrno(err) return -1 } setStatFromWASIStat(dst, &stat) return 0 } // int fstat(int fd, struct stat * buf); // //export fstat func fstat(fd int32, dst *Stat_t) int32 { if _, ok := wasiStreams[fd]; ok { // TODO(dgryski): fill in stat buffer for stdin etc return -1 } stream, ok := wasiFiles[fd] if !ok { libcErrno = EBADF return -1 } if stream.d == cm.ResourceNone { libcErrno = EBADF return -1 } stat, err, isErr := stream.d.Stat().Result() if isErr { libcErrno = errorCodeToErrno(err) return -1 } setStatFromWASIStat(dst, &stat) return 0 } func setStatFromWASIStat(sstat *Stat_t, wstat *types.DescriptorStat) { // This will cause problems for people who want to compare inodes sstat.Dev = 0 sstat.Ino = 0 sstat.Rdev = 0 sstat.Nlink = uint64(wstat.LinkCount) sstat.Mode = p2fileTypeToStatType(wstat.Type) // No uid/gid sstat.Uid = 0 sstat.Gid = 0 sstat.Size = int64(wstat.Size) // made up numbers sstat.Blksize = 512 sstat.Blocks = (sstat.Size + 511) / int64(sstat.Blksize) setOptTime := func(t *Timespec, o *wallclock.DateTime) { t.Sec = 0 t.Nsec = 0 if o != nil { t.Sec = int32(o.Seconds) t.Nsec = int64(o.Nanoseconds) } } setOptTime(&sstat.Atim, wstat.DataAccessTimestamp.Some()) setOptTime(&sstat.Mtim, wstat.DataModificationTimestamp.Some()) setOptTime(&sstat.Ctim, wstat.StatusChangeTimestamp.Some()) } // int lstat(const char *path, struct stat * buf); // //export lstat func lstat(pathname *byte, dst *Stat_t) int32 { path := goString(pathname) dir, relPath := findPreopenForPath(path) if dir.d == cm.ResourceNone { libcErrno = EACCES return -1 } stat, err, isErr := dir.d.StatAt(0, relPath).Result() if isErr { libcErrno = errorCodeToErrno(err) return -1 } setStatFromWASIStat(dst, &stat) return 0 } func init() { populateEnvironment() populatePreopens() } type wasiDir struct { d types.Descriptor // wasip2 descriptor root string // root path for this descriptor rel string // relative path under root } var libcCWD wasiDir var wasiPreopens map[string]types.Descriptor func populatePreopens() { var cwd string // find CWD result := environment.InitialCWD() if s := result.Some(); s != nil { cwd = *s } else if s, _ := Getenv("PWD"); s != "" { cwd = s } dirs := preopens.GetDirectories().Slice() preopens := make(map[string]types.Descriptor, len(dirs)) for _, tup := range dirs { desc, path := tup.F0, tup.F1 if path == cwd { libcCWD.d = desc libcCWD.root = path libcCWD.rel = "" } preopens[path] = desc } wasiPreopens = preopens } // -- BEGIN fs_wasip1.go -- // The following section has been taken from upstream Go with the following copyright: // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:nosplit func appendCleanPath(buf []byte, path string, lookupParent bool) ([]byte, bool) { i := 0 for i < len(path) { for i < len(path) && path[i] == '/' { i++ } j := i for j < len(path) && path[j] != '/' { j++ } s := path[i:j] i = j switch s { case "": continue case ".": continue case "..": if !lookupParent { k := len(buf) for k > 0 && buf[k-1] != '/' { k-- } for k > 1 && buf[k-1] == '/' { k-- } buf = buf[:k] if k == 0 { lookupParent = true } else { s = "" continue } } default: lookupParent = false } if len(buf) > 0 && buf[len(buf)-1] != '/' { buf = append(buf, '/') } buf = append(buf, s...) } return buf, lookupParent } // joinPath concatenates dir and file paths, producing a cleaned path where // "." and ".." have been removed, unless dir is relative and the references // to parent directories in file represented a location relative to a parent // of dir. // // This function is used for path resolution of all wasi functions expecting // a path argument; the returned string is heap allocated, which we may want // to optimize in the future. Instead of returning a string, the function // could append the result to an output buffer that the functions in this // file can manage to have allocated on the stack (e.g. initializing to a // fixed capacity). Since it will significantly increase code complexity, // we prefer to optimize for readability and maintainability at this time. func joinPath(dir, file string) string { buf := make([]byte, 0, len(dir)+len(file)+1) if isAbs(dir) { buf = append(buf, '/') } buf, lookupParent := appendCleanPath(buf, dir, true) buf, _ = appendCleanPath(buf, file, lookupParent) // The appendCleanPath function cleans the path so it does not inject // references to the current directory. If both the dir and file args // were ".", this results in the output buffer being empty so we handle // this condition here. if len(buf) == 0 { buf = append(buf, '.') } // If the file ended with a '/' we make sure that the output also ends // with a '/'. This is needed to ensure that programs have a mechanism // to represent dereferencing symbolic links pointing to directories. if buf[len(buf)-1] != '/' && isDir(file) { buf = append(buf, '/') } return unsafe.String(&buf[0], len(buf)) } func isAbs(path string) bool { return hasPrefix(path, "/") } func isDir(path string) bool { return hasSuffix(path, "/") } func hasPrefix(s, p string) bool { return len(s) >= len(p) && s[:len(p)] == p } func hasSuffix(s, x string) bool { return len(s) >= len(x) && s[len(s)-len(x):] == x } // findPreopenForPath finds which preopen it relates to and return that descriptor/root and the path relative to that directory descriptor/root func findPreopenForPath(path string) (wasiDir, string) { dir := "/" var wasidir wasiDir if !isAbs(path) { dir = libcCWD.root wasidir = libcCWD if libcCWD.rel != "" && libcCWD.rel != "." && libcCWD.rel != "./" { path = libcCWD.rel + "/" + path } } path = joinPath(dir, path) var best string for k, v := range wasiPreopens { if len(k) > len(best) && hasPrefix(path, k) { wasidir = wasiDir{d: v, root: k} best = wasidir.root } } if hasPrefix(path, wasidir.root) { path = path[len(wasidir.root):] } for isAbs(path) { path = path[1:] } if len(path) == 0 { path = "." } return wasidir, path } // -- END fs_wasip1.go -- // int open(const char *pathname, int flags, mode_t mode); // //export open func open(pathname *byte, flags int32, mode uint32) int32 { path := goString(pathname) dir, relPath := findPreopenForPath(path) if dir.d == cm.ResourceNone { libcErrno = EACCES return -1 } var dflags types.DescriptorFlags if (flags & O_RDONLY) == O_RDONLY { dflags |= types.DescriptorFlagsRead } if (flags & O_WRONLY) == O_WRONLY { dflags |= types.DescriptorFlagsWrite } var oflags types.OpenFlags if flags&O_CREAT == O_CREAT { oflags |= types.OpenFlagsCreate } if flags&O_DIRECTORY == O_DIRECTORY { oflags |= types.OpenFlagsDirectory } if flags&O_EXCL == O_EXCL { oflags |= types.OpenFlagsExclusive } if flags&O_TRUNC == O_TRUNC { oflags |= types.OpenFlagsTruncate } // By default, follow symlinks for open() unless O_NOFOLLOW was passed var pflags types.PathFlags = types.PathFlagsSymlinkFollow if flags&O_NOFOLLOW == O_NOFOLLOW { // O_NOFOLLOW was passed, so turn off SymlinkFollow pflags &^= types.PathFlagsSymlinkFollow } descriptor, err, isErr := dir.d.OpenAt(pflags, relPath, oflags, dflags).Result() if isErr { libcErrno = errorCodeToErrno(err) return -1 } stream := wasiFile{ d: descriptor, oflag: flags, refs: 1, } if flags&(O_WRONLY|O_APPEND) == (O_WRONLY | O_APPEND) { stat, err, isErr := stream.d.Stat().Result() if isErr { libcErrno = errorCodeToErrno(err) return -1 } stream.offset = int64(stat.Size) } libcfd := findFreeFD() wasiFiles[libcfd] = &stream return int32(libcfd) } func errorCodeToErrno(err types.ErrorCode) Errno { switch err { case types.ErrorCodeAccess: return EACCES case types.ErrorCodeWouldBlock: return EAGAIN case types.ErrorCodeAlready: return EALREADY case types.ErrorCodeBadDescriptor: return EBADF case types.ErrorCodeBusy: return EBUSY case types.ErrorCodeDeadlock: return EDEADLK case types.ErrorCodeQuota: return EDQUOT case types.ErrorCodeExist: return EEXIST case types.ErrorCodeFileTooLarge: return EFBIG case types.ErrorCodeIllegalByteSequence: return EILSEQ case types.ErrorCodeInProgress: return EINPROGRESS case types.ErrorCodeInterrupted: return EINTR case types.ErrorCodeInvalid: return EINVAL case types.ErrorCodeIO: return EIO case types.ErrorCodeIsDirectory: return EISDIR case types.ErrorCodeLoop: return ELOOP case types.ErrorCodeTooManyLinks: return EMLINK case types.ErrorCodeMessageSize: return EMSGSIZE case types.ErrorCodeNameTooLong: return ENAMETOOLONG case types.ErrorCodeNoDevice: return ENODEV case types.ErrorCodeNoEntry: return ENOENT case types.ErrorCodeNoLock: return ENOLCK case types.ErrorCodeInsufficientMemory: return ENOMEM case types.ErrorCodeInsufficientSpace: return ENOSPC case types.ErrorCodeNotDirectory: return ENOTDIR case types.ErrorCodeNotEmpty: return ENOTEMPTY case types.ErrorCodeNotRecoverable: return ENOTRECOVERABLE case types.ErrorCodeUnsupported: return ENOSYS case types.ErrorCodeNoTTY: return ENOTTY case types.ErrorCodeNoSuchDevice: return ENXIO case types.ErrorCodeOverflow: return EOVERFLOW case types.ErrorCodeNotPermitted: return EPERM case types.ErrorCodePipe: return EPIPE case types.ErrorCodeReadOnly: return EROFS case types.ErrorCodeInvalidSeek: return ESPIPE case types.ErrorCodeTextFileBusy: return ETXTBSY case types.ErrorCodeCrossDevice: return EXDEV } return Errno(err) } type libc_DIR struct { d types.DirectoryEntryStream } // DIR *fdopendir(int); // //export fdopendir func fdopendir(fd int32) unsafe.Pointer { if _, ok := wasiStreams[fd]; ok { libcErrno = EBADF return nil } stream, ok := wasiFiles[fd] if !ok { libcErrno = EBADF return nil } if stream.d == cm.ResourceNone { libcErrno = EBADF return nil } dir, err, isErr := stream.d.ReadDirectory().Result() if isErr { libcErrno = errorCodeToErrno(err) return nil } return unsafe.Pointer(&libc_DIR{d: dir}) } // int fdclosedir(DIR *); // //export fdclosedir func fdclosedir(dirp unsafe.Pointer) int32 { if dirp == nil { return 0 } dir := (*libc_DIR)(dirp) if dir.d == cm.ResourceNone { return 0 } dir.d.ResourceDrop() dir.d = cm.ResourceNone return 0 } // struct dirent *readdir(DIR *); // //export readdir func readdir(dirp unsafe.Pointer) *Dirent { if dirp == nil { return nil } dir := (*libc_DIR)(dirp) if dir.d == cm.ResourceNone { return nil } someEntry, err, isErr := dir.d.ReadDirectoryEntry().Result() if isErr { libcErrno = errorCodeToErrno(err) return nil } entry := someEntry.Some() if entry == nil { libcErrno = 0 return nil } // The dirent C struct uses a flexible array member to indicate that the // directory name is laid out in memory right after the struct data: // // struct dirent { // ino_t d_ino; // unsigned char d_type; // char d_name[]; // }; buf := make([]byte, unsafe.Sizeof(Dirent{})+uintptr(len(entry.Name))) dirent := (*Dirent)((unsafe.Pointer)(&buf[0])) // No inodes in wasi dirent.Ino = 0 dirent.Type = p2fileTypeToDirentType(entry.Type) copy(buf[unsafe.Offsetof(dirent.Type)+1:], entry.Name) return dirent } func p2fileTypeToDirentType(t types.DescriptorType) uint8 { switch t { case types.DescriptorTypeUnknown: return DT_UNKNOWN case types.DescriptorTypeBlockDevice: return DT_BLK case types.DescriptorTypeCharacterDevice: return DT_CHR case types.DescriptorTypeDirectory: return DT_DIR case types.DescriptorTypeFIFO: return DT_FIFO case types.DescriptorTypeSymbolicLink: return DT_LNK case types.DescriptorTypeRegularFile: return DT_REG case types.DescriptorTypeSocket: return DT_FIFO } return DT_UNKNOWN } func p2fileTypeToStatType(t types.DescriptorType) uint32 { switch t { case types.DescriptorTypeUnknown: return 0 case types.DescriptorTypeBlockDevice: return S_IFBLK case types.DescriptorTypeCharacterDevice: return S_IFCHR case types.DescriptorTypeDirectory: return S_IFDIR case types.DescriptorTypeFIFO: return S_IFIFO case types.DescriptorTypeSymbolicLink: return S_IFLNK case types.DescriptorTypeRegularFile: return S_IFREG case types.DescriptorTypeSocket: return S_IFSOCK } return 0 } // void arc4random_buf (void *, size_t); // //export arc4random_buf func arc4random_buf(p unsafe.Pointer, l uint) { result := random.GetRandomBytes(uint64(l)) s := result.Slice() memcpy(unsafe.Pointer(p), unsafe.Pointer(unsafe.SliceData(s)), uintptr(l)) } // int chdir(char *name) // //export chdir func chdir(name *byte) int { path := goString(name) + "/" if !isAbs(path) { path = joinPath(libcCWD.root+"/"+libcCWD.rel+"/", path) } if path == "." { return 0 } dir, rel := findPreopenForPath(path) if dir.d == cm.ResourceNone { libcErrno = EACCES return -1 } _, err, isErr := dir.d.OpenAt(types.PathFlagsSymlinkFollow, rel, types.OpenFlagsDirectory, types.DescriptorFlagsRead).Result() if isErr { libcErrno = errorCodeToErrno(err) return -1 } libcCWD = dir // keep the same cwd base but update "rel" to point to new base path libcCWD.rel = rel return 0 } // char *getcwd(char *buf, size_t size) // //export getcwd func getcwd(buf *byte, size uint) *byte { cwd := libcCWD.root if libcCWD.rel != "" && libcCWD.rel != "." && libcCWD.rel != "./" { cwd += libcCWD.rel } if buf == nil { b := make([]byte, len(cwd)+1) buf = unsafe.SliceData(b) } else if size == 0 { libcErrno = EINVAL return nil } if size < uint(len(cwd)+1) { libcErrno = ERANGE return nil } s := unsafe.Slice(buf, size) s[size-1] = 0 // Enforce NULL termination copy(s, cwd) return buf } // int truncate(const char *path, off_t length); // //export truncate func truncate(path *byte, length int64) int32 { libcErrno = ENOSYS return -1 } ================================================ FILE: src/syscall/mmap_unix_test.go ================================================ // Copyright 2014 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build linux package syscall_test import ( "syscall" "testing" ) func TestMmap(t *testing.T) { b, err := syscall.Mmap(-1, 0, syscall.Getpagesize(), syscall.PROT_NONE, syscall.MAP_ANON|syscall.MAP_PRIVATE) if err != nil { t.Fatalf("Mmap: %v", err) } if err := syscall.Munmap(b); err != nil { t.Fatalf("Munmap: %v", err) } } ================================================ FILE: src/syscall/net.go ================================================ // Copyright 2017 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package syscall // A RawConn is a raw network connection. type RawConn interface { // Control invokes f on the underlying connection's file // descriptor or handle. // The file descriptor fd is guaranteed to remain valid while // f executes but not after f returns. Control(f func(fd uintptr)) error // Read invokes f on the underlying connection's file // descriptor or handle; f is expected to try to read from the // file descriptor. // If f returns true, Read returns. Otherwise Read blocks // waiting for the connection to be ready for reading and // tries again repeatedly. // The file descriptor is guaranteed to remain valid while f // executes but not after f returns. Read(f func(fd uintptr) (done bool)) error // Write is like Read but for writing. Write(f func(fd uintptr) (done bool)) error } // Conn is implemented by some types in the net and os packages to provide // access to the underlying file descriptor or handle. type Conn interface { // SyscallConn returns a raw network connection. SyscallConn() (RawConn, error) } ================================================ FILE: src/syscall/proc_emulated.go ================================================ //go:build baremetal || tinygo.wasm || nintendoswitch // This file emulates some process-related functions that are only available // under a real operating system. package syscall func Getuid() int { return -1 } func Geteuid() int { return -1 } func Getgid() int { return -1 } func Getegid() int { return -1 } func Getpid() int { return -1 } func Getppid() int { return -1 } ================================================ FILE: src/syscall/proc_hosted.go ================================================ //go:build !baremetal && !tinygo.wasm && !nintendoswitch // This file assumes there is a libc available that runs on a real operating // system. package syscall func Getuid() int { return int(libc_getuid()) } func Geteuid() int { return int(libc_geteuid()) } func Getgid() int { return int(libc_getgid()) } func Getegid() int { return int(libc_getegid()) } func Getpid() int { return int(libc_getpid()) } func Getppid() int { return int(libc_getppid()) } // uid_t getuid(void) // //export getuid func libc_getuid() int32 // gid_t getgid(void) // //export getgid func libc_getgid() int32 // uid_t geteuid(void) // //export geteuid func libc_geteuid() int32 // gid_t getegid(void) // //export getegid func libc_getegid() int32 // gid_t getpid(void) // //export getpid func libc_getpid() int32 // gid_t getppid(void) // //export getppid func libc_getppid() int32 ================================================ FILE: src/syscall/str.go ================================================ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package syscall // clen returns the index of the first NULL byte in n or len(n) if n contains no NULL byte. func clen(n []byte) int { for i := 0; i < len(n); i++ { if n[i] == 0 { return i } } return len(n) } ================================================ FILE: src/syscall/syscall.go ================================================ package syscall import ( "errors" ) const ( MSG_DONTWAIT = 0x40 AF_INET = 0x2 AF_INET6 = 0xa ) func Exit(code int) type Rlimit struct { Cur uint64 Max uint64 } func Setrlimit(resource int, rlim *Rlimit) error { return errors.New("Setrlimit not implemented") } ================================================ FILE: src/syscall/syscall_libc.go ================================================ //go:build js || nintendoswitch || wasip1 || wasip2 package syscall import ( "unsafe" ) type sliceHeader struct { buf *byte len uintptr cap uintptr } func Close(fd int) (err error) { if libc_close(int32(fd)) < 0 { err = getErrno() } return } func Dup(fd int) (fd2 int, err error) { fd2 = int(libc_dup(int32(fd))) if fd2 < 0 { err = getErrno() } return } func Write(fd int, p []byte) (n int, err error) { buf, count := splitSlice(p) n = libc_write(int32(fd), buf, uint(count)) if n < 0 { err = getErrno() } return } func Read(fd int, p []byte) (n int, err error) { buf, count := splitSlice(p) n = libc_read(int32(fd), buf, uint(count)) if n < 0 { err = getErrno() } return } func Pread(fd int, p []byte, offset int64) (n int, err error) { buf, count := splitSlice(p) n = libc_pread(int32(fd), buf, uint(count), offset) if n < 0 { err = getErrno() } return } func Pwrite(fd int, p []byte, offset int64) (n int, err error) { buf, count := splitSlice(p) n = libc_pwrite(int32(fd), buf, uint(count), offset) if n < 0 { err = getErrno() } return } func Seek(fd int, offset int64, whence int) (newoffset int64, err error) { newoffset = libc_lseek(int32(fd), offset, whence) if newoffset < 0 { err = getErrno() } return } func Open(path string, flag int, mode uint32) (fd int, err error) { data := cstring(path) fd = int(libc_open(&data[0], int32(flag), mode)) if fd < 0 { err = getErrno() } return } func Fsync(fd int) (err error) { if libc_fsync(int32(fd)) < 0 { err = getErrno() } return } func Readlink(path string, p []byte) (n int, err error) { data := cstring(path) buf, count := splitSlice(p) n = libc_readlink(&data[0], buf, uint(count)) if n < 0 { err = getErrno() } return } func Chdir(path string) (err error) { data := cstring(path) fail := int(libc_chdir(&data[0])) if fail < 0 { err = getErrno() } return } func Mkdir(path string, mode uint32) (err error) { data := cstring(path) fail := int(libc_mkdir(&data[0], mode)) if fail < 0 { err = getErrno() } return } func Rmdir(path string) (err error) { data := cstring(path) fail := int(libc_rmdir(&data[0])) if fail < 0 { err = getErrno() } return } func Rename(from, to string) (err error) { fromdata := cstring(from) todata := cstring(to) fail := int(libc_rename(&fromdata[0], &todata[0])) if fail < 0 { err = getErrno() } return } func Link(oldname, newname string) (err error) { fromdata := cstring(oldname) todata := cstring(newname) fail := int(libc_link(&fromdata[0], &todata[0])) if fail < 0 { err = getErrno() } return } func Symlink(from, to string) (err error) { fromdata := cstring(from) todata := cstring(to) fail := int(libc_symlink(&fromdata[0], &todata[0])) if fail < 0 { err = getErrno() } return } func Unlink(path string) (err error) { data := cstring(path) fail := int(libc_unlink(&data[0])) if fail < 0 { err = getErrno() } return } func Chown(path string, uid, gid int) (err error) { data := cstring(path) fail := int(libc_chown(&data[0], uid, gid)) if fail < 0 { err = getErrno() } return } func Fork() (err error) { fail := int(libc_fork()) if fail < 0 { err = getErrno() } return } func Execve(pathname string, argv []string, envv []string) (err error) { argv0 := cstring(pathname) // transform argv and envv into the format expected by execve argv1 := make([]*byte, len(argv)+1) for i, arg := range argv { argv1[i] = &cstring(arg)[0] } argv1[len(argv)] = nil env1 := make([]*byte, len(envv)+1) for i, env := range envv { env1[i] = &cstring(env)[0] } env1[len(envv)] = nil fail := int(libc_execve(&argv0[0], &argv1[0], &env1[0])) if fail < 0 { err = getErrno() } return } func Truncate(path string, length int64) (err error) { data := cstring(path) fail := int(libc_truncate(&data[0], length)) if fail < 0 { err = getErrno() } return } func Faccessat(dirfd int, path string, mode uint32, flags int) (err error) func Kill(pid int, sig Signal) (err error) { return ENOSYS // TODO } type SysProcAttr struct{} // TODO type WaitStatus uint32 func (w WaitStatus) Exited() bool { return false } func (w WaitStatus) ExitStatus() int { return 0 } func (w WaitStatus) Signaled() bool { return false } func (w WaitStatus) Signal() Signal { return 0 } func (w WaitStatus) CoreDump() bool { return false } func (w WaitStatus) Stopped() bool { return false } func (w WaitStatus) Continued() bool { return false } func (w WaitStatus) StopSignal() Signal { return 0 } func (w WaitStatus) TrapCause() int { return 0 } // Purely here for compatibility. type Rusage struct { } // since rusage is quite a big struct and we stub it out anyway no need to define it here func Wait4(pid int, wstatus *WaitStatus, options int, rusage uintptr) (wpid int, err error) { return 0, ENOSYS // TODO } func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error) { addr := libc_mmap(nil, uintptr(length), int32(prot), int32(flags), int32(fd), uintptr(offset)) if addr == unsafe.Pointer(^uintptr(0)) { return nil, getErrno() } return (*[1 << 30]byte)(addr)[:length:length], nil } func Munmap(b []byte) (err error) { errCode := libc_munmap(unsafe.Pointer(&b[0]), uintptr(len(b))) if errCode != 0 { err = getErrno() } return err } func Mprotect(b []byte, prot int) (err error) { errCode := libc_mprotect(unsafe.Pointer(&b[0]), uintptr(len(b)), int32(prot)) if errCode != 0 { err = getErrno() } return } // BytePtrFromString returns a pointer to a NUL-terminated array of // bytes containing the text of s. If s contains a NUL byte at any // location, it returns (nil, EINVAL). func BytePtrFromString(s string) (*byte, error) { for i := 0; i < len(s); i++ { if s[i] == 0 { return nil, EINVAL } } return &cstring(s)[0], nil } // cstring converts a Go string to a C string. func cstring(s string) []byte { data := make([]byte, len(s)+1) copy(data, s) // final byte should be zero from the initial allocation return data } func splitSlice(p []byte) (buf *byte, len uintptr) { slice := (*sliceHeader)(unsafe.Pointer(&p)) return slice.buf, slice.len } // These two functions are provided by the runtime. func runtimeSetenv(key, value string) func runtimeUnsetenv(key string) //export strlen func libc_strlen(ptr unsafe.Pointer) uintptr // ssize_t write(int fd, const void *buf, size_t count) // //export write func libc_write(fd int32, buf *byte, count uint) int // char *getenv(const char *name); // //export getenv func libc_getenv(name *byte) *byte // ssize_t read(int fd, void *buf, size_t count); // //export read func libc_read(fd int32, buf *byte, count uint) int // ssize_t pread(int fd, void *buf, size_t count, off_t offset); // //export pread func libc_pread(fd int32, buf *byte, count uint, offset int64) int // ssize_t pwrite(int fd, void *buf, size_t count, off_t offset); // //export pwrite func libc_pwrite(fd int32, buf *byte, count uint, offset int64) int // ssize_t lseek(int fd, off_t offset, int whence); // //export lseek func libc_lseek(fd int32, offset int64, whence int) int64 // int close(int fd) // //export close func libc_close(fd int32) int32 // int dup(int fd) // //export dup func libc_dup(fd int32) int32 // void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); // //export mmap func libc_mmap(addr unsafe.Pointer, length uintptr, prot, flags, fd int32, offset uintptr) unsafe.Pointer // int munmap(void *addr, size_t length); // //export munmap func libc_munmap(addr unsafe.Pointer, length uintptr) int32 // int mprotect(void *addr, size_t len, int prot); // //export mprotect func libc_mprotect(addr unsafe.Pointer, len uintptr, prot int32) int32 // int chdir(const char *pathname, mode_t mode); // //export chdir func libc_chdir(pathname *byte) int32 // int chmod(const char *pathname, mode_t mode); // //export chmod func libc_chmod(pathname *byte, mode uint32) int32 // int chown(const char *pathname, uid_t owner, gid_t group); // //export chown func libc_chown(pathname *byte, owner, group int) int32 // int mkdir(const char *pathname, mode_t mode); // //export mkdir func libc_mkdir(pathname *byte, mode uint32) int32 // int rmdir(const char *pathname); // //export rmdir func libc_rmdir(pathname *byte) int32 // int rename(const char *from, *to); // //export rename func libc_rename(from, to *byte) int32 // int symlink(const char *from, *to); // //export symlink func libc_symlink(from, to *byte) int32 // int link(const char *oldname, *newname); // //export link func libc_link(oldname, newname *byte) int32 // int fsync(int fd); // //export fsync func libc_fsync(fd int32) int32 // ssize_t readlink(const char *path, void *buf, size_t count); // //export readlink func libc_readlink(path *byte, buf *byte, count uint) int // int unlink(const char *pathname); // //export unlink func libc_unlink(pathname *byte) int32 // pid_t fork(void); // //export fork func libc_fork() int32 // int execve(const char *filename, char *const argv[], char *const envp[]); // //export execve func libc_execve(filename *byte, argv **byte, envp **byte) int // int truncate(const char *path, off_t length); // //export truncate func libc_truncate(path *byte, length int64) int32 ================================================ FILE: src/syscall/syscall_libc_nintendoswitch.go ================================================ //go:build nintendoswitch package syscall import ( "internal/itoa" ) // A Signal is a number describing a process signal. // It implements the os.Signal interface. type Signal int const ( _ Signal = iota SIGCHLD SIGINT SIGKILL SIGTRAP SIGQUIT SIGTERM ) func (s Signal) Signal() {} func (s Signal) String() string { if 0 <= s && int(s) < len(signals) { str := signals[s] if str != "" { return str } } return "signal " + itoa.Itoa(int(s)) } var signals = [...]string{} // File system const ( Stdin = 0 Stdout = 1 Stderr = 2 ) const ( O_RDONLY = 0 O_WRONLY = 1 O_RDWR = 2 O_CREAT = 0100 O_TRUNC = 01000 O_APPEND = 02000 O_EXCL = 0200 O_SYNC = 010000 O_CLOEXEC = 0 ) //go:extern errno var libcErrno uintptr func getErrno() error { return Errno(libcErrno) } func Pipe2(p []int, flags int) (err error) { return ENOSYS // TODO } func Getpagesize() int { return 4096 // TODO } type RawSockaddrInet4 struct { // stub } type RawSockaddrInet6 struct { // stub } func Chmod(path string, mode uint32) (err error) { data := cstring(path) fail := int(libc_chmod(&data[0], mode)) if fail < 0 { err = getErrno() } return } // int open(const char *pathname, int flags, mode_t mode); // //export open func libc_open(pathname *byte, flags int32, mode uint32) int32 ================================================ FILE: src/syscall/syscall_libc_wasi.go ================================================ //go:build js || wasip1 || wasip2 // Note: also including js in here because it also uses wasi-libc. package syscall import ( "internal/itoa" "unsafe" ) // https://github.com/WebAssembly/wasi-libc/blob/main/expected/wasm32-wasi/predefined-macros.txt // disagrees with ../../lib/wasi-libc/libc-top-half/musl/arch/wasm32/bits/signal.h for SIGCHLD? // https://github.com/WebAssembly/wasi-libc/issues/271 type Signal int const ( SIGINT Signal = 2 SIGQUIT Signal = 3 SIGILL Signal = 4 SIGTRAP Signal = 5 SIGABRT Signal = 6 SIGBUS Signal = 7 SIGFPE Signal = 8 SIGKILL Signal = 9 SIGSEGV Signal = 11 SIGPIPE Signal = 13 SIGTERM Signal = 15 SIGCHLD Signal = 17 ) func (s Signal) Signal() {} func (s Signal) String() string { if 0 <= s && int(s) < len(signals) { str := signals[s] if str != "" { return str } } return "signal " + itoa.Itoa(int(s)) } var signals = [...]string{} const ( Stdin = 0 Stdout = 1 Stderr = 2 ) const ( __WASI_OFLAGS_CREAT = 1 __WASI_OFLAGS_DIRECTORY = 2 __WASI_OFLAGS_EXCL = 4 __WASI_OFLAGS_TRUNC = 8 __WASI_FDFLAGS_APPEND = 1 __WASI_FDFLAGS_DSYNC = 2 __WASI_FDFLAGS_NONBLOCK = 4 __WASI_FDFLAGS_RSYNC = 8 __WASI_FDFLAGS_SYNC = 16 __WASI_FILETYPE_UNKNOWN = 0 __WASI_FILETYPE_BLOCK_DEVICE = 1 __WASI_FILETYPE_CHARACTER_DEVICE = 2 __WASI_FILETYPE_DIRECTORY = 3 __WASI_FILETYPE_REGULAR_FILE = 4 __WASI_FILETYPE_SOCKET_DGRAM = 5 __WASI_FILETYPE_SOCKET_STREAM = 6 __WASI_FILETYPE_SYMBOLIC_LINK = 7 // ../../lib/wasi-libc/libc-bottom-half/headers/public/__header_fcntl.h O_NOFOLLOW = 0x01000000 O_RDONLY = 0x04000000 O_WRONLY = 0x10000000 O_RDWR = O_RDONLY | O_WRONLY O_CREAT = __WASI_OFLAGS_CREAT << 12 O_TRUNC = __WASI_OFLAGS_TRUNC << 12 O_EXCL = __WASI_OFLAGS_EXCL << 12 O_DIRECTORY = __WASI_OFLAGS_DIRECTORY << 12 O_APPEND = __WASI_FDFLAGS_APPEND O_DSYNC = __WASI_FDFLAGS_DSYNC O_NONBLOCK = __WASI_FDFLAGS_NONBLOCK O_RSYNC = __WASI_FDFLAGS_RSYNC O_SYNC = __WASI_FDFLAGS_SYNC O_CLOEXEC = 0 // ../../lib/wasi-libc/sysroot/include/sys/mman.h MAP_FILE = 0 MAP_SHARED = 0x01 MAP_PRIVATE = 0x02 MAP_ANON = 0x20 MAP_ANONYMOUS = MAP_ANON // ../../lib/wasi-libc/sysroot/include/sys/mman.h PROT_NONE = 0 PROT_READ = 1 PROT_WRITE = 2 PROT_EXEC = 4 // ../../lib/wasi-libc/expected/wasm32-wasi/predefined-macros.txt F_GETFL = 3 F_SETFL = 4 // ../../lib/wasi-libc/libc-top-half/musl/arch/generic/bits/ioctl.h TIOCSPGRP = 0x5410 ) // These values are needed as a stub until Go supports WASI as a full target. // The constant values don't have a meaning and don't correspond to anything // real. const ( _ = iota SYS_FCNTL SYS_FCNTL64 SYS_FSTATAT64 SYS_IOCTL SYS_MKDIRAT SYS_OPENAT SYS_READLINKAT SYS_UNLINKAT SYS_WAITID PATH_MAX = 4096 ) func getErrno() error { // libcErrno is the errno from wasi-libc for wasip1 and the errno for libc_wasip2 for wasip2 return libcErrno } func (e Errno) Is(target error) bool { switch target.Error() { case "permission denied": return e == EACCES || e == EPERM || e == ENOTCAPABLE // ENOTCAPABLE is unique in WASI case "file already exists": return e == EEXIST || e == ENOTEMPTY case "file does not exist": return e == ENOENT } return false } // https://github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/headers/public/__errno.h const ( E2BIG Errno = 1 /* Argument list too long */ EACCES Errno = 2 /* Permission denied */ EADDRINUSE Errno = 3 /* Address already in use */ EADDRNOTAVAIL Errno = 4 /* Address not available */ EAFNOSUPPORT Errno = 5 /* Address family not supported by protocol family */ EAGAIN Errno = 6 /* Try again */ EWOULDBLOCK Errno = EAGAIN /* Operation would block */ EALREADY Errno = 7 /* Socket already connected */ EBADF Errno = 8 /* Bad file number */ EBADMSG Errno = 9 /* Trying to read unreadable message */ EBUSY Errno = 10 /* Device or resource busy */ ECANCELED Errno = 11 /* Operation canceled. */ ECHILD Errno = 12 /* No child processes */ ECONNABORTED Errno = 13 /* Connection aborted */ ECONNREFUSED Errno = 14 /* Connection refused */ ECONNRESET Errno = 15 /* Connection reset by peer */ EDEADLK Errno = 16 /* Deadlock condition */ EDESTADDRREQ Errno = 17 /* Destination address required */ EDOM Errno = 18 /* Math arg out of domain of func */ EDQUOT Errno = 19 /* Quota exceeded */ EEXIST Errno = 20 /* File exists */ EFAULT Errno = 21 /* Bad address */ EFBIG Errno = 22 /* File too large */ EHOSTUNREACH Errno = 23 /* Host is unreachable */ EIDRM Errno = 24 /* Identifier removed */ EILSEQ Errno = 25 EINPROGRESS Errno = 26 /* Connection already in progress */ EINTR Errno = 27 /* Interrupted system call */ EINVAL Errno = 28 /* Invalid argument */ EIO Errno = 29 /* I/O error */ EISCONN Errno = 30 /* Socket is already connected */ EISDIR Errno = 31 /* Is a directory */ ELOOP Errno = 32 /* Too many symbolic links */ EMFILE Errno = 33 /* Too many open files */ EMLINK Errno = 34 /* Too many links */ EMSGSIZE Errno = 35 /* Message too long */ EMULTIHOP Errno = 36 /* Multihop attempted */ ENAMETOOLONG Errno = 37 /* File name too long */ ENETDOWN Errno = 38 /* Network interface is not configured */ ENETRESET Errno = 39 ENETUNREACH Errno = 40 /* Network is unreachable */ ENFILE Errno = 41 /* File table overflow */ ENOBUFS Errno = 42 /* No buffer space available */ ENODEV Errno = 43 /* No such device */ ENOENT Errno = 44 /* No such file or directory */ ENOEXEC Errno = 45 /* Exec format error */ ENOLCK Errno = 46 /* No record locks available */ ENOLINK Errno = 47 /* The link has been severed */ ENOMEM Errno = 48 /* Out of memory */ ENOMSG Errno = 49 /* No message of desired type */ ENOPROTOOPT Errno = 50 /* Protocol not available */ ENOSPC Errno = 51 /* No space left on device */ ENOSYS Errno = 52 /* Function not implemented */ ENOTCONN Errno = 53 /* Socket is not connected */ ENOTDIR Errno = 54 /* Not a directory */ ENOTEMPTY Errno = 55 /* Directory not empty */ ENOTRECOVERABLE Errno = 56 /* State not recoverable */ ENOTSOCK Errno = 57 /* Socket operation on non-socket */ ESOCKTNOSUPPORT Errno = 58 /* Socket type not supported */ EOPNOTSUPP Errno = 58 /* Operation not supported on transport endpoint */ ENOTSUP Errno = EOPNOTSUPP /* Not supported */ ENOTTY Errno = 59 /* Not a typewriter */ ENXIO Errno = 60 /* No such device or address */ EOVERFLOW Errno = 61 /* Value too large for defined data type */ EPERM Errno = 63 /* Operation not permitted */ EPIPE Errno = 64 /* Broken pipe */ EPROTO Errno = 65 /* Protocol error */ EPROTONOSUPPORT Errno = 66 /* Unknown protocol */ EPROTOTYPE Errno = 67 /* Protocol wrong type for socket */ ERANGE Errno = 68 /* Math result not representable */ EROFS Errno = 69 /* Read-only file system */ ESPIPE Errno = 70 /* Illegal seek */ ESRCH Errno = 71 /* No such process */ ESTALE Errno = 72 ETIMEDOUT Errno = 73 /* Connection timed out */ ETXTBSY Errno = 74 /* Text file busy */ EXDEV Errno = 75 /* Cross-device link */ ENOTCAPABLE Errno = 76 /* Extension: Capabilities insufficient. */ EWASIERROR Errno = 255 /* Unknown WASI error */ ) // TODO(ydnar): remove Timespec for WASI Preview 2 (seconds is uint64). // // https://github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/headers/public/__struct_timespec.h type Timespec struct { Sec int32 Nsec int64 } // Unix returns the time stored in ts as seconds plus nanoseconds. func (ts *Timespec) Unix() (sec int64, nsec int64) { return int64(ts.Sec), int64(ts.Nsec) } // https://github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/headers/public/__struct_stat.h // https://github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/headers/public/__typedef_ino_t.h // etc. type Stat_t struct { Dev uint64 Ino uint64 Nlink uint64 Mode uint32 Uid uint32 Gid uint32 Pad_cgo_0 [4]byte Rdev uint64 Size int64 Blksize int32 Blocks int64 Atim Timespec Mtim Timespec Ctim Timespec Qspare [3]int64 } // https://github.com/WebAssembly/wasi-libc/blob/main/libc-top-half/musl/include/sys/stat.h const ( S_IFBLK = 0x6000 S_IFCHR = 0x2000 S_IFDIR = 0x4000 S_IFIFO = 0x1000 S_IFLNK = 0xa000 S_IFMT = 0xf000 S_IFREG = 0x8000 S_IFSOCK = 0xc000 S_IREAD = 0x100 S_IRGRP = 0x20 S_IROTH = 0x4 S_IRUSR = 0x100 S_IRWXG = 0x38 S_IRWXO = 0x7 S_IRWXU = 0x1c0 S_ISGID = 0x400 S_ISUID = 0x800 S_ISVTX = 0x200 S_IWGRP = 0x10 S_IWOTH = 0x2 S_IWRITE = 0x80 S_IWUSR = 0x80 S_IXGRP = 0x8 S_IXOTH = 0x1 S_IXUSR = 0x40 ) // https://github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/headers/public/__header_dirent.h const ( DT_BLK = __WASI_FILETYPE_BLOCK_DEVICE DT_CHR = __WASI_FILETYPE_CHARACTER_DEVICE DT_DIR = __WASI_FILETYPE_DIRECTORY DT_FIFO = __WASI_FILETYPE_SOCKET_STREAM DT_LNK = __WASI_FILETYPE_SYMBOLIC_LINK DT_REG = __WASI_FILETYPE_REGULAR_FILE DT_UNKNOWN = __WASI_FILETYPE_UNKNOWN ) // Dirent is returned by pointer from Readdir to iterate over directory entries. // // The pointer is managed by wasi-libc and is only valid until the next call to // Readdir or Fdclosedir. // // https://github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/headers/public/__struct_dirent.h type Dirent struct { Ino uint64 Type uint8 } func (dirent *Dirent) Name() []byte { // The dirent C struct uses a flexible array member to indicate that the // directory name is laid out in memory right after the struct data: // // struct dirent { // ino_t d_ino; // unsigned char d_type; // char d_name[]; // }; name := (*[PATH_MAX]byte)(unsafe.Add(unsafe.Pointer(dirent), 9)) for i, c := range name { if c == 0 { return name[:i:i] } } return name[:] } func Fdopendir(fd int) (dir uintptr, err error) { d := libc_fdopendir(int32(fd)) if d == nil { err = getErrno() } return uintptr(d), err } func Fdclosedir(dir uintptr) (err error) { // Unlike on other unix platform where only closedir exists, wasi-libc has // fdclosedir which releases resources and returns the file descriptor but // does not close it. This is useful for us since we want to be able to keep // using it. n := libc_fdclosedir(unsafe.Pointer(dir)) if n < 0 { err = getErrno() } return } func Readdir(dir uintptr) (dirent *Dirent, err error) { // There might be a leftover errno value in the global variable, so we have // to clear it before calling readdir because we cannot know whether a nil // return means that we reached EOF or that an error occurred. libcErrno = 0 dirent = libc_readdir(unsafe.Pointer(dir)) if dirent == nil && libcErrno != 0 { err = getErrno() } return } func Stat(path string, p *Stat_t) (err error) { data := cstring(path) n := libc_stat(&data[0], unsafe.Pointer(p)) if n < 0 { err = getErrno() } return } func Fstat(fd int, p *Stat_t) (err error) { n := libc_fstat(int32(fd), unsafe.Pointer(p)) if n < 0 { err = getErrno() } return } func Lstat(path string, p *Stat_t) (err error) { data := cstring(path) n := libc_lstat(&data[0], unsafe.Pointer(p)) if n < 0 { err = getErrno() } return } func Pipe2(p []int, flags int) (err error) { return ENOSYS // TODO } func Chmod(path string, mode uint32) (err error) { // wasi does not have chmod, but there are tests that validate that calling // os.Chmod does not error (e.g. io/fs.TestIssue51617). // // We make a call to Lstat instead so we detect conditions like the path not // existing, but we don't honnor the request to modify the file permissions. stat := Stat_t{} return Lstat(path, &stat) } // TODO: should this return runtime.wasmPageSize? func Getpagesize() int { return libc_getpagesize() } type Utsname struct { Sysname [65]int8 Nodename [65]int8 Release [65]int8 Version [65]int8 Machine [65]int8 Domainname [65]int8 } //go:linkname faccessat syscall.Faccessat func faccessat(dirfd int, path string, mode uint32, flags int) (err error) { return ENOSYS } // Stub Utsname, needed because WASI pretends to be linux/arm. func Uname(buf *Utsname) (err error) type RawSockaddrInet4 struct { // stub } type RawSockaddrInet6 struct { // stub } func RandomGet(b []byte) error { if len(b) > 0 { libc_arc4random_buf(unsafe.Pointer(&b[0]), uint(len(b))) } return nil } // This is a stub, it is not functional. func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno) // This is a stub, it is not functional. func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno) // int getpagesize(void); // //export getpagesize func libc_getpagesize() int // int stat(const char *path, struct stat * buf); // //export stat func libc_stat(pathname *byte, ptr unsafe.Pointer) int32 // int fstat(int fd, struct stat * buf); // //export fstat func libc_fstat(fd int32, ptr unsafe.Pointer) int32 // int lstat(const char *path, struct stat * buf); // //export lstat func libc_lstat(pathname *byte, ptr unsafe.Pointer) int32 // int open(const char *pathname, int flags, mode_t mode); // //export open func libc_open(pathname *byte, flags int32, mode uint32) int32 // DIR *fdopendir(int); // //export fdopendir func libc_fdopendir(fd int32) unsafe.Pointer // int fdclosedir(DIR *); // //export fdclosedir func libc_fdclosedir(unsafe.Pointer) int32 // struct dirent *readdir(DIR *); // //export readdir func libc_readdir(unsafe.Pointer) *Dirent // void arc4random_buf(void *buf, size_t buflen); // //export arc4random_buf func libc_arc4random_buf(buf unsafe.Pointer, buflen uint) ================================================ FILE: src/syscall/syscall_linux.go ================================================ package syscall func Setuid(code int) (err error) func Setgid(code int) (err error) func Setreuid(ruid, euid int) (err error) func Setregid(rgid, egid int) (err error) func Setresuid(ruid, euid, suid int) (err error) func Setresgid(rgid, egid, sgid int) (err error) ================================================ FILE: src/syscall/syscall_nonhosted.go ================================================ //go:build baremetal || wasm_unknown package syscall import ( "internal/itoa" ) // Most code here has been copied from the Go sources: // https://github.com/golang/go/blob/go1.12/src/syscall/syscall_js.go // It has the following copyright note: // // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // A Signal is a number describing a process signal. // It implements the os.Signal interface. type Signal int const ( _ Signal = iota SIGCHLD SIGINT SIGKILL SIGTRAP SIGQUIT SIGTERM SIGILL SIGABRT SIGBUS SIGFPE SIGSEGV SIGPIPE ) func (s Signal) Signal() {} func (s Signal) String() string { if 0 <= s && int(s) < len(signals) { str := signals[s] if str != "" { return str } } return "signal " + itoa.Itoa(int(s)) } var signals = [...]string{} // File system const ( Stdin = 0 Stdout = 1 Stderr = 2 ) const ( O_RDONLY = 0 O_WRONLY = 1 O_RDWR = 2 O_CREAT = 0100 O_CREATE = O_CREAT O_TRUNC = 01000 O_APPEND = 02000 O_EXCL = 0200 O_SYNC = 010000 O_CLOEXEC = 0 ) // Dummy values to allow compiling tests // Dummy source: https://opensource.apple.com/source/xnu/xnu-7195.81.3/bsd/sys/mman.h.auto.html const ( PROT_NONE = 0x00 // no permissions PROT_READ = 0x01 // pages can be read PROT_WRITE = 0x02 // pages can be written PROT_EXEC = 0x04 // pages can be executed MAP_SHARED = 0x0001 // share changes MAP_PRIVATE = 0x0002 // changes are private MAP_FILE = 0x0000 // map from file (default) MAP_ANON = 0x1000 // allocated from memory, swap space MAP_ANONYMOUS = MAP_ANON ) func Open(path string, mode int, perm uint32) (fd int, err error) { return 0, ENOSYS } func Read(fd int, p []byte) (n int, err error) { return 0, ENOSYS } func Seek(fd int, offset int64, whence int) (off int64, err error) { return 0, ENOSYS } func Close(fd int) (err error) { return ENOSYS } // Processes type WaitStatus uint32 func (w WaitStatus) Exited() bool { return false } func (w WaitStatus) ExitStatus() int { return 0 } func (w WaitStatus) Signaled() bool { return false } func (w WaitStatus) Signal() Signal { return 0 } func (w WaitStatus) CoreDump() bool { return false } func (w WaitStatus) Stopped() bool { return false } func (w WaitStatus) Continued() bool { return false } func (w WaitStatus) StopSignal() Signal { return 0 } func (w WaitStatus) TrapCause() int { return 0 } // XXX made up type Rusage struct { Utime Timeval Stime Timeval } // XXX made up type ProcAttr struct { Dir string Env []string Files []uintptr Sys *SysProcAttr } type SysProcAttr struct { } func Getgroups() ([]int, error) { return []int{1}, nil } func Gettimeofday(tv *Timeval) error { return ENOSYS } func Kill(pid int, signum Signal) error { return ENOSYS } func Pipe2(p []int, flags int) (err error) { return ENOSYS // TODO } func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err error) { return 0, ENOSYS } func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) { return 0, 0, ENOSYS } func Wait4(pid int, wstatus *WaitStatus, options int, rusage *Rusage) (wpid int, err error) { return 0, ENOSYS } func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error) { return nil, ENOSYS } func Munmap(b []byte) (err error) { return ENOSYS } type Timeval struct { Sec int64 Usec int64 } func Getpagesize() int { // There is no right value to return here, but 4096 is a // common assumption when pagesize is unknown return 4096 } type RawSockaddrInet4 struct { // stub } type RawSockaddrInet6 struct { // stub } ================================================ FILE: src/syscall/syscall_unix.go ================================================ //go:build linux || unix package syscall func Exec(argv0 string, argv []string, envv []string) (err error) // The two SockaddrInet* structs have been copied from the Go source tree. type SockaddrInet4 struct { Port int Addr [4]byte raw RawSockaddrInet4 } type SockaddrInet6 struct { Port int ZoneId uint32 Addr [16]byte raw RawSockaddrInet6 } ================================================ FILE: src/syscall/tables_nonhosted.go ================================================ // Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build baremetal || nintendoswitch || wasm_unknown package syscall // TODO: generate with runtime/mknacl.sh, allow override with IRT. const ( sys_null = 1 sys_nameservice = 2 sys_dup = 8 sys_dup2 = 9 sys_open = 10 sys_close = 11 sys_read = 12 sys_write = 13 sys_lseek = 14 sys_stat = 16 sys_fstat = 17 sys_chmod = 18 sys_isatty = 19 sys_brk = 20 sys_mmap = 21 sys_munmap = 22 sys_getdents = 23 sys_mprotect = 24 sys_list_mappings = 25 sys_exit = 30 sys_getpid = 31 sys_sched_yield = 32 sys_sysconf = 33 sys_gettimeofday = 40 sys_clock = 41 sys_nanosleep = 42 sys_clock_getres = 43 sys_clock_gettime = 44 sys_mkdir = 45 sys_rmdir = 46 sys_chdir = 47 sys_getcwd = 48 sys_unlink = 49 sys_imc_makeboundsock = 60 sys_imc_accept = 61 sys_imc_connect = 62 sys_imc_sendmsg = 63 sys_imc_recvmsg = 64 sys_imc_mem_obj_create = 65 sys_imc_socketpair = 66 sys_mutex_create = 70 sys_mutex_lock = 71 sys_mutex_trylock = 72 sys_mutex_unlock = 73 sys_cond_create = 74 sys_cond_wait = 75 sys_cond_signal = 76 sys_cond_broadcast = 77 sys_cond_timed_wait_abs = 79 sys_thread_create = 80 sys_thread_exit = 81 sys_tls_init = 82 sys_thread_nice = 83 sys_tls_get = 84 sys_second_tls_set = 85 sys_second_tls_get = 86 sys_exception_handler = 87 sys_exception_stack = 88 sys_exception_clear_flag = 89 sys_sem_create = 100 sys_sem_wait = 101 sys_sem_post = 102 sys_sem_get_value = 103 sys_dyncode_create = 104 sys_dyncode_modify = 105 sys_dyncode_delete = 106 sys_test_infoleak = 109 sys_test_crash = 110 sys_test_syscall_1 = 111 sys_test_syscall_2 = 112 sys_futex_wait_abs = 120 sys_futex_wake = 121 sys_pread = 130 sys_pwrite = 131 sys_truncate = 140 sys_lstat = 141 sys_link = 142 sys_rename = 143 sys_symlink = 144 sys_access = 145 sys_readlink = 146 sys_utimes = 147 sys_get_random_bytes = 150 ) // TODO: Auto-generate some day. (Hard-coded in binaries so not likely to change.) const ( // native_client/src/trusted/service_runtime/include/sys/errno.h // The errors are mainly copied from Linux. EPERM Errno = 1 /* Operation not permitted */ ENOENT Errno = 2 /* No such file or directory */ ESRCH Errno = 3 /* No such process */ EINTR Errno = 4 /* Interrupted system call */ EIO Errno = 5 /* I/O error */ ENXIO Errno = 6 /* No such device or address */ E2BIG Errno = 7 /* Argument list too long */ ENOEXEC Errno = 8 /* Exec format error */ EBADF Errno = 9 /* Bad file number */ ECHILD Errno = 10 /* No child processes */ EAGAIN Errno = 11 /* Try again */ ENOMEM Errno = 12 /* Out of memory */ EACCES Errno = 13 /* Permission denied */ EFAULT Errno = 14 /* Bad address */ EBUSY Errno = 16 /* Device or resource busy */ EEXIST Errno = 17 /* File exists */ EXDEV Errno = 18 /* Cross-device link */ ENODEV Errno = 19 /* No such device */ ENOTDIR Errno = 20 /* Not a directory */ EISDIR Errno = 21 /* Is a directory */ EINVAL Errno = 22 /* Invalid argument */ ENFILE Errno = 23 /* File table overflow */ EMFILE Errno = 24 /* Too many open files */ ENOTTY Errno = 25 /* Not a typewriter */ EFBIG Errno = 27 /* File too large */ ENOSPC Errno = 28 /* No space left on device */ ESPIPE Errno = 29 /* Illegal seek */ EROFS Errno = 30 /* Read-only file system */ EMLINK Errno = 31 /* Too many links */ EPIPE Errno = 32 /* Broken pipe */ ENAMETOOLONG Errno = 36 /* File name too long */ ENOSYS Errno = 38 /* Function not implemented */ EDQUOT Errno = 122 /* Quota exceeded */ EDOM Errno = 33 /* Math arg out of domain of func */ ERANGE Errno = 34 /* Math result not representable */ EDEADLK Errno = 35 /* Deadlock condition */ ENOLCK Errno = 37 /* No record locks available */ ENOTEMPTY Errno = 39 /* Directory not empty */ ELOOP Errno = 40 /* Too many symbolic links */ ENOMSG Errno = 42 /* No message of desired type */ EIDRM Errno = 43 /* Identifier removed */ ECHRNG Errno = 44 /* Channel number out of range */ EL2NSYNC Errno = 45 /* Level 2 not synchronized */ EL3HLT Errno = 46 /* Level 3 halted */ EL3RST Errno = 47 /* Level 3 reset */ ELNRNG Errno = 48 /* Link number out of range */ EUNATCH Errno = 49 /* Protocol driver not attached */ ENOCSI Errno = 50 /* No CSI structure available */ EL2HLT Errno = 51 /* Level 2 halted */ EBADE Errno = 52 /* Invalid exchange */ EBADR Errno = 53 /* Invalid request descriptor */ EXFULL Errno = 54 /* Exchange full */ ENOANO Errno = 55 /* No anode */ EBADRQC Errno = 56 /* Invalid request code */ EBADSLT Errno = 57 /* Invalid slot */ EDEADLOCK Errno = EDEADLK /* File locking deadlock error */ EBFONT Errno = 59 /* Bad font file fmt */ ENOSTR Errno = 60 /* Device not a stream */ ENODATA Errno = 61 /* No data (for no delay io) */ ETIME Errno = 62 /* Timer expired */ ENOSR Errno = 63 /* Out of streams resources */ ENONET Errno = 64 /* Machine is not on the network */ ENOPKG Errno = 65 /* Package not installed */ EREMOTE Errno = 66 /* The object is remote */ ENOLINK Errno = 67 /* The link has been severed */ EADV Errno = 68 /* Advertise error */ ESRMNT Errno = 69 /* Srmount error */ ECOMM Errno = 70 /* Communication error on send */ EPROTO Errno = 71 /* Protocol error */ EMULTIHOP Errno = 72 /* Multihop attempted */ EDOTDOT Errno = 73 /* Cross mount point (not really error) */ EBADMSG Errno = 74 /* Trying to read unreadable message */ EOVERFLOW Errno = 75 /* Value too large for defined data type */ ENOTUNIQ Errno = 76 /* Given log. name not unique */ EBADFD Errno = 77 /* f.d. invalid for this operation */ EREMCHG Errno = 78 /* Remote address changed */ ELIBACC Errno = 79 /* Can't access a needed shared lib */ ELIBBAD Errno = 80 /* Accessing a corrupted shared lib */ ELIBSCN Errno = 81 /* .lib section in a.out corrupted */ ELIBMAX Errno = 82 /* Attempting to link in too many libs */ ELIBEXEC Errno = 83 /* Attempting to exec a shared library */ EILSEQ Errno = 84 EUSERS Errno = 87 ENOTSOCK Errno = 88 /* Socket operation on non-socket */ EDESTADDRREQ Errno = 89 /* Destination address required */ EMSGSIZE Errno = 90 /* Message too long */ EPROTOTYPE Errno = 91 /* Protocol wrong type for socket */ ENOPROTOOPT Errno = 92 /* Protocol not available */ EPROTONOSUPPORT Errno = 93 /* Unknown protocol */ ESOCKTNOSUPPORT Errno = 94 /* Socket type not supported */ EOPNOTSUPP Errno = 95 /* Operation not supported on transport endpoint */ EPFNOSUPPORT Errno = 96 /* Protocol family not supported */ EAFNOSUPPORT Errno = 97 /* Address family not supported by protocol family */ EADDRINUSE Errno = 98 /* Address already in use */ EADDRNOTAVAIL Errno = 99 /* Address not available */ ENETDOWN Errno = 100 /* Network interface is not configured */ ENETUNREACH Errno = 101 /* Network is unreachable */ ENETRESET Errno = 102 ECONNABORTED Errno = 103 /* Connection aborted */ ECONNRESET Errno = 104 /* Connection reset by peer */ ENOBUFS Errno = 105 /* No buffer space available */ EISCONN Errno = 106 /* Socket is already connected */ ENOTCONN Errno = 107 /* Socket is not connected */ ESHUTDOWN Errno = 108 /* Can't send after socket shutdown */ ETOOMANYREFS Errno = 109 ETIMEDOUT Errno = 110 /* Connection timed out */ ECONNREFUSED Errno = 111 /* Connection refused */ EHOSTDOWN Errno = 112 /* Host is down */ EHOSTUNREACH Errno = 113 /* Host is unreachable */ EALREADY Errno = 114 /* Socket already connected */ EINPROGRESS Errno = 115 /* Connection already in progress */ ESTALE Errno = 116 ENOTSUP Errno = EOPNOTSUPP /* Not supported */ ENOMEDIUM Errno = 123 /* No medium (in tape drive) */ ECANCELED Errno = 125 /* Operation canceled. */ ELBIN Errno = 2048 /* Inode is remote (not really error) */ EFTYPE Errno = 2049 /* Inappropriate file type or format */ ENMFILE Errno = 2050 /* No more files */ EPROCLIM Errno = 2051 ENOSHARE Errno = 2052 /* No such host or network path */ ECASECLASH Errno = 2053 /* Filename exists with different case */ EWOULDBLOCK Errno = EAGAIN /* Operation would block */ ) // Do the interface allocations only once for common // Errno values. var ( errEAGAIN error = EAGAIN errEINVAL error = EINVAL errENOENT error = ENOENT ) // errnoErr returns common boxed Errno values, to prevent // allocations at runtime. func errnoErr(e Errno) error { switch e { case 0: return nil case EAGAIN: return errEAGAIN case EINVAL: return errEINVAL case ENOENT: return errENOENT } return e } var errnoByCode = map[string]Errno{ "EPERM": EPERM, "ENOENT": ENOENT, "ESRCH": ESRCH, "EINTR": EINTR, "EIO": EIO, "ENXIO": ENXIO, "E2BIG": E2BIG, "ENOEXEC": ENOEXEC, "EBADF": EBADF, "ECHILD": ECHILD, "EAGAIN": EAGAIN, "ENOMEM": ENOMEM, "EACCES": EACCES, "EFAULT": EFAULT, "EBUSY": EBUSY, "EEXIST": EEXIST, "EXDEV": EXDEV, "ENODEV": ENODEV, "ENOTDIR": ENOTDIR, "EISDIR": EISDIR, "EINVAL": EINVAL, "ENFILE": ENFILE, "EMFILE": EMFILE, "ENOTTY": ENOTTY, "EFBIG": EFBIG, "ENOSPC": ENOSPC, "ESPIPE": ESPIPE, "EROFS": EROFS, "EMLINK": EMLINK, "EPIPE": EPIPE, "ENAMETOOLONG": ENAMETOOLONG, "ENOSYS": ENOSYS, "EDQUOT": EDQUOT, "EDOM": EDOM, "ERANGE": ERANGE, "EDEADLK": EDEADLK, "ENOLCK": ENOLCK, "ENOTEMPTY": ENOTEMPTY, "ELOOP": ELOOP, "ENOMSG": ENOMSG, "EIDRM": EIDRM, "ECHRNG": ECHRNG, "EL2NSYNC": EL2NSYNC, "EL3HLT": EL3HLT, "EL3RST": EL3RST, "ELNRNG": ELNRNG, "EUNATCH": EUNATCH, "ENOCSI": ENOCSI, "EL2HLT": EL2HLT, "EBADE": EBADE, "EBADR": EBADR, "EXFULL": EXFULL, "ENOANO": ENOANO, "EBADRQC": EBADRQC, "EBADSLT": EBADSLT, "EDEADLOCK": EDEADLOCK, "EBFONT": EBFONT, "ENOSTR": ENOSTR, "ENODATA": ENODATA, "ETIME": ETIME, "ENOSR": ENOSR, "ENONET": ENONET, "ENOPKG": ENOPKG, "EREMOTE": EREMOTE, "ENOLINK": ENOLINK, "EADV": EADV, "ESRMNT": ESRMNT, "ECOMM": ECOMM, "EPROTO": EPROTO, "EMULTIHOP": EMULTIHOP, "EDOTDOT": EDOTDOT, "EBADMSG": EBADMSG, "EOVERFLOW": EOVERFLOW, "ENOTUNIQ": ENOTUNIQ, "EBADFD": EBADFD, "EREMCHG": EREMCHG, "ELIBACC": ELIBACC, "ELIBBAD": ELIBBAD, "ELIBSCN": ELIBSCN, "ELIBMAX": ELIBMAX, "ELIBEXEC": ELIBEXEC, "EILSEQ": EILSEQ, "EUSERS": EUSERS, "ENOTSOCK": ENOTSOCK, "EDESTADDRREQ": EDESTADDRREQ, "EMSGSIZE": EMSGSIZE, "EPROTOTYPE": EPROTOTYPE, "ENOPROTOOPT": ENOPROTOOPT, "EPROTONOSUPPORT": EPROTONOSUPPORT, "ESOCKTNOSUPPORT": ESOCKTNOSUPPORT, "EOPNOTSUPP": EOPNOTSUPP, "EPFNOSUPPORT": EPFNOSUPPORT, "EAFNOSUPPORT": EAFNOSUPPORT, "EADDRINUSE": EADDRINUSE, "EADDRNOTAVAIL": EADDRNOTAVAIL, "ENETDOWN": ENETDOWN, "ENETUNREACH": ENETUNREACH, "ENETRESET": ENETRESET, "ECONNABORTED": ECONNABORTED, "ECONNRESET": ECONNRESET, "ENOBUFS": ENOBUFS, "EISCONN": EISCONN, "ENOTCONN": ENOTCONN, "ESHUTDOWN": ESHUTDOWN, "ETOOMANYREFS": ETOOMANYREFS, "ETIMEDOUT": ETIMEDOUT, "ECONNREFUSED": ECONNREFUSED, "EHOSTDOWN": EHOSTDOWN, "EHOSTUNREACH": EHOSTUNREACH, "EALREADY": EALREADY, "EINPROGRESS": EINPROGRESS, "ESTALE": ESTALE, "ENOTSUP": ENOTSUP, "ENOMEDIUM": ENOMEDIUM, "ECANCELED": ECANCELED, "ELBIN": ELBIN, "EFTYPE": EFTYPE, "ENMFILE": ENMFILE, "EPROCLIM": EPROCLIM, "ENOSHARE": ENOSHARE, "ECASECLASH": ECASECLASH, "EWOULDBLOCK": EWOULDBLOCK, } ================================================ FILE: src/testing/benchmark.go ================================================ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // // This file has been modified for use by the TinyGo compiler. package testing import ( "flag" "fmt" "io" "math" "os" "runtime" "strconv" "strings" "time" ) func initBenchmarkFlags() { matchBenchmarks = flag.String("test.bench", "", "run only benchmarks matching `regexp`") benchmarkMemory = flag.Bool("test.benchmem", false, "print memory allocations for benchmarks") flag.Var(&benchTime, "test.benchtime", "run each benchmark for duration `d`") } var ( matchBenchmarks *string benchmarkMemory *bool benchTime = benchTimeFlag{d: 1 * time.Second} // changed during test of testing package ) type benchTimeFlag struct { d time.Duration n int } func (f *benchTimeFlag) String() string { if f.n > 0 { return fmt.Sprintf("%dx", f.n) } return time.Duration(f.d).String() } func (f *benchTimeFlag) Set(s string) error { if strings.HasSuffix(s, "x") { n, err := strconv.ParseInt(s[:len(s)-1], 10, 0) if err != nil || n <= 0 { return fmt.Errorf("invalid count") } *f = benchTimeFlag{n: int(n)} return nil } d, err := time.ParseDuration(s) if err != nil || d <= 0 { return fmt.Errorf("invalid duration") } *f = benchTimeFlag{d: d} return nil } // InternalBenchmark is an internal type but exported because it is cross-package; // it is part of the implementation of the "go test" command. type InternalBenchmark struct { Name string F func(b *B) } // B is a type passed to Benchmark functions to manage benchmark // timing and to specify the number of iterations to run. // // A benchmark ends when its Benchmark function returns or calls any of the methods // FailNow, Fatal, Fatalf, SkipNow, Skip, or Skipf. Those methods must be called // only from the goroutine running the Benchmark function. // The other reporting methods, such as the variations of Log and Error, // may be called simultaneously from multiple goroutines. // // Like in tests, benchmark logs are accumulated during execution // and dumped to standard output when done. Unlike in tests, benchmark logs // are always printed, so as not to hide output whose existence may be // affecting benchmark results. type B struct { common context *benchContext N int benchFunc func(b *B) bytes int64 missingBytes bool // one of the subbenchmarks does not have bytes set. benchTime benchTimeFlag timerOn bool result BenchmarkResult // report memory statistics showAllocResult bool // initial state of MemStats.Mallocs and MemStats.TotalAlloc startAllocs uint64 startBytes uint64 // net total after running benchmar netAllocs uint64 netBytes uint64 } // StartTimer starts timing a test. This function is called automatically // before a benchmark starts, but it can also be used to resume timing after // a call to StopTimer. func (b *B) StartTimer() { if !b.timerOn { b.start = time.Now() b.timerOn = true var mstats runtime.MemStats runtime.ReadMemStats(&mstats) b.startAllocs = mstats.Mallocs b.startBytes = mstats.TotalAlloc } } // StopTimer stops timing a test. This can be used to pause the timer // while performing complex initialization that you don't // want to measure. func (b *B) StopTimer() { if b.timerOn { b.duration += time.Since(b.start) b.timerOn = false var mstats runtime.MemStats runtime.ReadMemStats(&mstats) b.netAllocs += mstats.Mallocs - b.startAllocs b.netBytes += mstats.TotalAlloc - b.startBytes } } // ResetTimer zeroes the elapsed benchmark time and memory allocation counters // and deletes user-reported metrics. func (b *B) ResetTimer() { if b.timerOn { b.start = time.Now() var mstats runtime.MemStats runtime.ReadMemStats(&mstats) b.startAllocs = mstats.Mallocs b.startBytes = mstats.TotalAlloc } b.duration = 0 b.netAllocs = 0 b.netBytes = 0 } // SetBytes records the number of bytes processed in a single operation. // If this is called, the benchmark will report ns/op and MB/s. func (b *B) SetBytes(n int64) { b.bytes = n } // ReportAllocs enables malloc statistics for this benchmark. // It is equivalent to setting -test.benchmem, but it only affects the // benchmark function that calls ReportAllocs. func (b *B) ReportAllocs() { b.showAllocResult = true } // runN runs a single benchmark for the specified number of iterations. func (b *B) runN(n int) { b.N = n runtime.GC() b.ResetTimer() b.StartTimer() b.benchFunc(b) b.StopTimer() } func min(x, y int64) int64 { if x > y { return y } return x } func max(x, y int64) int64 { if x < y { return y } return x } // run1 runs the first iteration of benchFunc. It reports whether more // iterations of this benchmarks should be run. func (b *B) run1() bool { if ctx := b.context; ctx != nil { // Extend maxLen, if needed. if n := len(b.name); n > ctx.maxLen { ctx.maxLen = n + 8 // Add additional slack to avoid too many jumps in size. } } b.runN(1) return !b.hasSub } // run executes the benchmark. func (b *B) run() { if b.context != nil { // Running go test --test.bench b.processBench(b.context) // calls doBench and prints results } else { // Running func Benchmark. b.doBench() } } func (b *B) doBench() BenchmarkResult { // in upstream, this uses a goroutine b.launch() return b.result } // launch launches the benchmark function. It gradually increases the number // of benchmark iterations until the benchmark runs for the requested benchtime. // run1 must have been called on b. func (b *B) launch() { // Run the benchmark for at least the specified amount of time. if b.benchTime.n > 0 { b.runN(b.benchTime.n) } else { d := b.benchTime.d b.failed = false b.duration = 0 for n := int64(1); !b.failed && b.duration < d && n < 1e9; { last := n // Predict required iterations. goalns := d.Nanoseconds() prevIters := int64(b.N) prevns := b.duration.Nanoseconds() if prevns <= 0 { // Round up, to avoid div by zero. prevns = 1 } // Order of operations matters. // For very fast benchmarks, prevIters ~= prevns. // If you divide first, you get 0 or 1, // which can hide an order of magnitude in execution time. // So multiply first, then divide. n = goalns * prevIters / prevns // Run more iterations than we think we'll need (1.2x). n += n / 5 // Don't grow too fast in case we had timing errors previously. n = min(n, 100*last) // Be sure to run at least one more than last time. n = max(n, last+1) // Don't run more than 1e9 times. (This also keeps n in int range on 32 bit platforms.) n = min(n, 1e9) b.runN(int(n)) } } b.result = BenchmarkResult{b.N, b.duration, b.bytes, b.netAllocs, b.netBytes} } // BenchmarkResult contains the results of a benchmark run. type BenchmarkResult struct { N int // The number of iterations. T time.Duration // The total time taken. Bytes int64 // Bytes processed in one iteration. MemAllocs uint64 // The total number of memory allocations. MemBytes uint64 // The total number of bytes allocated. } // NsPerOp returns the "ns/op" metric. func (r BenchmarkResult) NsPerOp() int64 { if r.N <= 0 { return 0 } return r.T.Nanoseconds() / int64(r.N) } // mbPerSec returns the "MB/s" metric. func (r BenchmarkResult) mbPerSec() float64 { if r.Bytes <= 0 || r.T <= 0 || r.N <= 0 { return 0 } return (float64(r.Bytes) * float64(r.N) / 1e6) / r.T.Seconds() } // AllocsPerOp returns the "allocs/op" metric, // which is calculated as r.MemAllocs / r.N. func (r BenchmarkResult) AllocsPerOp() int64 { if r.N <= 0 { return 0 } return int64(r.MemAllocs) / int64(r.N) } // AllocedBytesPerOp returns the "B/op" metric, // which is calculated as r.MemBytes / r.N. func (r BenchmarkResult) AllocedBytesPerOp() int64 { if r.N <= 0 { return 0 } return int64(r.MemBytes) / int64(r.N) } // String returns a summary of the benchmark results. // It follows the benchmark result line format from // https://golang.org/design/14313-benchmark-format, not including the // benchmark name. // Extra metrics override built-in metrics of the same name. // String does not include allocs/op or B/op, since those are reported // by MemString. func (r BenchmarkResult) String() string { buf := new(strings.Builder) fmt.Fprintf(buf, "%8d", r.N) // Get ns/op as a float. ns := float64(r.T.Nanoseconds()) / float64(r.N) if ns != 0 { buf.WriteByte('\t') prettyPrint(buf, ns, "ns/op") } if mbs := r.mbPerSec(); mbs != 0 { fmt.Fprintf(buf, "\t%7.2f MB/s", mbs) } return buf.String() } // MemString returns r.AllocedBytesPerOp and r.AllocsPerOp in the same format as 'go test'. func (r BenchmarkResult) MemString() string { return fmt.Sprintf("%8d B/op\t%8d allocs/op", r.AllocedBytesPerOp(), r.AllocsPerOp()) } func prettyPrint(w io.Writer, x float64, unit string) { // Print all numbers with 10 places before the decimal point // and small numbers with four sig figs. Field widths are // chosen to fit the whole part in 10 places while aligning // the decimal point of all fractional formats. var format string switch y := math.Abs(x); { case y == 0 || y >= 999.95: format = "%10.0f %s" case y >= 99.995: format = "%12.1f %s" case y >= 9.9995: format = "%13.2f %s" case y >= 0.99995: format = "%14.3f %s" case y >= 0.099995: format = "%15.4f %s" case y >= 0.0099995: format = "%16.5f %s" case y >= 0.00099995: format = "%17.6f %s" default: format = "%18.7f %s" } fmt.Fprintf(w, format, x, unit) } type benchContext struct { match *matcher maxLen int // The largest recorded benchmark name. } func runBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) bool { // If no flag was specified, don't run benchmarks. if len(*matchBenchmarks) == 0 { return true } ctx := &benchContext{ match: newMatcher(matchString, *matchBenchmarks, "-test.bench", flagSkipRegexp), } var bs []InternalBenchmark for _, Benchmark := range benchmarks { if _, matched, _ := ctx.match.fullName(nil, Benchmark.Name); matched { bs = append(bs, Benchmark) benchName := Benchmark.Name if l := len(benchName); l > ctx.maxLen { ctx.maxLen = l } } } main := &B{ common: common{ output: &logger{}, name: "Main", }, benchTime: benchTime, benchFunc: func(b *B) { for _, Benchmark := range bs { b.Run(Benchmark.Name, Benchmark.F) } }, context: ctx, } main.runN(1) return true } // processBench runs bench b and prints the results. func (b *B) processBench(ctx *benchContext) { benchName := b.name for i := 0; i < flagCount; i++ { if ctx != nil { fmt.Printf("%-*s\t", ctx.maxLen, benchName) } r := b.doBench() if b.failed { // The output could be very long here, but probably isn't. // We print it all, regardless, because we don't want to trim the reason // the benchmark failed. fmt.Printf("--- FAIL: %s\n%s", benchName, "") // b.output) return } if ctx != nil { results := r.String() if *benchmarkMemory || b.showAllocResult { results += "\t" + r.MemString() } fmt.Println(results) // Print any benchmark output if b.output.Len() > 0 { fmt.Printf("--- BENCH: %s\n", benchName) b.output.WriteTo(os.Stdout) } } } } // Run benchmarks f as a subbenchmark with the given name. It reports // true if the subbenchmark succeeded. // // A subbenchmark is like any other benchmark. A benchmark that calls Run at // least once will not be measured itself and will be called once with N=1. func (b *B) Run(name string, f func(b *B)) bool { benchName, ok, partial := b.name, true, false if b.context != nil { benchName, ok, partial = b.context.match.fullName(&b.common, name) } if !ok { return true } b.hasSub = true sub := &B{ common: common{ output: &logger{}, name: benchName, level: b.level + 1, }, benchFunc: f, benchTime: b.benchTime, context: b.context, } if partial { // Partial name match, like -bench=X/Y matching BenchmarkX. // Only process sub-benchmarks, if any. sub.hasSub = true } if sub.run1() { sub.run() } b.add(sub.result) return !sub.failed } // add simulates running benchmarks in sequence in a single iteration. It is // used to give some meaningful results in case func Benchmark is used in // combination with Run. func (b *B) add(other BenchmarkResult) { r := &b.result // The aggregated BenchmarkResults resemble running all subbenchmarks as // in sequence in a single benchmark. r.N = 1 r.T += time.Duration(other.NsPerOp()) if other.Bytes == 0 { // Summing Bytes is meaningless in aggregate if not all subbenchmarks // set it. b.missingBytes = true r.Bytes = 0 } if !b.missingBytes { r.Bytes += other.Bytes } } // A PB is used by RunParallel for running parallel benchmarks. type PB struct { } // Next reports whether there are more iterations to execute. func (pb *PB) Next() bool { return false } // RunParallel runs a benchmark in parallel. // // Not implemented func (b *B) RunParallel(body func(*PB)) { return } func (b *B) Loop() bool { panic("unimplemented: testing.B.Loop") } // Benchmark benchmarks a single function. It is useful for creating // custom benchmarks that do not use the "go test" command. // // If f calls Run, the result will be an estimate of running all its // subbenchmarks that don't call Run in sequence in a single benchmark. func Benchmark(f func(b *B)) BenchmarkResult { b := &B{ benchFunc: f, benchTime: benchTime, } if b.run1() { b.run() } return b.result } ================================================ FILE: src/testing/benchmark_test.go ================================================ // Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package testing_test import ( "testing" ) var buf = make([]byte, 13579) func NonASCII(b []byte, i int, offset int) int { for i = offset; i < len(b)+offset; i++ { if b[i%len(b)] >= 0x80 { break } } return i } func BenchmarkFastNonASCII(b *testing.B) { var val int for i := 0; i < b.N; i++ { val += NonASCII(buf, 0, 0) } } func BenchmarkSlowNonASCII(b *testing.B) { var val int for i := 0; i < b.N; i++ { val += NonASCII(buf, 0, 0) val += NonASCII(buf, 0, 1) } } // TestBenchmark simply uses Benchmark twice and makes sure it does not crash. func TestBenchmark(t *testing.T) { // FIXME: reduce runtime from the current 3 seconds. rslow := testing.Benchmark(BenchmarkSlowNonASCII) rfast := testing.Benchmark(BenchmarkFastNonASCII) tslow := rslow.NsPerOp() tfast := rfast.NsPerOp() // Be exceedingly forgiving; do not fail even if system gets busy. speedup := float64(tslow) / float64(tfast) if speedup < 0.3 { t.Errorf("Expected speedup >= 0.3, got %f", speedup) } } func BenchmarkSub(b *testing.B) { b.Run("Fast", func(b *testing.B) { BenchmarkFastNonASCII(b) }) b.Run("Slow", func(b *testing.B) { BenchmarkSlowNonASCII(b) }) } ================================================ FILE: src/testing/doc.go ================================================ package testing /* This is a sad stub of the upstream testing package because it doesn't compile with tinygo right now. */ ================================================ FILE: src/testing/fuzz.go ================================================ package testing import ( "errors" "fmt" "reflect" "time" ) // InternalFuzzTarget is an internal type but exported because it is // cross-package; it is part of the implementation of the "go test" command. type InternalFuzzTarget struct { Name string Fn func(f *F) } // F is a type passed to fuzz tests. // // Fuzz tests run generated inputs against a provided fuzz target, which can // find and report potential bugs in the code being tested. // // A fuzz test runs the seed corpus by default, which includes entries provided // by (*F).Add and entries in the testdata/fuzz/ directory. After // any necessary setup and calls to (*F).Add, the fuzz test must then call // (*F).Fuzz to provide the fuzz target. See the testing package documentation // for an example, and see the F.Fuzz and F.Add method documentation for // details. // // *F methods can only be called before (*F).Fuzz. Once the test is // executing the fuzz target, only (*T) methods can be used. The only *F methods // that are allowed in the (*F).Fuzz function are (*F).Failed and (*F).Name. type F struct { common fuzzContext *fuzzContext testContext *testContext // inFuzzFn is true when the fuzz function is running. Most F methods cannot // be called when inFuzzFn is true. inFuzzFn bool // corpus is a set of seed corpus entries, added with F.Add and loaded // from testdata. corpus []corpusEntry result fuzzResult fuzzCalled bool } // corpusEntry is an alias to the same type as internal/fuzz.CorpusEntry. // We use a type alias because we don't want to export this type, and we can't // import internal/fuzz from testing. type corpusEntry = struct { Parent string Path string Data []byte Values []interface{} Generation int IsSeed bool } // Add will add the arguments to the seed corpus for the fuzz test. This will be // a no-op if called after or within the fuzz target, and args must match the // arguments for the fuzz target. func (f *F) Add(args ...interface{}) { var values []interface{} for i := range args { if t := reflect.TypeOf(args[i]); !supportedTypes[t] { panic(fmt.Sprintf("testing: unsupported type to Add %v", t)) } values = append(values, args[i]) } f.corpus = append(f.corpus, corpusEntry{Values: values, IsSeed: true, Path: fmt.Sprintf("seed#%d", len(f.corpus))}) } // supportedTypes represents all of the supported types which can be fuzzed. var supportedTypes = map[reflect.Type]bool{ reflect.TypeOf(([]byte)("")): true, reflect.TypeOf((string)("")): true, reflect.TypeOf((bool)(false)): true, reflect.TypeOf((byte)(0)): true, reflect.TypeOf((rune)(0)): true, reflect.TypeOf((float32)(0)): true, reflect.TypeOf((float64)(0)): true, reflect.TypeOf((int)(0)): true, reflect.TypeOf((int8)(0)): true, reflect.TypeOf((int16)(0)): true, reflect.TypeOf((int32)(0)): true, reflect.TypeOf((int64)(0)): true, reflect.TypeOf((uint)(0)): true, reflect.TypeOf((uint8)(0)): true, reflect.TypeOf((uint16)(0)): true, reflect.TypeOf((uint32)(0)): true, reflect.TypeOf((uint64)(0)): true, } // Fuzz runs the fuzz function, ff, for fuzz testing. If ff fails for a set of // arguments, those arguments will be added to the seed corpus. // // ff must be a function with no return value whose first argument is *T and // whose remaining arguments are the types to be fuzzed. // For example: // // f.Fuzz(func(t *testing.T, b []byte, i int) { ... }) // // The following types are allowed: []byte, string, bool, byte, rune, float32, // float64, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64. // More types may be supported in the future. // // ff must not call any *F methods, e.g. (*F).Log, (*F).Error, (*F).Skip. Use // the corresponding *T method instead. The only *F methods that are allowed in // the (*F).Fuzz function are (*F).Failed and (*F).Name. // // This function should be fast and deterministic, and its behavior should not // depend on shared state. No mutatable input arguments, or pointers to them, // should be retained between executions of the fuzz function, as the memory // backing them may be mutated during a subsequent invocation. ff must not // modify the underlying data of the arguments provided by the fuzzing engine. // // When fuzzing, F.Fuzz does not return until a problem is found, time runs out // (set with -fuzztime), or the test process is interrupted by a signal. F.Fuzz // should be called exactly once, unless F.Skip or F.Fail is called beforehand. func (f *F) Fuzz(ff interface{}) { f.failed = true f.result.N = 0 f.result.T = 0 f.result.Error = errors.New("operation not implemented") return } // fuzzContext holds fields common to all fuzz tests. type fuzzContext struct { deps testDeps mode fuzzMode } type fuzzMode uint8 // fuzzResult contains the results of a fuzz run. type fuzzResult struct { N int // The number of iterations. T time.Duration // The total time taken. Error error // Error is the error from the failing input } ================================================ FILE: src/testing/is_baremetal.go ================================================ // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // //go:build baremetal package testing const isBaremetal = true ================================================ FILE: src/testing/is_not_baremetal.go ================================================ // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // //go:build !baremetal package testing const isBaremetal = false ================================================ FILE: src/testing/match.go ================================================ // Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package testing import ( "fmt" "os" "strconv" "strings" "sync" ) // matcher sanitizes, uniques, and filters names of subtests and subbenchmarks. type matcher struct { filter filterMatch skip filterMatch matchFunc func(pat, str string) (bool, error) mu sync.Mutex // subNames is used to deduplicate subtest names. // Each key is the subtest name joined to the deduplicated name of the parent test. // Each value is the count of the number of occurrences of the given subtest name // already seen. subNames map[string]int32 } type filterMatch interface { // matches checks the name against the receiver's pattern strings using the // given match function. matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool) // verify checks that the receiver's pattern strings are valid filters by // calling the given match function. verify(name string, matchString func(pat, str string) (bool, error)) error } // simpleMatch matches a test name if all of the pattern strings match in // sequence. type simpleMatch []string // alternationMatch matches a test name if one of the alternations match. type alternationMatch []filterMatch // TODO: fix test_main to avoid race and improve caching, also allowing to // eliminate this Mutex. var matchMutex sync.Mutex func allMatcher() *matcher { return newMatcher(nil, "", "", "") } func newMatcher(matchString func(pat, str string) (bool, error), patterns, name, skips string) *matcher { if isBaremetal { matchString = fakeMatchString } var filter, skip filterMatch if patterns == "" { filter = simpleMatch{} // always partial true } else { filter = splitRegexp(patterns) if err := filter.verify(name, matchString); err != nil { fmt.Fprintf(os.Stderr, "testing: invalid regexp for %s\n", err) os.Exit(1) } } if skips == "" { skip = alternationMatch{} // always false } else { skip = splitRegexp(skips) if err := skip.verify("-test.skip", matchString); err != nil { fmt.Fprintf(os.Stderr, "testing: invalid regexp for %v\n", err) os.Exit(1) } } return &matcher{ filter: filter, skip: skip, matchFunc: matchString, subNames: map[string]int32{}, } } func (m *matcher) fullName(c *common, subname string) (name string, ok, partial bool) { name = subname m.mu.Lock() defer m.mu.Unlock() if c != nil && c.level > 0 { name = m.unique(c.name, rewrite(subname)) } matchMutex.Lock() defer matchMutex.Unlock() // We check the full array of paths each time to allow for the case that a pattern contains a '/'. elem := strings.Split(name, "/") // filter must match. // accept partial match that may produce full match later. ok, partial = m.filter.matches(elem, m.matchFunc) if !ok { return name, false, false } // skip must not match. // ignore partial match so we can get to more precise match later. skip, partialSkip := m.skip.matches(elem, m.matchFunc) if skip && !partialSkip { return name, false, false } return name, ok, partial } // clearSubNames clears the matcher's internal state, potentially freeing // memory. After this is called, T.Name may return the same strings as it did // for earlier subtests. func (m *matcher) clearSubNames() { m.mu.Lock() defer m.mu.Unlock() for key := range m.subNames { delete(m.subNames, key) } } func (m simpleMatch) matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool) { for i, s := range name { if i >= len(m) { break } if ok, _ := matchString(m[i], s); !ok { return false, false } } return true, len(name) < len(m) } func (m simpleMatch) verify(name string, matchString func(pat, str string) (bool, error)) error { for i, s := range m { m[i] = rewrite(s) } // Verify filters before doing any processing. for i, s := range m { if _, err := matchString(s, "non-empty"); err != nil { return fmt.Errorf("element %d of %s (%q): %s", i, name, s, err) } } return nil } func (m alternationMatch) matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool) { for _, m := range m { if ok, partial = m.matches(name, matchString); ok { return ok, partial } } return false, false } func (m alternationMatch) verify(name string, matchString func(pat, str string) (bool, error)) error { for i, m := range m { if err := m.verify(name, matchString); err != nil { return fmt.Errorf("alternation %d of %s", i, err) } } return nil } func splitRegexp(s string) filterMatch { a := make(simpleMatch, 0, strings.Count(s, "/")) b := make(alternationMatch, 0, strings.Count(s, "|")) cs := 0 cp := 0 for i := 0; i < len(s); { switch s[i] { case '[': cs++ case ']': if cs--; cs < 0 { // An unmatched ']' is legal. cs = 0 } case '(': if cs == 0 { cp++ } case ')': if cs == 0 { cp-- } case '\\': i++ case '/': if cs == 0 && cp == 0 { a = append(a, s[:i]) s = s[i+1:] i = 0 continue } case '|': if cs == 0 && cp == 0 { a = append(a, s[:i]) s = s[i+1:] i = 0 b = append(b, a) a = make(simpleMatch, 0, len(a)) continue } } i++ } a = append(a, s) if len(b) == 0 { return a } return append(b, a) } // unique creates a unique name for the given parent and subname by affixing it // with one or more counts, if necessary. func (m *matcher) unique(parent, subname string) string { base := parent + "/" + subname for { n := m.subNames[base] if n < 0 { panic("subtest count overflow") } m.subNames[base] = n + 1 if n == 0 && subname != "" { prefix, nn := parseSubtestNumber(base) if len(prefix) < len(base) && nn < m.subNames[prefix] { // This test is explicitly named like "parent/subname#NN", // and #NN was already used for the NNth occurrence of "parent/subname". // Loop to add a disambiguating suffix. continue } return base } name := fmt.Sprintf("%s#%02d", base, n) if m.subNames[name] != 0 { // This is the nth occurrence of base, but the name "parent/subname#NN" // collides with the first occurrence of a subtest *explicitly* named // "parent/subname#NN". Try the next number. continue } return name } } // parseSubtestNumber splits a subtest name into a "#%02d"-formatted int32 // suffix (if present), and a prefix preceding that suffix (always). func parseSubtestNumber(s string) (prefix string, nn int32) { i := strings.LastIndex(s, "#") if i < 0 { return s, 0 } prefix, suffix := s[:i], s[i+1:] if len(suffix) < 2 || (len(suffix) > 2 && suffix[0] == '0') { // Even if suffix is numeric, it is not a possible output of a "%02" format // string: it has either too few digits or too many leading zeroes. return s, 0 } if suffix == "00" { if !strings.HasSuffix(prefix, "/") { // We only use "#00" as a suffix for subtests named with the empty // string — it isn't a valid suffix if the subtest name is non-empty. return s, 0 } } n, err := strconv.ParseInt(suffix, 10, 32) if err != nil || n < 0 { return s, 0 } return prefix, int32(n) } // rewrite rewrites a subname to having only printable characters and no white // space. func rewrite(s string) string { b := []byte{} for _, r := range s { switch { case isSpace(r): b = append(b, '_') case !strconv.IsPrint(r): s := strconv.QuoteRune(r) b = append(b, s[1:len(s)-1]...) default: b = append(b, string(r)...) } } return string(b) } func isSpace(r rune) bool { if r < 0x2000 { switch r { // Note: not the same as Unicode Z class. case '\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0, 0x1680: return true } } else { if r <= 0x200a { return true } switch r { case 0x2028, 0x2029, 0x202f, 0x205f, 0x3000: return true } } return false } ================================================ FILE: src/testing/match_test.go ================================================ // Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package testing import ( "fmt" "reflect" "regexp" "strings" "unicode" ) // Verify that our IsSpace agrees with unicode.IsSpace. func TestIsSpace(t *T) { n := 0 for r := rune(0); r <= unicode.MaxRune; r++ { if isSpace(r) != unicode.IsSpace(r) { t.Errorf("IsSpace(%U)=%t incorrect", r, isSpace(r)) n++ if n > 10 { return } } } } func TestSplitRegexp(t *T) { res := func(s ...string) filterMatch { return simpleMatch(s) } alt := func(m ...filterMatch) filterMatch { return alternationMatch(m) } testCases := []struct { pattern string result filterMatch }{ // Correct patterns // If a regexp pattern is correct, all split regexps need to be correct // as well. {"", res("")}, {"/", res("", "")}, {"//", res("", "", "")}, {"A", res("A")}, {"A/B", res("A", "B")}, {"A/B/", res("A", "B", "")}, {"/A/B/", res("", "A", "B", "")}, {"[A]/(B)", res("[A]", "(B)")}, {"[/]/[/]", res("[/]", "[/]")}, {"[/]/[:/]", res("[/]", "[:/]")}, {"/]", res("", "]")}, {"]/", res("]", "")}, {"]/[/]", res("]", "[/]")}, {`([)/][(])`, res(`([)/][(])`)}, {"[(]/[)]", res("[(]", "[)]")}, {"A/B|C/D", alt(res("A", "B"), res("C", "D"))}, // Faulty patterns // Errors in original should produce at least one faulty regexp in results. {")/", res(")/")}, {")/(/)", res(")/(", ")")}, {"a[/)b", res("a[/)b")}, {"(/]", res("(/]")}, {"(/", res("(/")}, {"[/]/[/", res("[/]", "[/")}, {`\p{/}`, res(`\p{`, "}")}, {`\p/`, res(`\p`, "")}, {`[[:/:]]`, res(`[[:/:]]`)}, } for _, tc := range testCases { a := splitRegexp(tc.pattern) if !reflect.DeepEqual(a, tc.result) { t.Errorf("splitRegexp(%q) = %#v; want %#v", tc.pattern, a, tc.result) } // If there is any error in the pattern, one of the returned subpatterns // needs to have an error as well. if _, err := regexp.Compile(tc.pattern); err != nil { ok := true if err := a.verify("", regexp.MatchString); err != nil { ok = false } if ok { t.Errorf("%s: expected error in any of %q", tc.pattern, a) } } } } func TestMatcher(t *T) { testCases := []struct { pattern string skip string parent, sub string ok bool partial bool }{ // Behavior without subtests. {"", "", "", "TestFoo", true, false}, {"TestFoo", "", "", "TestFoo", true, false}, {"TestFoo/", "", "", "TestFoo", true, true}, {"TestFoo/bar/baz", "", "", "TestFoo", true, true}, {"TestFoo", "", "", "TestBar", false, false}, {"TestFoo/", "", "", "TestBar", false, false}, {"TestFoo/bar/baz", "", "", "TestBar/bar/baz", false, false}, {"", "TestBar", "", "TestFoo", true, false}, {"", "TestBar", "", "TestBar", false, false}, // Skipping a non-existent test doesn't change anything. {"", "TestFoo/skipped", "", "TestFoo", true, false}, {"TestFoo", "TestFoo/skipped", "", "TestFoo", true, false}, {"TestFoo/", "TestFoo/skipped", "", "TestFoo", true, true}, {"TestFoo/bar/baz", "TestFoo/skipped", "", "TestFoo", true, true}, {"TestFoo", "TestFoo/skipped", "", "TestBar", false, false}, {"TestFoo/", "TestFoo/skipped", "", "TestBar", false, false}, {"TestFoo/bar/baz", "TestFoo/skipped", "", "TestBar/bar/baz", false, false}, // with subtests {"", "", "TestFoo", "x", true, false}, {"TestFoo", "", "TestFoo", "x", true, false}, {"TestFoo/", "", "TestFoo", "x", true, false}, {"TestFoo/bar/baz", "", "TestFoo", "bar", true, true}, {"", "TestFoo/skipped", "TestFoo", "x", true, false}, {"TestFoo", "TestFoo/skipped", "TestFoo", "x", true, false}, {"TestFoo", "TestFoo/skipped", "TestFoo", "skipped", false, false}, {"TestFoo/", "TestFoo/skipped", "TestFoo", "x", true, false}, {"TestFoo/bar/baz", "TestFoo/skipped", "TestFoo", "bar", true, true}, // Subtest with a '/' in its name still allows for copy and pasted names // to match. {"TestFoo/bar/baz", "", "TestFoo", "bar/baz", true, false}, {"TestFoo/bar/baz", "TestFoo/bar/baz", "TestFoo", "bar/baz", false, false}, {"TestFoo/bar/baz", "TestFoo/bar/baz/skip", "TestFoo", "bar/baz", true, false}, {"TestFoo/bar/baz", "", "TestFoo/bar", "baz", true, false}, {"TestFoo/bar/baz", "", "TestFoo", "x", false, false}, {"TestFoo", "", "TestBar", "x", false, false}, {"TestFoo/", "", "TestBar", "x", false, false}, {"TestFoo/bar/baz", "", "TestBar", "x/bar/baz", false, false}, {"A/B|C/D", "", "TestA", "B", true, false}, {"A/B|C/D", "", "TestC", "D", true, false}, {"A/B|C/D", "", "TestA", "C", false, false}, // subtests only {"", "", "TestFoo", "x", true, false}, {"/", "", "TestFoo", "x", true, false}, {"./", "", "TestFoo", "x", true, false}, {"./.", "", "TestFoo", "x", true, false}, {"/bar/baz", "", "TestFoo", "bar", true, true}, {"/bar/baz", "", "TestFoo", "bar/baz", true, false}, {"//baz", "", "TestFoo", "bar/baz", true, false}, {"//", "", "TestFoo", "bar/baz", true, false}, {"/bar/baz", "", "TestFoo/bar", "baz", true, false}, {"//foo", "", "TestFoo", "bar/baz", false, false}, {"/bar/baz", "", "TestFoo", "x", false, false}, {"/bar/baz", "", "TestBar", "x/bar/baz", false, false}, } for _, tc := range testCases { m := newMatcher(regexp.MatchString, tc.pattern, "-test.run", tc.skip) parent := &common{name: tc.parent} if tc.parent != "" { parent.level = 1 } if n, ok, partial := m.fullName(parent, tc.sub); ok != tc.ok || partial != tc.partial { t.Errorf("for pattern %q, fullName(parent=%q, sub=%q) = %q, ok %v partial %v; want ok %v partial %v", tc.pattern, tc.parent, tc.sub, n, ok, partial, tc.ok, tc.partial) } } } var namingTestCases = []struct{ name, want string }{ // Uniqueness {"", "x/#00"}, {"", "x/#01"}, {"#0", "x/#0"}, // Doesn't conflict with #00 because the number of digits differs. {"#00", "x/#00#01"}, // Conflicts with implicit #00 (used above), so add a suffix. {"#", "x/#"}, {"#", "x/##01"}, {"t", "x/t"}, {"t", "x/t#01"}, {"t", "x/t#02"}, {"t#00", "x/t#00"}, // Explicit "#00" doesn't conflict with the unsuffixed first subtest. {"a#01", "x/a#01"}, // user has subtest with this name. {"a", "x/a"}, // doesn't conflict with this name. {"a", "x/a#02"}, // This string is claimed now, so resume {"a", "x/a#03"}, // with counting. {"a#02", "x/a#02#01"}, // We already used a#02 once, so add a suffix. {"b#00", "x/b#00"}, {"b", "x/b"}, // Implicit 0 doesn't conflict with explicit "#00". {"b", "x/b#01"}, {"b#9223372036854775807", "x/b#9223372036854775807"}, // MaxInt64 {"b", "x/b#02"}, {"b", "x/b#03"}, // Sanitizing {"A:1 B:2", "x/A:1_B:2"}, {"s\t\r\u00a0", "x/s___"}, {"\x01", `x/\x01`}, {"\U0010ffff", `x/\U0010ffff`}, } func TestNaming(t *T) { m := newMatcher(regexp.MatchString, "", "", "") parent := &common{name: "x", level: 1} // top-level test. for i, tc := range namingTestCases { if got, _, _ := m.fullName(parent, tc.name); got != tc.want { t.Errorf("%d:%s: got %q; want %q", i, tc.name, got, tc.want) } } } func FuzzNaming(f *F) { for _, tc := range namingTestCases { f.Add(tc.name) } parent := &common{name: "x", level: 1} var m *matcher var seen map[string]string reset := func() { m = allMatcher() seen = make(map[string]string) } reset() f.Fuzz(func(t *T, subname string) { if len(subname) > 10 { // Long names attract the OOM killer. t.Skip() } name := m.unique(parent.name, subname) if !strings.Contains(name, "/"+subname) { t.Errorf("name %q does not contain subname %q", name, subname) } if prev, ok := seen[name]; ok { t.Errorf("name %q generated by both %q and %q", name, prev, subname) } if len(seen) > 1e6 { // Free up memory. reset() } seen[name] = subname }) } // GoString returns a string that is more readable than the default, which makes // it easier to read test errors. func (m alternationMatch) GoString() string { s := make([]string, len(m)) for i, m := range m { s[i] = fmt.Sprintf("%#v", m) } return fmt.Sprintf("(%s)", strings.Join(s, " | ")) } ================================================ FILE: src/testing/sub_test.go ================================================ // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package testing import ( "reflect" ) func TestCleanup(t *T) { var cleanups []int t.Run("test", func(t *T) { t.Cleanup(func() { cleanups = append(cleanups, 1) }) t.Cleanup(func() { cleanups = append(cleanups, 2) }) }) if got, want := cleanups, []int{2, 1}; !reflect.DeepEqual(got, want) { t.Errorf("unexpected cleanup record; got %v want %v", got, want) } } func TestRunCleanup(t *T) { outerCleanup := 0 innerCleanup := 0 t.Run("test", func(t *T) { t.Cleanup(func() { outerCleanup++ }) t.Run("x", func(t *T) { t.Cleanup(func() { innerCleanup++ }) }) }) if innerCleanup != 1 { t.Errorf("unexpected inner cleanup count; got %d want 1", innerCleanup) } if outerCleanup != 1 { t.Errorf("unexpected outer cleanup count; got %d want 1", outerCleanup) // wrong upstream! } } func TestCleanupParallelSubtests(t *T) { ranCleanup := 0 t.Run("test", func(t *T) { t.Cleanup(func() { ranCleanup++ }) t.Run("x", func(t *T) { t.Parallel() if ranCleanup > 0 { t.Error("outer cleanup ran before parallel subtest") } }) }) if ranCleanup != 1 { t.Errorf("unexpected cleanup count; got %d want 1", ranCleanup) } } func TestNestedCleanup(t *T) { ranCleanup := 0 t.Run("test", func(t *T) { t.Cleanup(func() { if ranCleanup != 2 { t.Errorf("unexpected cleanup count in first cleanup: got %d want 2", ranCleanup) } ranCleanup++ }) t.Cleanup(func() { if ranCleanup != 0 { t.Errorf("unexpected cleanup count in second cleanup: got %d want 0", ranCleanup) } ranCleanup++ t.Cleanup(func() { if ranCleanup != 1 { t.Errorf("unexpected cleanup count in nested cleanup: got %d want 1", ranCleanup) } ranCleanup++ }) }) }) if ranCleanup != 3 { t.Errorf("unexpected cleanup count: got %d want 3", ranCleanup) } } ================================================ FILE: src/testing/testing.go ================================================ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // // This file has been modified for use by the TinyGo compiler. // src: https://github.com/golang/go/blob/61bb56ad/src/testing/testing.go // Package testing provides support for automated testing of Go packages. package testing import ( "bytes" "context" "errors" "flag" "fmt" "io" "io/fs" "math/rand" "os" "path/filepath" "runtime" "strconv" "strings" "time" "unicode" "unicode/utf8" ) // Testing flags. var ( flagVerbose bool flagShort bool flagRunRegexp string flagSkipRegexp string flagShuffle string flagCount int ) var initRan bool // Init registers testing flags. It has no effect if it has already run. func Init() { if initRan { return } initRan = true flag.BoolVar(&flagVerbose, "test.v", false, "verbose: print additional output") flag.BoolVar(&flagShort, "test.short", false, "short: run smaller test suite to save time") flag.StringVar(&flagRunRegexp, "test.run", "", "run: regexp of tests to run") flag.StringVar(&flagSkipRegexp, "test.skip", "", "skip: regexp of tests to run") flag.StringVar(&flagShuffle, "test.shuffle", "off", "shuffle: off, on, ") flag.IntVar(&flagCount, "test.count", 1, "run each test or benchmark `count` times") initBenchmarkFlags() } // common holds the elements common between T and B and // captures common methods such as Errorf. type common struct { output *logger indent string ran bool // Test or benchmark (or one of its subtests) was executed. failed bool // Test or benchmark has failed. skipped bool // Test of benchmark has been skipped. cleanups []func() // optional functions to be called at the end of the test finished bool // Test function has completed. hasSub bool // TODO: should be atomic parent *common level int // Nesting depth of test or benchmark. name string // Name of test or benchmark. start time.Time // Time test or benchmark started duration time.Duration tempDir string tempDirErr error tempDirSeq int32 ctx context.Context cancelCtx context.CancelFunc } type logger struct { logToStdout bool b bytes.Buffer } func (l *logger) Write(p []byte) (int, error) { if l.logToStdout { return os.Stdout.Write(p) } return l.b.Write(p) } func (l *logger) WriteTo(w io.Writer) (int64, error) { if l.logToStdout { // We've already been logging to stdout; nothing to do. return 0, nil } return l.b.WriteTo(w) } func (l *logger) Len() int { return l.b.Len() } // Short reports whether the -test.short flag is set. func Short() bool { return flagShort } // CoverMode reports what the test coverage mode is set to. // // Test coverage is not supported; this returns the empty string. func CoverMode() string { return "" } // Verbose reports whether the -test.v flag is set. func Verbose() bool { return flagVerbose } // String constant that is being set when running a test. var testBinary string // Testing returns whether the program was compiled as a test, using "tinygo // test". It returns false when built using "tinygo build", "tinygo flash", etc. func Testing() bool { return testBinary == "1" } // flushToParent writes c.output to the parent after first writing the header // with the given format and arguments. func (c *common) flushToParent(testName, format string, args ...interface{}) { if c.parent == nil { // The fake top-level test doesn't want a FAIL or PASS banner. // Not quite sure how this works upstream. c.output.WriteTo(os.Stdout) } else { fmt.Fprintf(c.parent.output, format, args...) c.output.WriteTo(c.parent.output) } } // fmtDuration returns a string representing d in the form "87.00s". func fmtDuration(d time.Duration) string { return fmt.Sprintf("%.2fs", d.Seconds()) } // TB is the interface common to T and B. type TB interface { Cleanup(func()) Context() context.Context Error(args ...interface{}) Errorf(format string, args ...interface{}) Fail() FailNow() Failed() bool Fatal(args ...interface{}) Fatalf(format string, args ...interface{}) Helper() Log(args ...interface{}) Logf(format string, args ...interface{}) Name() string Setenv(key, value string) Skip(args ...interface{}) SkipNow() Skipf(format string, args ...interface{}) Skipped() bool TempDir() string } var _ TB = (*T)(nil) var _ TB = (*B)(nil) // T is a type passed to Test functions to manage test state and support formatted test logs. // Logs are accumulated during execution and dumped to standard output when done. type T struct { common context *testContext // For running tests and subtests. } // Name returns the name of the running test or benchmark. func (c *common) Name() string { return c.name } func (c *common) setRan() { if c.parent != nil { c.parent.setRan() } c.ran = true } // Fail marks the function as having failed but continues execution. func (c *common) Fail() { c.failed = true } // Failed reports whether the function has failed. func (c *common) Failed() bool { failed := c.failed return failed } // FailNow marks the function as having failed and stops its execution // by calling runtime.Goexit (which then runs all deferred calls in the // current goroutine). func (c *common) FailNow() { c.Fail() c.finished = true c.Error("FailNow is incomplete, requires runtime.Goexit()") } // log generates the output. func (c *common) log(s string) { // This doesn't print the same as in upstream go, but works for now. if len(s) != 0 && s[len(s)-1] == '\n' { s = s[:len(s)-1] } lines := strings.Split(s, "\n") // First line. fmt.Fprintf(c.output, "%s %s\n", c.indent, lines[0]) // More lines. for _, line := range lines[1:] { fmt.Fprintf(c.output, "%s %s\n", c.indent, line) } } // Log formats its arguments using default formatting, analogous to Println, // and records the text in the error log. For tests, the text will be printed only if // the test fails or the -test.v flag is set. For benchmarks, the text is always // printed to avoid having performance depend on the value of the -test.v flag. func (c *common) Log(args ...interface{}) { c.log(fmt.Sprintln(args...)) } // Logf formats its arguments according to the format, analogous to Printf, and // records the text in the error log. A final newline is added if not provided. For // tests, the text will be printed only if the test fails or the -test.v flag is // set. For benchmarks, the text is always printed to avoid having performance // depend on the value of the -test.v flag. func (c *common) Logf(format string, args ...interface{}) { c.log(fmt.Sprintf(format, args...)) } // Error is equivalent to Log followed by Fail. func (c *common) Error(args ...interface{}) { c.log(fmt.Sprintln(args...)) c.Fail() } // Errorf is equivalent to Logf followed by Fail. func (c *common) Errorf(format string, args ...interface{}) { c.log(fmt.Sprintf(format, args...)) c.Fail() } // Fatal is equivalent to Log followed by FailNow. func (c *common) Fatal(args ...interface{}) { c.log(fmt.Sprintln(args...)) c.FailNow() } // Fatalf is equivalent to Logf followed by FailNow. func (c *common) Fatalf(format string, args ...interface{}) { c.log(fmt.Sprintf(format, args...)) c.FailNow() } // Skip is equivalent to Log followed by SkipNow. func (c *common) Skip(args ...interface{}) { c.log(fmt.Sprintln(args...)) c.SkipNow() } // Skipf is equivalent to Logf followed by SkipNow. func (c *common) Skipf(format string, args ...interface{}) { c.log(fmt.Sprintf(format, args...)) c.SkipNow() } // SkipNow marks the test as having been skipped and stops its execution // by calling runtime.Goexit. func (c *common) SkipNow() { c.skip() c.finished = true c.Error("SkipNow is incomplete, requires runtime.Goexit()") } func (c *common) skip() { c.skipped = true } // Skipped reports whether the test was skipped. func (c *common) Skipped() bool { return c.skipped } // Helper is not implemented, it is only provided for compatibility. func (c *common) Helper() { // Unimplemented. } // Cleanup registers a function to be called when the test (or subtest) and all its // subtests complete. Cleanup functions will be called in last added, // first called order. func (c *common) Cleanup(f func()) { c.cleanups = append(c.cleanups, f) } // Context returns a context that is canceled just before // Cleanup-registered functions are called. // // Cleanup functions can wait for any resources // that shut down on [context.Context.Done] before the test or benchmark completes. func (c *common) Context() context.Context { return c.ctx } // TempDir returns a temporary directory for the test to use. // The directory is automatically removed by Cleanup when the test and // all its subtests complete. // Each subsequent call to t.TempDir returns a unique directory; // if the directory creation fails, TempDir terminates the test by calling Fatal. func (c *common) TempDir() string { // Use a single parent directory for all the temporary directories // created by a test, each numbered sequentially. var nonExistent bool if c.tempDir == "" { // Usually the case with js/wasm nonExistent = true } else { _, err := os.Stat(c.tempDir) nonExistent = errors.Is(err, fs.ErrNotExist) if err != nil && !nonExistent { c.Fatalf("TempDir: %v", err) } } if nonExistent { c.Helper() // Drop unusual characters (such as path separators or // characters interacting with globs) from the directory name to // avoid surprising os.MkdirTemp behavior. mapper := func(r rune) rune { if r < utf8.RuneSelf { const allowed = "!#$%&()+,-.=@^_{}~ " if '0' <= r && r <= '9' || 'a' <= r && r <= 'z' || 'A' <= r && r <= 'Z' { return r } if strings.ContainsRune(allowed, r) { return r } } else if unicode.IsLetter(r) || unicode.IsNumber(r) { return r } return -1 } pattern := strings.Map(mapper, c.Name()) c.tempDir, c.tempDirErr = os.MkdirTemp("", pattern) if c.tempDirErr == nil { c.Cleanup(func() { if err := os.RemoveAll(c.tempDir); err != nil { c.Errorf("TempDir RemoveAll cleanup: %v", err) } }) } } if c.tempDirErr != nil { c.Fatalf("TempDir: %v", c.tempDirErr) } seq := c.tempDirSeq c.tempDirSeq++ dir := fmt.Sprintf("%s%c%03d", c.tempDir, os.PathSeparator, seq) if err := os.Mkdir(dir, 0777); err != nil { c.Fatalf("TempDir: %v", err) } return dir } // Setenv calls os.Setenv(key, value) and uses Cleanup to // restore the environment variable to its original value // after the test. func (c *common) Setenv(key, value string) { prevValue, ok := os.LookupEnv(key) if err := os.Setenv(key, value); err != nil { c.Fatalf("cannot set environment variable: %v", err) } if ok { c.Cleanup(func() { os.Setenv(key, prevValue) }) } else { c.Cleanup(func() { os.Unsetenv(key) }) } } // Chdir calls os.Chdir(dir) and uses Cleanup to restore the current // working directory to its original value after the test. On Unix, it // also sets PWD environment variable for the duration of the test. // // Because Chdir affects the whole process, it cannot be used // in parallel tests or tests with parallel ancestors. func (c *common) Chdir(dir string) { // Note: function copied from the Go 1.24.0 source tree. oldwd, err := os.Open(".") if err != nil { c.Fatal(err) } if err := os.Chdir(dir); err != nil { c.Fatal(err) } // On POSIX platforms, PWD represents “an absolute pathname of the // current working directory.” Since we are changing the working // directory, we should also set or update PWD to reflect that. switch runtime.GOOS { case "windows", "plan9": // Windows and Plan 9 do not use the PWD variable. default: if !filepath.IsAbs(dir) { dir, err = os.Getwd() if err != nil { c.Fatal(err) } } c.Setenv("PWD", dir) } c.Cleanup(func() { err := oldwd.Chdir() oldwd.Close() if err != nil { // It's not safe to continue with tests if we can't // get back to the original working directory. Since // we are holding a dirfd, this is highly unlikely. panic("testing.Chdir: " + err.Error()) } }) } // runCleanup is called at the end of the test. func (c *common) runCleanup() { for { var cleanup func() if len(c.cleanups) > 0 { last := len(c.cleanups) - 1 cleanup = c.cleanups[last] c.cleanups = c.cleanups[:last] } if cleanup == nil { return } if c.cancelCtx != nil { c.cancelCtx() } cleanup() } } // Parallel is not implemented, it is only provided for compatibility. func (t *T) Parallel() { // Unimplemented. } // InternalTest is a reference to a test that should be called during a test suite run. type InternalTest struct { Name string F func(*T) } func tRunner(t *T, fn func(t *T)) { defer func() { t.runCleanup() }() // Run the test. t.start = time.Now() fn(t) t.duration += time.Since(t.start) // TODO: capture cleanup time, too. t.report() // Report after all subtests have finished. if t.parent != nil && !t.hasSub { t.setRan() } } // Run runs f as a subtest of t called name. It waits until the subtest is finished // and returns whether the subtest succeeded. func (t *T) Run(name string, f func(t *T)) bool { t.hasSub = true testName, ok, _ := t.context.match.fullName(&t.common, name) if !ok { return true } // Create a subtest. ctx, cancelCtx := context.WithCancel(context.Background()) sub := T{ common: common{ output: &logger{logToStdout: flagVerbose}, name: testName, parent: &t.common, level: t.level + 1, ctx: ctx, cancelCtx: cancelCtx, }, context: t.context, } if t.level > 0 { sub.indent = sub.indent + " " } if flagVerbose { fmt.Fprintf(t.output, "=== RUN %s\n", sub.name) } tRunner(&sub, f) return !sub.failed } // Deadline reports the time at which the test binary will have // exceeded the timeout specified by the -timeout flag. // // The ok result is false if the -timeout flag indicates “no timeout” (0). // For now tinygo always return 0, false. // // Not Implemented. func (t *T) Deadline() (deadline time.Time, ok bool) { deadline = t.context.deadline return deadline, !deadline.IsZero() } // testContext holds all fields that are common to all tests. This includes // synchronization primitives to run at most *parallel tests. type testContext struct { match *matcher deadline time.Time } func newTestContext(m *matcher) *testContext { return &testContext{ match: m, } } // M is a test suite. type M struct { // tests is a list of the test names to execute Tests []InternalTest Benchmarks []InternalBenchmark deps testDeps // value to pass to os.Exit, the outer test func main // harness calls os.Exit with this code. See #34129. exitCode int } type testDeps interface { MatchString(pat, str string) (bool, error) } func (m *M) shuffle() error { var n int64 if flagShuffle == "on" { n = time.Now().UnixNano() } else { var err error n, err = strconv.ParseInt(flagShuffle, 10, 64) if err != nil { m.exitCode = 2 return fmt.Errorf(`testing: -shuffle should be "off", "on", or a valid integer: %v`, err) } } fmt.Println("-test.shuffle", n) rng := rand.New(rand.NewSource(n)) rng.Shuffle(len(m.Tests), func(i, j int) { m.Tests[i], m.Tests[j] = m.Tests[j], m.Tests[i] }) rng.Shuffle(len(m.Benchmarks), func(i, j int) { m.Benchmarks[i], m.Benchmarks[j] = m.Benchmarks[j], m.Benchmarks[i] }) return nil } // Run runs the tests. It returns an exit code to pass to os.Exit. func (m *M) Run() (code int) { defer func() { code = m.exitCode }() if !flag.Parsed() { flag.Parse() } if flagShuffle != "off" { if err := m.shuffle(); err != nil { fmt.Fprintln(os.Stderr, err) return } } testRan, testOk := runTests(m.deps.MatchString, m.Tests) if !testRan && *matchBenchmarks == "" { fmt.Fprintln(os.Stderr, "testing: warning: no tests to run") } if !testOk || !runBenchmarks(m.deps.MatchString, m.Benchmarks) { fmt.Println("FAIL") m.exitCode = 1 } else { fmt.Println("PASS") m.exitCode = 0 } return } func runTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ran, ok bool) { ok = true ctx := newTestContext(newMatcher(matchString, flagRunRegexp, "-test.run", flagSkipRegexp)) runCtx, cancelCtx := context.WithCancel(context.Background()) t := &T{ common: common{ output: &logger{logToStdout: flagVerbose}, ctx: runCtx, cancelCtx: cancelCtx, }, context: ctx, } for i := 0; i < flagCount; i++ { tRunner(t, func(t *T) { for _, test := range tests { t.Run(test.Name, test.F) ok = ok && !t.Failed() } }) } return t.ran, ok } func (t *T) report() { dstr := fmtDuration(t.duration) format := t.indent + "--- %s: %s (%s)\n" if t.Failed() { if t.parent != nil { t.parent.failed = true } t.flushToParent(t.name, format, "FAIL", t.name, dstr) } else if flagVerbose { if t.Skipped() { t.flushToParent(t.name, format, "SKIP", t.name, dstr) } else { t.flushToParent(t.name, format, "PASS", t.name, dstr) } } } // AllocsPerRun returns the average number of allocations during calls to f. // Although the return value has type float64, it will always be an integral // value. // // Not implemented. func AllocsPerRun(runs int, f func()) (avg float64) { f() for i := 0; i < runs; i++ { f() } return 0 } type InternalExample struct { Name string F func() Output string Unordered bool } // MainStart is meant for use by tests generated by 'go test'. // It is not meant to be called directly and is not subject to the Go 1 compatibility document. // It may change signature from release to release. func MainStart(deps interface{}, tests []InternalTest, benchmarks []InternalBenchmark, fuzzTargets []InternalFuzzTarget, examples []InternalExample) *M { Init() return &M{ Tests: tests, Benchmarks: benchmarks, deps: deps.(testDeps), } } // A fake regexp matcher. // Inflexible, but saves 50KB of flash and 50KB of RAM per -size full, // and lets tests pass on cortex-m. func fakeMatchString(pat, str string) (bool, error) { if pat == ".*" { return true, nil } matched := strings.Contains(str, pat) return matched, nil } ================================================ FILE: src/testing/testing_test.go ================================================ //go:build !windows // TODO: implement readdir for windows, then enable this file // Copyright 2014 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package testing_test import ( "errors" "io/fs" "os" "path/filepath" "runtime" "testing" ) // This is exactly what a test would do without a TestMain. // It's here only so that there is at least one package in the // standard library with a TestMain, so that code is executed. func TestMain(m *testing.M) { os.Exit(m.Run()) } func TestTempDirInCleanup(t *testing.T) { if runtime.GOOS == "wasip1" || runtime.GOOS == "wasip2" { t.Log("Skipping. TODO: implement RemoveAll for wasi") return } var dir string t.Run("test", func(t *testing.T) { t.Cleanup(func() { dir = t.TempDir() }) _ = t.TempDir() }) fi, err := os.Stat(dir) if fi != nil { t.Fatalf("Directory %q from user Cleanup still exists", dir) } if !errors.Is(err, fs.ErrNotExist) { t.Fatalf("Unexpected error: %v", err) } } func TestTempDirInBenchmark(t *testing.T) { testing.Benchmark(func(b *testing.B) { if !b.Run("test", func(b *testing.B) { // Add a loop so that the test won't fail. See issue 38677. for i := 0; i < b.N; i++ { _ = b.TempDir() } }) { t.Fatal("Sub test failure in a benchmark") } }) } func TestTempDir(t *testing.T) { if runtime.GOOS == "wasip1" || runtime.GOOS == "wasip2" { t.Log("Skipping. TODO: implement RemoveAll for wasi") return } testTempDir(t) t.Run("InSubtest", testTempDir) t.Run("test/subtest", testTempDir) t.Run("test\\subtest", testTempDir) t.Run("test:subtest", testTempDir) t.Run("test/..", testTempDir) t.Run("../test", testTempDir) t.Run("test[]", testTempDir) t.Run("test*", testTempDir) t.Run("äöüéè", testTempDir) } func testTempDir(t *testing.T) { dirCh := make(chan string, 1) t.Cleanup(func() { // Verify directory has been removed. select { case dir := <-dirCh: fi, err := os.Stat(dir) if errors.Is(err, fs.ErrNotExist) { // All good return } if err != nil { t.Fatal(err) } t.Errorf("directory %q still exists: %v, isDir=%v", dir, fi, fi.IsDir()) default: if !t.Failed() { t.Fatal("never received dir channel") } } }) dir := t.TempDir() if dir == "" { t.Fatal("expected dir") } dir2 := t.TempDir() if dir == dir2 { t.Fatal("subsequent calls to TempDir returned the same directory") } if filepath.Dir(dir) != filepath.Dir(dir2) { t.Fatalf("calls to TempDir do not share a parent; got %q, %q", dir, dir2) } dirCh <- dir fi, err := os.Stat(dir) if err != nil { t.Fatal(err) } if !fi.IsDir() { t.Errorf("dir %q is not a dir", dir) } files, err := os.ReadDir(dir) if err != nil { t.Fatal(err) } if len(files) > 0 { t.Errorf("unexpected %d files in TempDir: %v", len(files), files) } glob := filepath.Join(dir, "*.txt") if _, err := filepath.Glob(glob); err != nil { t.Error(err) } err = os.Remove(dir) if err != nil { t.Errorf("unexpected files in TempDir") } } func TestSetenv(t *testing.T) { tests := []struct { name string key string initialValueExists bool initialValue string newValue string }{ { name: "initial value exists", key: "GO_TEST_KEY_1", initialValueExists: true, initialValue: "111", newValue: "222", }, { name: "initial value exists but empty", key: "GO_TEST_KEY_2", initialValueExists: true, initialValue: "", newValue: "222", }, { name: "initial value is not exists", key: "GO_TEST_KEY_3", initialValueExists: false, initialValue: "", newValue: "222", }, } for _, test := range tests { if test.initialValueExists { if err := os.Setenv(test.key, test.initialValue); err != nil { t.Fatalf("unable to set env: got %v", err) } } else { os.Unsetenv(test.key) } t.Run(test.name, func(t *testing.T) { t.Setenv(test.key, test.newValue) if os.Getenv(test.key) != test.newValue { t.Fatalf("unexpected value after t.Setenv: got %s, want %s", os.Getenv(test.key), test.newValue) } }) got, exists := os.LookupEnv(test.key) if got != test.initialValue { t.Fatalf("unexpected value after t.Setenv cleanup: got %s, want %s", got, test.initialValue) } if exists != test.initialValueExists { t.Fatalf("unexpected value after t.Setenv cleanup: got %t, want %t", exists, test.initialValueExists) } } } func TestTesting(t *testing.T) { if !testing.Testing() { t.Error("Expected testing.Testing() to return true while in a test") } } ================================================ FILE: src/tinygo/runtime.go ================================================ // Package tinygo contains constants used between the TinyGo compiler and // runtime. package tinygo const ( PanicStrategyPrint = iota + 1 PanicStrategyTrap ) type HashmapAlgorithm uint8 // Constants for hashmap algorithms. const ( HashmapAlgorithmBinary HashmapAlgorithm = iota HashmapAlgorithmString HashmapAlgorithmInterface ) ================================================ FILE: src/unique/handle.go ================================================ // Package unique implements the upstream Go unique package for TinyGo. // // It is not a full implementation: while it should behave the same way, it // doesn't free unreferenced uniqued objects. package unique import ( "sync" "unsafe" ) var ( // We use a two-level map because that way it's easier to store and retrieve // values. globalMap map[unsafe.Pointer]any // map value type is always map[T]Handle[T] globalMapMutex sync.Mutex ) // Unique handle for the given value. Comparing two handles is cheap. type Handle[T comparable] struct { value *T } // Value returns a shallow copy of the T value that produced the Handle. func (h Handle[T]) Value() T { return *h.value } // Make a new unqique handle for the given value. func Make[T comparable](value T) Handle[T] { // Very simple implementation of the unique package. This is much, *much* // simpler than the upstream implementation. Sadly it's not possible to // reuse the upstream version because it relies on implementation details of // the upstream runtime. // It probably isn't as efficient as the upstream version, but the first // goal here is compatibility. If the performance is a problem, it can be // optimized later. globalMapMutex.Lock() // The map isn't initialized at program startup (and after a test run), so // create it. if globalMap == nil { globalMap = make(map[unsafe.Pointer]any) } // Retrieve the type-specific map, creating it if not yet present. typeptr, _ := decomposeInterface(value) var typeSpecificMap map[T]Handle[T] if typeSpecificMapValue, ok := globalMap[typeptr]; !ok { typeSpecificMap = make(map[T]Handle[T]) globalMap[typeptr] = typeSpecificMap } else { typeSpecificMap = typeSpecificMapValue.(map[T]Handle[T]) } // Retrieve the handle for the value, creating it if it isn't created yet. var handle Handle[T] if h, ok := typeSpecificMap[value]; !ok { var clone T = value handle.value = &clone typeSpecificMap[value] = handle } else { handle = h } globalMapMutex.Unlock() return handle } //go:linkname decomposeInterface runtime.decomposeInterface func decomposeInterface(i interface{}) (unsafe.Pointer, unsafe.Pointer) ================================================ FILE: src/unique/handle_test.go ================================================ // Copyright 2024 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // This file is a copy of src/unique/handle_test.go in upstream Go, but with // some parts removed that rely on Go runtime implementation details. package unique import ( "fmt" "reflect" "testing" ) // Set up special types. Because the internal maps are sharded by type, // this will ensure that we're not overlapping with other tests. type testString string type testIntArray [4]int type testEface any type testStringArray [3]string type testStringStruct struct { a string } type testStringStructArrayStruct struct { s [2]testStringStruct } type testStruct struct { z float64 b string } func TestHandle(t *testing.T) { testHandle[testString](t, "foo") testHandle[testString](t, "bar") testHandle[testString](t, "") testHandle[testIntArray](t, [4]int{7, 77, 777, 7777}) //testHandle[testEface](t, nil) // requires Go 1.20 testHandle[testStringArray](t, [3]string{"a", "b", "c"}) testHandle[testStringStruct](t, testStringStruct{"x"}) testHandle[testStringStructArrayStruct](t, testStringStructArrayStruct{ s: [2]testStringStruct{testStringStruct{"y"}, testStringStruct{"z"}}, }) testHandle[testStruct](t, testStruct{0.5, "184"}) } func testHandle[T comparable](t *testing.T, value T) { name := reflect.TypeFor[T]().Name() t.Run(fmt.Sprintf("%s/%#v", name, value), func(t *testing.T) { t.Parallel() v0 := Make(value) v1 := Make(value) if v0.Value() != v1.Value() { t.Error("v0.Value != v1.Value") } if v0.Value() != value { t.Errorf("v0.Value not %#v", value) } if v0 != v1 { t.Error("v0 != v1") } drainMaps(t) }) } // drainMaps ensures that the internal maps are drained. func drainMaps(t *testing.T) { t.Helper() globalMapMutex.Lock() globalMap = nil globalMapMutex.Unlock() } ================================================ FILE: stacksize/dwarf.go ================================================ package stacksize // This file implements parsing DWARF call frame information and interpreting // the CFI bytecode, or enough of it for most practical code. import ( "bytes" "debug/elf" "encoding/binary" "fmt" "io" ) // dwarfCIE represents one DWARF Call Frame Information structure. type dwarfCIE struct { bytecode []byte codeAlignmentFactor uint64 } // parseFrames parses all call frame information from a .debug_frame section and // provides the passed in symbols map with frame size information. func parseFrames(f *elf.File, data []byte, symbols map[uint64]*CallNode) error { if f.Class != elf.ELFCLASS32 { // TODO: ELF64 return fmt.Errorf("expected ELF32") } cies := make(map[uint32]*dwarfCIE) // Read each entity. r := bytes.NewBuffer(data) for { start := len(data) - r.Len() var length uint32 err := binary.Read(r, binary.LittleEndian, &length) if err == io.EOF { return nil } if err != nil { return err } var cie uint32 err = binary.Read(r, binary.LittleEndian, &cie) if err != nil { return err } if cie == 0xffffffff { // This is a CIE. var fields struct { Version uint8 Augmentation uint8 AddressSize uint8 SegmentSize uint8 } err = binary.Read(r, binary.LittleEndian, &fields) if err != nil { return err } if fields.Version != 4 { return fmt.Errorf("unimplemented: .debug_frame version %d", fields.Version) } if fields.Augmentation != 0 { return fmt.Errorf("unimplemented: .debug_frame with augmentation") } if fields.SegmentSize != 0 { return fmt.Errorf("unimplemented: .debug_frame with segment size") } codeAlignmentFactor, err := readULEB128(r) if err != nil { return err } _, err = readSLEB128(r) // data alignment factor if err != nil { return err } _, err = readULEB128(r) // return address register if err != nil { return err } rest := (start + int(length) + 4) - (len(data) - r.Len()) bytecode := r.Next(rest) cies[uint32(start)] = &dwarfCIE{ codeAlignmentFactor: codeAlignmentFactor, bytecode: bytecode, } } else { // This is a FDE. var fields struct { InitialLocation uint32 AddressRange uint32 } err = binary.Read(r, binary.LittleEndian, &fields) if err != nil { return err } if _, ok := cies[cie]; !ok { return fmt.Errorf("could not find CIE 0x%x in .debug_frame section", cie) } frame := frameInfo{ cie: cies[cie], start: uint64(fields.InitialLocation), loc: uint64(fields.InitialLocation), length: uint64(fields.AddressRange), } rest := (start + int(length) + 4) - (len(data) - r.Len()) bytecode := r.Next(rest) if frame.start == 0 { // Not sure where these come from but they don't seem to be // important. continue } _, err = frame.exec(frame.cie.bytecode) if err != nil { return err } entries, err := frame.exec(bytecode) if err != nil { return err } var maxFrameSize uint64 for _, entry := range entries { switch f.Machine { case elf.EM_ARM: if entry.cfaRegister != 13 { // r13 or sp // something other than a stack pointer (on ARM) return fmt.Errorf("%08x..%08x: unknown CFA register number %d", frame.start, frame.start+frame.length, entry.cfaRegister) } default: return fmt.Errorf("unknown architecture: %s", f.Machine) } if entry.cfaOffset > maxFrameSize { maxFrameSize = entry.cfaOffset } } node := symbols[frame.start] if node.Size != frame.length { return fmt.Errorf("%s: symtab gives symbol length %d while DWARF gives symbol length %d", node, node.Size, frame.length) } node.FrameSize = maxFrameSize node.FrameSizeType = Bounded if debugPrint { fmt.Printf("%08x..%08x: frame size %4d %s\n", frame.start, frame.start+frame.length, maxFrameSize, node) } } } } // frameInfo contains the state of executing call frame information bytecode. type frameInfo struct { cie *dwarfCIE start uint64 loc uint64 length uint64 cfaRegister uint64 cfaOffset uint64 } // frameInfoLine represents one line in the frame table (.debug_frame) at one // point in the execution of the bytecode. type frameInfoLine struct { loc uint64 cfaRegister uint64 cfaOffset uint64 } func (fi *frameInfo) newLine() frameInfoLine { return frameInfoLine{ loc: fi.loc, cfaRegister: fi.cfaRegister, cfaOffset: fi.cfaOffset, } } // exec executes the given bytecode in the CFI. Most CFI bytecode is actually // very simple and provides a way to determine the maximum call frame size. // // The frame size often changes multiple times in a function, for example the // frame size may be adjusted in the prologue and epilogue. Each frameInfoLine // may contain such a change. func (fi *frameInfo) exec(bytecode []byte) ([]frameInfoLine, error) { var entries []frameInfoLine r := bytes.NewBuffer(bytecode) for { op, err := r.ReadByte() if err != nil { if err == io.EOF { entries = append(entries, fi.newLine()) return entries, nil } return nil, err } // For details on the various opcodes, see: // http://dwarfstd.org/doc/DWARF5.pdf (page 239) highBits := op >> 6 // high order 2 bits lowBits := op & 0x1f switch highBits { case 1: // DW_CFA_advance_loc fi.loc += uint64(lowBits) * fi.cie.codeAlignmentFactor entries = append(entries, fi.newLine()) case 2: // DW_CFA_offset // This indicates where a register is saved on the stack in the // prologue. We can ignore that for our purposes. _, err := readULEB128(r) if err != nil { return nil, err } case 3: // DW_CFA_restore // Restore a register. Used after an outlined function call. // It should be possible to ignore this. // TODO: check that this is not the stack pointer. case 0: switch lowBits { case 0: // DW_CFA_nop // no operation case 0x02: // DW_CFA_advance_loc1 // Very similar to DW_CFA_advance_loc but allows for a slightly // larger range. offset, err := r.ReadByte() if err != nil { return nil, err } fi.loc += uint64(offset) * fi.cie.codeAlignmentFactor entries = append(entries, fi.newLine()) case 0x03: // DW_CFA_advance_loc2 var offset uint16 err := binary.Read(r, binary.LittleEndian, &offset) if err != nil { return nil, err } fi.loc += uint64(offset) * fi.cie.codeAlignmentFactor entries = append(entries, fi.newLine()) case 0x04: // DW_CFA_advance_loc4 var offset uint32 err := binary.Read(r, binary.LittleEndian, &offset) if err != nil { return nil, err } fi.loc += uint64(offset) * fi.cie.codeAlignmentFactor entries = append(entries, fi.newLine()) case 0x05: // DW_CFA_offset_extended // Semantics are the same as DW_CFA_offset, but the encoding is // different. Ignore it just like DW_CFA_offset. _, err := readULEB128(r) // ULEB128 register if err != nil { return nil, err } _, err = readULEB128(r) // ULEB128 offset if err != nil { return nil, err } case 0x07: // DW_CFA_undefined // Marks a single register as undefined. This is used to stop // unwinding in tinygo_startTask using: // .cfi_undefined lr // Ignore this directive. _, err := readULEB128(r) if err != nil { return nil, err } case 0x09: // DW_CFA_register // Copies a register. Emitted by the machine outliner, for example. // It should be possible to ignore this. // TODO: check that the stack pointer is not affected. _, err := readULEB128(r) if err != nil { return nil, err } _, err = readULEB128(r) if err != nil { return nil, err } case 0x0c: // DW_CFA_def_cfa register, err := readULEB128(r) if err != nil { return nil, err } offset, err := readULEB128(r) if err != nil { return nil, err } fi.cfaRegister = register fi.cfaOffset = offset case 0x0e: // DW_CFA_def_cfa_offset offset, err := readULEB128(r) if err != nil { return nil, err } fi.cfaOffset = offset default: return nil, fmt.Errorf("could not decode .debug_frame bytecode op 0x%x (for address 0x%x)", op, fi.loc) } default: return nil, fmt.Errorf("could not decode .debug_frame bytecode op 0x%x (for address 0x%x)", op, fi.loc) } } } // Source: https://en.wikipedia.org/wiki/LEB128#Decode_unsigned_integer func readULEB128(r *bytes.Buffer) (result uint64, err error) { // TODO: guard against overflowing 64-bit integers. var shift uint8 for { b, err := r.ReadByte() if err != nil { return 0, err } result |= uint64(b&0x7f) << shift if b&0x80 == 0 { break } shift += 7 } return } // Source: https://en.wikipedia.org/wiki/LEB128#Decode_signed_integer func readSLEB128(r *bytes.Buffer) (result int64, err error) { var shift uint8 var b byte var rawResult uint64 for { b, err = r.ReadByte() if err != nil { return 0, err } rawResult |= uint64(b&0x7f) << shift shift += 7 if b&0x80 == 0 { break } } // sign bit of byte is second high order bit (0x40) if shift < 64 && b&0x40 != 0 { // sign extend rawResult |= ^uint64(0) << shift } result = int64(rawResult) return } ================================================ FILE: stacksize/stacksize.go ================================================ // Package stacksize tries to determine the call graph for ELF binaries and // tries to parse stack size information from DWARF call frame information. package stacksize import ( "debug/elf" "encoding/binary" "errors" "fmt" "os" "sort" ) // set to true to print information useful for debugging const debugPrint = false // SizeType indicates whether a stack or frame size could be determined and if // not, why. type SizeType uint8 // Results after trying to determine the stack size of a function in the call // graph. The goal is to find a maximum (bounded) stack size, but sometimes this // is not possible for some reasons such as recursion or indirect calls. const ( Undefined SizeType = iota // not yet calculated Unknown // child has unknown stack size Bounded // stack size is fixed at compile time (no recursion etc) Recursive IndirectCall ) func (s SizeType) String() string { switch s { case Undefined: return "undefined" case Unknown: return "unknown" case Bounded: return "bounded" case Recursive: return "recursive" case IndirectCall: return "indirect call" default: return "" } } // CallNode is a node in the call graph (that is, a function). Because this is // determined after linking, there may be multiple names for a single function // (due to aliases). It is also possible multiple functions have the same name // (but are in fact different), for example for static functions in C. type CallNode struct { Names []string Address uint64 // address at which the function is linked (without T bit on ARM) Size uint64 // symbol size, in bytes Children []*CallNode // functions this function calls FrameSize uint64 // frame size, if FrameSizeType is Bounded FrameSizeType SizeType // can be Undefined or Bounded stackSize uint64 stackSizeType SizeType missingFrameInfo *CallNode // the child function that is the cause for not being able to determine the stack size } func (n *CallNode) String() string { if n == nil { return "" } return n.Names[0] } // CallGraph parses the ELF file and reads DWARF call frame information to // determine frame sizes for each function, as far as that's possible. Because // at this point it is not possible to determine indirect calls, a list of // indirect function calling functions needs to be supplied separately. // // This function does not attempt to determine the stack size for functions. // This is done by calling StackSize on a function in the call graph. func CallGraph(f *elf.File, callsIndirectFunction []string) (map[string][]*CallNode, error) { // Sanity check that there is exactly one symbol table. // Multiple symbol tables are possible, but aren't yet supported below. numSymbolTables := 0 for _, section := range f.Sections { if section.Type == elf.SHT_SYMTAB { numSymbolTables++ } } if numSymbolTables != 1 { return nil, fmt.Errorf("expected exactly one symbol table, got %d", numSymbolTables) } // Collect all symbols in the executable. symbols := make(map[uint64]*CallNode) symbolList := make([]*CallNode, 0) symbolNames := make(map[string][]*CallNode) elfSymbols, err := f.Symbols() if err != nil { return nil, err } for _, elfSymbol := range elfSymbols { if elf.ST_TYPE(elfSymbol.Info) != elf.STT_FUNC { continue } address := elfSymbol.Value if f.Machine == elf.EM_ARM { address = address &^ 1 } var node *CallNode if n, ok := symbols[address]; ok { // Existing symbol. if n.Size != elfSymbol.Size { return nil, fmt.Errorf("symbol at 0x%x has inconsistent size (%d for %s and %d for %s)", address, n.Size, n.Names[0], elfSymbol.Size, elfSymbol.Name) } node = n node.Names = append(node.Names, elfSymbol.Name) } else { // New symbol. node = &CallNode{ Names: []string{elfSymbol.Name}, Address: address, Size: elfSymbol.Size, } symbols[address] = node symbolList = append(symbolList, node) } symbolNames[elfSymbol.Name] = append(symbolNames[elfSymbol.Name], node) } // Sort symbols by address, for binary searching. sort.Slice(symbolList, func(i, j int) bool { return symbolList[i].Address < symbolList[j].Address }) // Load relocations and construct the call graph. for _, section := range f.Sections { if section.Type != elf.SHT_REL { continue } if section.Entsize != 8 { // Assume ELF32, this should be fixed. return nil, fmt.Errorf("only ELF32 is supported at this time") } data, err := section.Data() if err != nil { return nil, err } for i := uint64(0); i < section.Size/section.Entsize; i++ { offset := binary.LittleEndian.Uint32(data[i*section.Entsize:]) info := binary.LittleEndian.Uint32(data[i*section.Entsize+4:]) if elf.R_SYM32(info) == 0 { continue } elfSymbol := elfSymbols[elf.R_SYM32(info)-1] if elf.ST_TYPE(elfSymbol.Info) != elf.STT_FUNC { continue } address := elfSymbol.Value if f.Machine == elf.EM_ARM { address = address &^ 1 } childSym := symbols[address] switch f.Machine { case elf.EM_ARM: relocType := elf.R_ARM(elf.R_TYPE32(info)) parentSym := findSymbol(symbolList, uint64(offset)) if debugPrint { fmt.Fprintf(os.Stderr, "found relocation %-24s at %s (0x%x) to %s (0x%x)\n", relocType, parentSym, offset, childSym, childSym.Address) } isCall := true switch relocType { case elf.R_ARM_THM_PC22: // actually R_ARM_THM_CALL // used for bl calls case elf.R_ARM_THM_JUMP24: // used for b.w jumps isCall = parentSym != childSym case elf.R_ARM_THM_JUMP11: // used for b.n jumps isCall = parentSym != childSym case elf.R_ARM_THM_MOVW_ABS_NC, elf.R_ARM_THM_MOVT_ABS: // used for getting a function pointer isCall = false case elf.R_ARM_ABS32: // when compiling with -Oz (minsize), used for calling isCall = true default: return nil, fmt.Errorf("unknown relocation: %s", relocType) } if isCall { if parentSym != nil { parentSym.Children = append(parentSym.Children, childSym) } } default: return nil, fmt.Errorf("unknown architecture: %s", f.Machine) } } } // Set fixed frame size information, depending on the architecture. switch f.Machine { case elf.EM_ARM: knownFrameSizes := map[string]uint64{ // implemented with assembly in compiler-rt "__aeabi_idivmod": 3 * 4, // 3 registers on thumb1 but 1 register on thumb2 "__aeabi_uidivmod": 3 * 4, // 3 registers on thumb1 but 1 register on thumb2 "__aeabi_ldivmod": 2 * 4, "__aeabi_uldivmod": 2 * 4, "__aeabi_memclr": 2 * 4, // 2 registers on thumb1 "__aeabi_memset": 2 * 4, // 2 registers on thumb1 "__aeabi_memcmp": 2 * 4, // 2 registers on thumb1 "__aeabi_memcpy": 2 * 4, // 2 registers on thumb1 "__aeabi_memmove": 2 * 4, // 2 registers on thumb1 "__aeabi_dcmpeq": 2 * 4, "__aeabi_dcmplt": 2 * 4, "__aeabi_dcmple": 2 * 4, "__aeabi_dcmpge": 2 * 4, "__aeabi_dcmpgt": 2 * 4, "__aeabi_fcmpeq": 2 * 4, "__aeabi_fcmplt": 2 * 4, "__aeabi_fcmple": 2 * 4, "__aeabi_fcmpge": 2 * 4, "__aeabi_fcmpgt": 2 * 4, } for name, size := range knownFrameSizes { if sym, ok := symbolNames[name]; ok { if len(sym) > 1 { return nil, fmt.Errorf("expected zero or one occurrence of the symbol %s, found %d", name, len(sym)) } sym[0].FrameSize = size sym[0].FrameSizeType = Bounded } } } // Mark functions that do indirect calls (which cannot be determined // directly from ELF/DWARF information). for _, name := range callsIndirectFunction { for _, fn := range symbolNames[name] { fn.stackSizeType = IndirectCall fn.missingFrameInfo = fn } } // Read the .debug_frame section. section := f.Section(".debug_frame") if section == nil { return nil, errors.New("no .debug_frame section present, binary was compiled without debug information") } data, err := section.Data() if err != nil { return nil, fmt.Errorf("could not read .debug_frame section: %w", err) } err = parseFrames(f, data, symbols) if err != nil { return nil, err } return symbolNames, nil } // findSymbol determines in which symbol the given address lies. func findSymbol(symbolList []*CallNode, address uint64) *CallNode { // TODO: binary search for _, sym := range symbolList { if address >= sym.Address && address < sym.Address+sym.Size { return sym } } return nil } // StackSize tries to determine the stack size of the given call graph node. It // returns the maximum stack size, whether this size can be known at compile // time and the call node responsible for failing to determine the maximum stack // usage. The stack size is only valid if sizeType is Bounded. func (node *CallNode) StackSize() (uint64, SizeType, *CallNode) { if node.stackSizeType == Undefined { node.determineStackSize(make(map[*CallNode]struct{})) } return node.stackSize, node.stackSizeType, node.missingFrameInfo } // determineStackSize tries to determine the maximum stack size for this // function, recursively. func (node *CallNode) determineStackSize(parents map[*CallNode]struct{}) { if _, ok := parents[node]; ok { // The function calls itself (directly or indirectly). node.stackSizeType = Recursive node.missingFrameInfo = node return } parents[node] = struct{}{} defer func() { delete(parents, node) }() switch node.FrameSizeType { case Bounded: // Determine the stack size recursively. childMaxStackSize := uint64(0) for _, child := range node.Children { if child.stackSizeType == Undefined { child.determineStackSize(parents) } switch child.stackSizeType { case Bounded: if child.stackSize > childMaxStackSize { childMaxStackSize = child.stackSize } case Unknown, Recursive, IndirectCall: node.stackSizeType = child.stackSizeType node.missingFrameInfo = child.missingFrameInfo return default: panic("unknown child stack size type") } } node.stackSize = node.FrameSize + childMaxStackSize node.stackSizeType = Bounded case Undefined: node.stackSizeType = Unknown node.missingFrameInfo = node default: panic("unknown frame size type") // unreachable } } ================================================ FILE: targets/adafruit-esp32-feather-v2.json ================================================ { "inherits": ["esp32"], "build-tags": ["adafruit_esp32_feather_v2"] } ================================================ FILE: targets/ae-rp2040.json ================================================ { "inherits": [ "rp2040" ], "build-tags": ["ae_rp2040"], "serial-port": ["2e8a:000A"], "ldflags": [ "--defsym=__flash_size=2048K" ], "extra-files": [ "targets/pico-boot-stage2.S" ] } ================================================ FILE: targets/arduino-leonardo.json ================================================ { "inherits": ["atmega32u4"], "build-tags": ["arduino_leonardo"], "ldflags": [ "--defsym=_bootloader_size=512", "--defsym=_stack_size=512" ], "flash-command": "avrdude -c avr109 -p atmega32u4 -b 57600 -P {port} -U flash:w:{hex}:i", "emulator": "simavr -m atmega32u4 -f 16000000 {}" } ================================================ FILE: targets/arduino-mega1280.json ================================================ { "inherits": ["atmega1280"], "build-tags": ["arduino_mega1280"], "ldflags": [ "--defsym=_bootloader_size=4096" ], "flash-command":"avrdude -c arduino -b 57600 -p atmega1280 -P {port} -U flash:w:{hex}:i -v -D" } ================================================ FILE: targets/arduino-mega2560.json ================================================ { "inherits": ["atmega2560"], "build-tags": ["arduino_mega2560"], "ldflags": [ "--defsym=_bootloader_size=8192" ], "flash-command":"avrdude -c wiring -b 115200 -p atmega2560 -P {port} -U flash:w:{hex}:i -v -D" } ================================================ FILE: targets/arduino-mkr1000.json ================================================ { "inherits": ["atsamd21g18a"], "build-tags": ["arduino_mkr1000"], "serial": "usb", "flash-command": "bossac -i -e -w -v -R -U --port={port} --offset=0x2000 {bin}", "flash-1200-bps-reset": "true" } ================================================ FILE: targets/arduino-mkrwifi1010.json ================================================ { "inherits": ["atsamd21g18a"], "build-tags": ["arduino_mkrwifi1010", "ninafw"], "serial": "usb", "serial-port": ["2341:8054", "2341:0054"], "flash-command": "bossac -i -e -w -v -R -U --port={port} --offset=0x2000 {bin}", "flash-1200-bps-reset": "true" } ================================================ FILE: targets/arduino-nano-new.json ================================================ { "inherits": ["arduino-nano"], "flash-command": "avrdude -c arduino -p atmega328p -b 115200 -P {port} -U flash:w:{hex}:i" } ================================================ FILE: targets/arduino-nano.json ================================================ { "inherits": ["atmega328p"], "build-tags": ["arduino_nano"], "ldflags": [ "--defsym=_bootloader_size=512", "--defsym=_stack_size=512" ], "flash-command": "avrdude -c arduino -p atmega328p -b 57600 -P {port} -U flash:w:{hex}:i", "emulator": "simavr -m atmega328p -f 16000000 {}" } ================================================ FILE: targets/arduino-nano33.json ================================================ { "inherits": ["atsamd21g18a"], "build-tags": ["arduino_nano33", "ninafw", "ninafw_machine_init"], "flash-command": "bossac -i -e -w -v -R -U --port={port} --offset=0x2000 {bin}", "serial-port": ["2341:8057", "2341:0057"], "flash-1200-bps-reset": "true" } ================================================ FILE: targets/arduino-zero.json ================================================ { "inherits": ["atsamd21g18a"], "build-tags": ["arduino_zero"], "serial": "usb", "flash-command": "bossac -i -e -w -v -R -U --port={port} --offset=0x2000 {bin}", "flash-1200-bps-reset": "true" } ================================================ FILE: targets/arduino.json ================================================ { "inherits": ["atmega328p"], "build-tags": ["arduino"], "ldflags": [ "--defsym=_bootloader_size=512", "--defsym=_stack_size=512" ], "flash-command": "avrdude -c arduino -p atmega328p -P {port} -U flash:w:{hex}:i", "serial-port": ["2341:0043", "2341:0001", "2a03:0043", "2341:0243"], "emulator": "simavr -m atmega328p -f 16000000 {}" } ================================================ FILE: targets/arm.ld ================================================ /* Unused, but here to silence a linker warning. */ ENTRY(Reset_Handler) /* define output sections */ SECTIONS { /* Program code and read-only data goes to FLASH_TEXT. */ .text : { KEEP(*(.isr_vector)) KEEP(*(.after_isr_vector)) /* for the RP2350 */ *(.text) *(.text.*) *(.rodata) *(.rodata.*) . = ALIGN(4); } >FLASH_TEXT .tinygo_stacksizes : { *(.tinygo_stacksizes) } > FLASH_TEXT /* Put the stack at the bottom of RAM, so that the application will * crash on stack overflow instead of silently corrupting memory. * See: http://blog.japaric.io/stack-overflow-protection/ */ .stack (NOLOAD) : { . = ALIGN(4); . += _stack_size; _stack_top = .; } >RAM /* Stack for second core (core 1), if there is one. */ .stack1 (NOLOAD) : { . = ALIGN(4); . += DEFINED(__num_stacks) && __num_stacks >= 2 ? _stack_size : 0; _stack1_top = .; } >RAM /* Start address (in flash) of .data, used by startup code. */ _sidata = LOADADDR(.data); /* Globals with initial value */ .data : { . = ALIGN(4); _sdata = .; /* used by startup code */ *(.data) *(.data.*) . = ALIGN(4); *(.ramfuncs*) /* Functions that must execute from RAM */ . = ALIGN(4); _edata = .; /* used by startup code */ } >RAM AT>FLASH_TEXT /* Zero-initialized globals */ .bss : { . = ALIGN(4); _sbss = .; /* used by startup code */ *(.bss) *(.bss.*) *(COMMON) . = ALIGN(4); _ebss = .; /* used by startup code */ } >RAM /DISCARD/ : { *(.ARM.exidx) /* causes 'no memory region specified' error in lld */ *(.ARM.exidx.*) /* causes spurious 'undefined reference' errors */ } } /* For the memory allocator. */ _heap_start = _ebss; _heap_end = ORIGIN(RAM) + LENGTH(RAM); _globals_start = _sdata; _globals_end = _ebss; /* For the flash API */ __flash_data_start = LOADADDR(.data) + SIZEOF(.data); __flash_data_end = ORIGIN(FLASH_TEXT) + LENGTH(FLASH_TEXT); ================================================ FILE: targets/atmega1280.json ================================================ { "inherits": ["avr"], "cpu": "atmega1280", "build-tags": ["atmega1280", "atmega"], "serial": "uart", "ldflags": [ "--defsym=_stack_size=512" ], "linkerscript": "src/device/avr/atmega1280.ld", "extra-files": [ "targets/avr.S", "src/device/avr/atmega1280.s" ] } ================================================ FILE: targets/atmega1284p.json ================================================ { "inherits": ["avr"], "cpu": "atmega1284p", "build-tags": ["atmega1284p", "atmega"], "serial": "uart", "ldflags": [ "--defsym=_bootloader_size=0", "--defsym=_stack_size=512" ], "linkerscript": "src/device/avr/atmega1284p.ld", "extra-files": [ "targets/avr.S", "src/device/avr/atmega1284p.s" ], "emulator": "simavr -m atmega1284p -f 20000000 {}" } ================================================ FILE: targets/atmega2560.json ================================================ { "inherits": ["avr"], "cpu": "atmega2560", "build-tags": ["atmega2560", "atmega"], "serial": "uart", "ldflags": [ "--defsym=_stack_size=512" ], "linkerscript": "src/device/avr/atmega2560.ld", "extra-files": [ "targets/avr.S", "src/device/avr/atmega2560.s" ] } ================================================ FILE: targets/atmega328p.json ================================================ { "inherits": ["avr"], "cpu": "atmega328p", "build-tags": ["atmega328p", "atmega", "avr5"], "serial": "uart", "linkerscript": "src/device/avr/atmega328p.ld", "extra-files": [ "targets/avr.S", "src/device/avr/atmega328p.s" ] } ================================================ FILE: targets/atmega328pb.json ================================================ { "inherits": ["avr"], "cpu": "atmega328pb", "build-tags": ["atmega328pb", "atmega", "avr5"], "ldflags": [ "--defsym=_bootloader_size=512", "--defsym=_stack_size=512" ], "serial": "uart", "linkerscript": "src/device/avr/atmega328pb.ld", "extra-files": [ "targets/avr.S", "src/device/avr/atmega328pb.s" ] } ================================================ FILE: targets/atmega32u4.json ================================================ { "inherits": ["avr"], "cpu": "atmega32u4", "build-tags": ["atmega32u4", "avr5"], "serial": "none", "linkerscript": "src/device/avr/atmega32u4.ld", "extra-files": [ "targets/avr.S", "src/device/avr/atmega32u4.s" ] } ================================================ FILE: targets/atsamd21.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x00000000+0x2000, LENGTH = 0x00040000-0x2000 /* First 8KB used by bootloader */ RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x00008000 } _stack_size = 2K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/atsamd21e18a.json ================================================ { "inherits": ["cortex-m0plus"], "build-tags": ["atsamd21e18a", "atsamd21e18", "atsamd21", "sam"], "serial": "usb", "linkerscript": "targets/atsamd21.ld", "extra-files": [ "src/device/sam/atsamd21e18a.s" ], "openocd-transport": "swd", "openocd-target": "at91samdXX" } ================================================ FILE: targets/atsamd21g18a.json ================================================ { "inherits": ["cortex-m0plus"], "build-tags": ["atsamd21g18a", "atsamd21g18", "atsamd21", "sam"], "serial": "usb", "linkerscript": "targets/atsamd21.ld", "extra-files": [ "src/device/sam/atsamd21g18a.s" ], "openocd-transport": "swd", "openocd-target": "at91samdXX" } ================================================ FILE: targets/atsamd51.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x00000000+0x4000, LENGTH = 0x00080000-0x4000 /* First 16KB used by bootloader */ RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x00030000 } _stack_size = 4K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/atsamd51g19a.json ================================================ { "inherits": ["cortex-m4"], "build-tags": ["atsamd51g19a", "atsamd51g19", "atsamd51", "sam"], "linkerscript": "targets/atsamd51.ld", "extra-files": [ "src/device/sam/atsamd51g19a.s" ], "openocd-transport": "swd", "openocd-target": "atsame5x" } ================================================ FILE: targets/atsamd51j19a.json ================================================ { "inherits": ["cortex-m4"], "build-tags": ["atsamd51j19a", "atsamd51j19", "atsamd51", "sam"], "linkerscript": "targets/atsamd51.ld", "extra-files": [ "src/device/sam/atsamd51j19a.s" ], "openocd-transport": "swd", "openocd-target": "atsame5x" } ================================================ FILE: targets/atsamd51j20a.json ================================================ { "inherits": ["cortex-m4"], "build-tags": ["sam", "atsamd51", "atsamd51j20", "atsamd51j20a"], "linkerscript": "targets/atsamd51j20a.ld", "extra-files": [ "src/device/sam/atsamd51j20a.s" ], "openocd-transport": "swd", "openocd-target": "atsame5x" } ================================================ FILE: targets/atsamd51j20a.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x00000000+0x4000, LENGTH = 0x00100000-0x4000 /* First 16KB used by bootloader */ RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x00040000 } _stack_size = 4K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/atsamd51p19a.json ================================================ { "inherits": ["cortex-m4"], "build-tags": ["atsamd51p19a", "atsamd51p19", "atsamd51", "sam"], "linkerscript": "targets/atsamd51.ld", "extra-files": [ "src/device/sam/atsamd51p19a.s" ], "openocd-transport": "swd", "openocd-target": "atsame5x" } ================================================ FILE: targets/atsamd51p20a.json ================================================ { "inherits": ["cortex-m4"], "build-tags": ["sam", "atsamd51", "atsamd51p20", "atsamd51p20a"], "linkerscript": "targets/atsamd51p20a.ld", "extra-files": [ "src/device/sam/atsamd51p20a.s" ], "openocd-transport": "swd", "openocd-target": "atsame5x" } ================================================ FILE: targets/atsamd51p20a.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x00000000+0x4000, LENGTH = 0x00100000-0x4000 /* First 16KB used by bootloader */ RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x00040000 } _stack_size = 4K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/atsame51j19a.json ================================================ { "inherits": ["cortex-m4"], "build-tags": ["atsame51j19a", "atsame51j19", "atsame51", "atsame5x", "sam"], "linkerscript": "targets/atsame5xx19.ld", "extra-files": [ "src/device/sam/atsame51j19a.s" ], "openocd-transport": "swd", "openocd-target": "atsame5x" } ================================================ FILE: targets/atsame54-xpro.json ================================================ { "inherits": ["atsame54p20a"], "build-tags": ["atsame54_xpro"], "serial": "usb", "flash-method": "openocd", "openocd-interface": "cmsis-dap" } ================================================ FILE: targets/atsame54p20a.json ================================================ { "inherits": ["cortex-m4"], "build-tags": ["sam", "atsame5x", "atsame54", "atsame54p20", "atsame54p20a"], "linkerscript": "targets/atsame5xx20-no-bootloader.ld", "extra-files": [ "src/device/sam/atsame54p20a.s" ], "openocd-transport": "swd", "openocd-target": "atsame5x" } ================================================ FILE: targets/atsame5xx19.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x00000000+0x4000, LENGTH = 0x00080000-0x4000 /* First 16KB used by bootloader */ RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x00030000 } _stack_size = 4K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/atsame5xx20-no-bootloader.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x00000000+0x0000, LENGTH = 0x00100000-0x0000 RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x00040000 } _stack_size = 4K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/attiny1616.json ================================================ { "inherits": ["avrtiny"], "cpu": "attiny1616", "build-tags": ["attiny1616"], "gc": "none", "cflags": [ "-D__AVR_ARCH__=103" ], "linkerscript": "src/device/avr/attiny1616.ld", "extra-files": [ "src/device/avr/attiny1616.s" ], "flash-command": "pymcuprog write -f {hex} --erase --verify -d attiny1616 -t uart -u {port}" } ================================================ FILE: targets/attiny85.json ================================================ { "inherits": ["avr"], "cpu": "attiny85", "build-tags": ["attiny85", "attiny", "avr2", "avr25"], "cflags": [ "-D__AVR_ARCH__=25" ], "linkerscript": "src/device/avr/attiny85.ld", "extra-files": [ "targets/avr.S", "src/device/avr/attiny85.s" ] } ================================================ FILE: targets/avr.S ================================================ ; This file provides common code across AVRs that cannot be implemented directly ; in Go. ; The reset vector is device-specific and is generated by tools/gen-device-avr.py. ; These definitions are necessary because LLVM does not yet know these register ; aliases. See: https://reviews.llvm.org/D96492 #define xl r26 #define xh r27 #define yl r28 #define yh r29 #define zl r30 #define zh r31 ; Ugly hack until https://reviews.llvm.org/D137572 is merged. #if !defined(__AVR_HAVE_ELPM__) && defined(__flash1) #define __AVR_HAVE_ELPM__ #endif ; Startup code .section .text.__vector_RESET .global __vector_RESET __vector_RESET: clr r1 ; r1 is expected to be 0 by the C calling convention ; Set up the stack pointer. ldi xl, lo8(_stack_top) ldi xh, hi8(_stack_top) out 0x3d, xl; SPL out 0x3e, xh; SPH ; Subtract one from the stack pointer, so it doesn't point in the .data section. push r0 ; Initialize .data init_data: ldi xl, lo8(_sdata) ldi xh, hi8(_sdata) ldi yl, lo8(_edata) ldi yh, hi8(_edata) ldi zl, lo8(_sidata) ldi zh, hi8(_sidata) #ifdef __AVR_HAVE_ELPM__ ldi r16, hh8(_sidata) ; RAMPZ = hh8(_sidata) out 0x3b, r16 #endif init_data_loop: cp xl, yl ; if x == y cpc xh, yh breq init_data_end ; goto main #ifdef __AVR_HAVE_ELPM__ elpm r0, Z+ ; r0 = *(z++) #else lpm r0, Z+ ; r0 = *(z++) #endif st X+, r0 ; *(x++) = r0 rjmp init_data_loop ; goto init_data_loop init_data_end: ; main will be placed right after here by the linker script so there's no ; need to jump. ; The only thing this WDT handler really does is disable itself, to get out of ; sleep mode. .section .text.__vector_WDT .global __vector_WDT __vector_WDT: push r16 clr r16 wdr ; Reset watchdog out 0x34, r16 ; Clear reset reason (MCUSR) ; part 1: set WDCE and WDE to enable editing WDTCSR lds r16, 0x60 ; r16 = WDTCSR ori r16, 0x18 ; r16 |= WDCE | WDE sts 0x60, r16 ; WDTCSR = r16 ; part 2: within 4 clock cycles, set the new value for WDTCSR clr r16 sts 0x60, r16 ; WDTCSR = 0 pop r16 reti ================================================ FILE: targets/avr.json ================================================ { "llvm-target": "avr", "build-tags": ["avr", "baremetal", "linux", "arm"], "goos": "linux", "goarch": "arm", "gc": "conservative", "linker": "ld.lld", "scheduler": "none", "rtlib": "compiler-rt", "libc": "picolibc", "default-stack-size": 256, "cflags": [ "-Werror" ], "ldflags": [ "-T", "targets/avr.ld", "--gc-sections" ], "extra-files": [ "src/internal/task/task_stack_avr.S", "src/runtime/asm_avr.S" ], "gdb": ["avr-gdb"] } ================================================ FILE: targets/avr.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0, LENGTH = __flash_size - _bootloader_size RAM (xrw) : ORIGIN = 0x800000 + __ram_start, LENGTH = __ram_size } ENTRY(main) SECTIONS { .text : { KEEP(*(.vectors)) KEEP(*(.text.__vector_RESET)) KEEP(*(.text.main)) /* main must follow the reset handler */ *(.text) *(.text.*) *(.progmem) *(.progmem.*) . = ALIGN(16); /* needed with ld.lld for some reasoon */ } .stack (NOLOAD) : { . += _stack_size; _stack_top = .; } >RAM _sidata = LOADADDR(.data); .data : { _sdata = .; /* used by startup code */ *(.rodata) *(.rodata.*) *(.data) *(.data*) _edata = .; /* used by startup code */ } >RAM AT>FLASH_TEXT .bss : { _sbss = .; /* used by startup code */ *(.bss) *(.bss*) *(COMMON) _ebss = .; /* used by startup code */ } >RAM } /* For the memory allocator. */ _heap_start = _ebss; _heap_end = ORIGIN(RAM) + LENGTH(RAM); _globals_start = _sdata; _globals_end = _ebss; ================================================ FILE: targets/avrtiny.S ================================================ #define __tmp_reg__ r16 #define __zero_reg__ r17 ; Startup code .section .text.__vector_RESET .global __vector_RESET __vector_RESET: clr __zero_reg__ ; this register is expected to be 0 by the C calling convention ; Keep the stack pointer at the default location, which is RAMEND. ; Initialize .data section. .section .text.__do_copy_data,"ax",@progbits .global __do_copy_data __do_copy_data: ldi xl, lo8(__data_start) ldi xh, hi8(__data_start) ldi yl, lo8(__data_end) ldi yh, hi8(__data_end) ldi zl, lo8(__data_load_start) ldi zh, hi8(__data_load_start) 1: ; loop cp xl, yl ; if x == y cpc xh, yh breq 2f ; goto end ld r16, Z+ ; r0 = *(z++) st X+, r16 ; *(x++) = r0 rjmp 1b ; goto loop 2: ; end ; Initialize .bss section. .section .text.__do_clear_bss,"ax",@progbits .global __do_clear_bss __do_clear_bss: ldi xl, lo8(__bss_start) ldi xh, hi8(__bss_start) ldi yl, lo8(__bss_end) 1: ; loop cp xl, yl ; if x == y breq 2f ; goto end st X+, __zero_reg__ ; *(x++) = 0 rjmp 1b ; goto loop 2: ; end ================================================ FILE: targets/avrtiny.json ================================================ { "llvm-target": "avr", "build-tags": ["avr", "avrtiny", "baremetal", "linux", "arm"], "goos": "linux", "goarch": "arm", "gc": "conservative", "linker": "ld.lld", "scheduler": "none", "rtlib": "compiler-rt", "libc": "picolibc", "default-stack-size": 256, "cflags": [ "-Werror" ], "ldflags": [ "-T", "targets/avrtiny.ld", "--gc-sections" ], "extra-files": [ "src/internal/task/task_stack_avr.S", "src/runtime/asm_avr.S", "targets/avrtiny.S" ], "gdb": ["avr-gdb"] } ================================================ FILE: targets/avrtiny.ld ================================================ /* Linker script for AVRs with a unified flash and RAM address space. This * includes the ATtiny10 and the ATtiny1616. */ MEMORY { FLASH_TEXT (x) : ORIGIN = 0, LENGTH = __flash_size FLASH_DATA (r) : ORIGIN = __mapped_flash_start, LENGTH = __flash_size RAM (xrw) : ORIGIN = __ram_start, LENGTH = __ram_size } ENTRY(main) SECTIONS { .text : { KEEP(*(.vectors)) *(.text.__vector_RESET) KEEP(*(.text.__do_copy_data)) /* TODO: only use when __do_copy_data is requested */ KEEP(*(.text.__do_clear_bss)) KEEP(*(.text.main)) /* main must follow the reset handler */ *(.text) *(.text.*) } > FLASH_TEXT /* Read-only data is stored in flash, but is read from an offset (0x4000 or * 0x8000 depending on the chip). This requires some weird math to get it in * the right place. */ .rodata ORIGIN(FLASH_DATA) + ADDR(.text) + SIZEOF(.text): { *(.rodata) *(.rodata.*) } AT>FLASH_TEXT /* The address to which the data section should be copied by the startup * code. */ __data_load_start = ORIGIN(FLASH_DATA) + LOADADDR(.data); .data : { __data_start = .; /* used by startup code */ *(.data) *(.data*) __data_end = .; /* used by startup code */ } >RAM AT>FLASH_TEXT .bss : { __bss_start = .; /* used by startup code */ *(.bss) *(.bss*) *(COMMON) __bss_end = .; /* used by startup code */ } >RAM } ================================================ FILE: targets/badger2040-w.json ================================================ { "inherits": [ "rp2040" ], "serial-port": ["2e8a:0003"], "default-stack-size": 8192, "build-tags": ["badger2040_w", "cyw43439"], "ldflags": [ "--defsym=__flash_size=1020K" ], "extra-files": [ "targets/pico-boot-stage2.S" ] } ================================================ FILE: targets/badger2040.json ================================================ { "inherits": [ "rp2040" ], "serial-port": ["2e8a:0003"], "default-stack-size": 8192, "build-tags": ["badger2040"], "ldflags": [ "--defsym=__flash_size=1020K" ], "extra-files": [ "targets/pico-boot-stage2.S" ] } ================================================ FILE: targets/bluemicro840.json ================================================ { "inherits": ["nrf52840", "nrf52840-s140v6-uf2"], "build-tags": ["bluemicro840"], "serial-port": ["1d50:6161"], "msd-volume-name": ["BLUEMICRO"] } ================================================ FILE: targets/bluepill-clone.json ================================================ { "inherits": ["bluepill"], "openocd-commands": ["set CPUTAPID 0x2ba01477"] } ================================================ FILE: targets/bluepill.json ================================================ { "inherits": ["cortex-m3"], "build-tags": ["bluepill", "stm32f103", "stm32f1", "stm32"], "serial": "uart", "linkerscript": "targets/stm32.ld", "extra-files": [ "src/device/stm32/stm32f103.s" ], "flash-method": "openocd", "openocd-interface": "stlink-v2", "openocd-target": "stm32f1x" } ================================================ FILE: targets/btt-skr-pico.json ================================================ { "inherits": [ "rp2040" ], "build-tags": ["btt_skr_pico"], "serial-port": ["2e8a:000A"], "ldflags": [ "--defsym=__flash_size=16M" ], "extra-files": [ "targets/pico-boot-stage2.S" ] } ================================================ FILE: targets/challenger-rp2040.json ================================================ { "inherits": [ "rp2040" ], "serial-port": ["2e8a:1023"], "default-stack-size": 8192, "build-tags": ["challenger_rp2040"], "ldflags": [ "--defsym=__flash_size=8M" ], "extra-files": [ "targets/feather-rp2040-boot-stage2.S" ] } ================================================ FILE: targets/circuitplay-bluefruit.json ================================================ { "inherits": ["nrf52840", "nrf52840-s140v6-uf2"], "build-tags": ["circuitplay_bluefruit"], "serial-port": ["239a:8045"], "msd-volume-name": ["CPLAYBTBOOT"] } ================================================ FILE: targets/circuitplay-express.json ================================================ { "inherits": ["atsamd21g18a"], "build-tags": ["circuitplay_express"], "flash-1200-bps-reset": "true", "flash-method": "msd", "serial-port": ["239a:8018"], "msd-volume-name": ["CPLAYBOOT"], "msd-firmware-name": "firmware.uf2" } ================================================ FILE: targets/clue-alpha.json ================================================ { "inherits": ["nrf52840", "nrf52840-s140v6-uf2"], "build-tags": ["clue_alpha"], "serial-port": ["239a:8072", "239a:8071"], "msd-volume-name": ["CLUEBOOT"] } ================================================ FILE: targets/clue.json ================================================ { "inherits": ["clue-alpha"] } ================================================ FILE: targets/cortex-m-qemu.json ================================================ { "inherits": ["cortex-m3"], "build-tags": ["qemu", "lm3s6965"], "linkerscript": "targets/lm3s6965.ld", "default-stack-size": 4096, "extra-files": [ "targets/cortex-m-qemu.s" ], "emulator": "qemu-system-arm -machine lm3s6965evb -semihosting -nographic -kernel {}" } ================================================ FILE: targets/cortex-m-qemu.s ================================================ // Generic Cortex-M interrupt vector. // This vector is used by the Cortex-M QEMU target. .cfi_sections .debug_frame .syntax unified // This is the default handler for interrupts, if triggered but not defined. .section .text.Default_Handler .global Default_Handler .type Default_Handler, %function Default_Handler: .cfi_startproc wfe b Default_Handler .cfi_endproc .size Default_Handler, .-Default_Handler // Avoid the need for repeated .weak and .set instructions. .macro IRQ handler .weak \handler .set \handler, Default_Handler .endm .section .isr_vector, "a", %progbits .global __isr_vector __isr_vector: // Interrupt vector as defined by Cortex-M, starting with the stack top. // On reset, SP is initialized with *0x0 and PC is loaded with *0x4, loading // _stack_top and Reset_Handler. .long _stack_top .long Reset_Handler .long NMI_Handler .long HardFault_Handler .long MemoryManagement_Handler .long BusFault_Handler .long UsageFault_Handler .long 0 .long 0 .long 0 .long 0 .long SVC_Handler .long DebugMon_Handler .long 0 .long PendSV_Handler .long SysTick_Handler // Define default implementations for interrupts, redirecting to // Default_Handler when not implemented. IRQ NMI_Handler IRQ HardFault_Handler IRQ MemoryManagement_Handler IRQ BusFault_Handler IRQ UsageFault_Handler IRQ SVC_Handler IRQ DebugMon_Handler IRQ PendSV_Handler IRQ SysTick_Handler .size __isr_vector, .-__isr_vector ================================================ FILE: targets/cortex-m.json ================================================ { "build-tags": ["cortexm", "baremetal", "linux", "arm"], "goos": "linux", "goarch": "arm", "gc": "conservative", "scheduler": "tasks", "linker": "ld.lld", "rtlib": "compiler-rt", "libc": "picolibc", "automatic-stack-size": true, "default-stack-size": 2048, "cflags": [ "-Werror", "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft", "-fno-exceptions", "-fno-unwind-tables", "-fno-asynchronous-unwind-tables", "-ffunction-sections", "-fdata-sections" ], "ldflags": [ "--emit-relocs", "--gc-sections" ], "extra-files": [ "src/device/arm/cortexm.S", "src/internal/task/task_stack_cortexm.S", "src/runtime/asm_arm.S" ], "gdb": ["gdb-multiarch", "arm-none-eabi-gdb", "gdb"] } ================================================ FILE: targets/cortex-m0.json ================================================ { "inherits": ["cortex-m"], "llvm-target": "thumbv6m-unknown-unknown-eabi", "cpu": "cortex-m0", "features": "+armv6-m,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } ================================================ FILE: targets/cortex-m0plus.json ================================================ { "inherits": ["cortex-m"], "llvm-target": "thumbv6m-unknown-unknown-eabi", "cpu": "cortex-m0plus", "features": "+armv6-m,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } ================================================ FILE: targets/cortex-m3.json ================================================ { "inherits": ["cortex-m"], "llvm-target": "thumbv7m-unknown-unknown-eabi", "cpu": "cortex-m3", "features": "+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } ================================================ FILE: targets/cortex-m33.json ================================================ { "inherits": ["cortex-m"], "llvm-target": "thumbv8m.main-unknown-unknown-eabi", "cpu": "cortex-m33", "features": "+armv8-m.main,+dsp,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } ================================================ FILE: targets/cortex-m4.json ================================================ { "inherits": ["cortex-m"], "llvm-target": "thumbv7em-unknown-unknown-eabi", "cpu": "cortex-m4", "features": "+armv7e-m,+dsp,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } ================================================ FILE: targets/cortex-m7.json ================================================ { "inherits": ["cortex-m"], "llvm-target": "thumbv7em-unknown-unknown-eabi", "cpu": "cortex-m7", "features": "+armv7e-m,+dsp,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } ================================================ FILE: targets/d1mini.json ================================================ { "inherits": ["nodemcu"] } ================================================ FILE: targets/digispark.json ================================================ { "inherits": ["attiny85"], "build-tags": ["digispark"], "ldflags": [ "--defsym=_bootloader_size=2180", "--defsym=_stack_size=128" ], "flash-command": "micronucleus --run {hex}", "emulator": "simavr -m attiny85 -f 16000000 {}" } ================================================ FILE: targets/elecrow-rp2040.json ================================================ { "inherits": ["rp2040"], "build-tags": ["elecrow_rp2040", "comboat_fw"], "serial-port": ["2e8a:000a"], "default-stack-size": 8192, "ldflags": [ "--defsym=__flash_size=8M" ], "extra-files": [ "targets/pico-boot-stage2.S" ] } ================================================ FILE: targets/elecrow-rp2350.json ================================================ { "inherits": ["rp2350"], "build-tags": ["elecrow_rp2350", "comboat_fw"], "serial-port": ["2e8a:000f"], "default-stack-size": 8192, "ldflags": [ "--defsym=__flash_size=8M" ], "extra-files": [ "targets/pico-boot-stage2.S" ] } ================================================ FILE: targets/esp-c3-32s-kit.json ================================================ { "inherits": ["esp32c3"], "build-tags": ["esp_c3_32s_kit"], "serial-port": ["1a86:7523"] } ================================================ FILE: targets/esp32-c3-devkit-rust-1.json ================================================ { "inherits": ["esp32c3"], "build-tags": ["esp32_c3_devkit_rust_1"] } ================================================ FILE: targets/esp32-coreboard-v2.json ================================================ { "inherits": ["esp32"], "build-tags": ["esp32_coreboard_v2"] } ================================================ FILE: targets/esp32-mini32.json ================================================ { "inherits": ["esp32-coreboard-v2"] } ================================================ FILE: targets/esp32.json ================================================ { "inherits": ["xtensa"], "cpu": "esp32", "features": "+atomctl,+bool,+clamps,+coprocessor,+debug,+density,+dfpaccel,+div32,+exception,+fp,+highpriinterrupts,+interrupt,+loop,+mac16,+memctl,+minmax,+miscsr,+mul32,+mul32high,+nsa,+prid,+regprotect,+rvector,+s32c1i,+sext,+threadptr,+timerint,+windowed", "build-tags": ["esp32", "esp"], "scheduler": "tasks", "serial": "uart", "linker": "ld.lld", "default-stack-size": 2048, "rtlib": "compiler-rt", "libc": "picolibc", "linkerscript": "targets/esp32.ld", "extra-files": [ "src/device/esp/esp32.S", "src/internal/task/task_stack_esp32.S" ], "binary-format": "esp32", "flash-command": "esptool.py --chip=esp32 --port {port} write_flash 0x1000 {bin} -ff 80m -fm dout", "emulator": "qemu-system-xtensa -machine esp32 -nographic -drive file={img},if=mtd,format=raw", "gdb": ["xtensa-esp32-elf-gdb"] } ================================================ FILE: targets/esp32.ld ================================================ /* Linker script for the ESP32 */ MEMORY { /* Data RAM. Allows byte access. * There are various data RAM regions: * SRAM2: 0x3FFA_E000..0x3FFD_FFFF (72 + 128 = 200K) * SRAM1: 0x3FFE_0000..0x3FFF_FFFF (128K) * This gives us 328K of contiguous RAM, which is the largest span possible. * SRAM1 has other addresses as well but the datasheet seems to indicate * these are aliases. */ DRAM (rw) : ORIGIN = 0x3FFAE000, LENGTH = 200K + 128K /* Internal SRAM 1 + 2 */ /* Instruction RAM. */ IRAM (x) : ORIGIN = 0x40080000, LENGTH = 128K /* Internal SRAM 0 */ } /* The entry point. It is set in the image flashed to the chip, so must be * defined. */ ENTRY(call_start_cpu0) SECTIONS { /* Constant literals and code. Loaded into IRAM for now. Eventually, most * code should be executed directly from flash. * Note that literals must be before code for the l32r instruction to work. */ .text : ALIGN(4) { *(.literal.call_start_cpu0) *(.text.call_start_cpu0) *(.literal .text) *(.literal.* .text.*) } >IRAM /* Put the stack at the bottom of DRAM, so that the application will * crash on stack overflow instead of silently corrupting memory. * See: http://blog.japaric.io/stack-overflow-protection/ */ .stack (NOLOAD) : { . = ALIGN(16); . += _stack_size; _stack_top = .; } >DRAM /* Constant global variables. * They are loaded in DRAM for ease of use. Eventually they should be stored * in flash and loaded directly from there but they're kept in RAM to make * sure they can always be accessed (even in interrupts). */ .rodata : ALIGN(4) { *(.rodata) *(.rodata.*) } >DRAM /* Mutable global variables. */ .data : ALIGN(4) { _sdata = ABSOLUTE(.); *(.data) *(.data.*) _edata = ABSOLUTE(.); } >DRAM /* Check that the boot ROM stack (for the APP CPU) does not overlap with the * data that is loaded by the boot ROM. There may be ways to avoid this * issue if it occurs in practice. * The magic value here is _stack_sentry in the boot ROM ELF file. */ ASSERT(_edata < 0x3ffe1320, "the .data section overlaps with the stack used by the boot ROM, possibly causing corruption at startup") /* Global variables that are mutable and zero-initialized. * These must be zeroed at startup (unlike data, which is loaded by the * bootloader). */ .bss (NOLOAD) : ALIGN(4) { . = ALIGN (4); _sbss = ABSOLUTE(.); *(.bss) *(.bss.*) . = ALIGN (4); _ebss = ABSOLUTE(.); } >DRAM } /* For the garbage collector. */ _globals_start = _sdata; _globals_end = _ebss; _heap_start = _ebss; _heap_end = ORIGIN(DRAM) + LENGTH(DRAM); _stack_size = 4K; /* From ESP-IDF: * components/esp_rom/esp32/ld/esp32.rom.newlib-funcs.ld * This is the subset that is sometimes used by LLVM during codegen, and thus * must always be present. */ memcpy = 0x4000c2c8; memmove = 0x4000c3c0; memset = 0x4000c44c; /* From ESP-IDF: * components/esp_rom/esp32/ld/esp32.rom.libgcc.ld * These are called from LLVM during codegen. The original license is Apache * 2.0, but I believe that a list of function names and addresses can't really * be copyrighted. */ __absvdi2 = 0x4006387c; __absvsi2 = 0x40063868; __adddf3 = 0x40002590; __addsf3 = 0x400020e8; __addvdi3 = 0x40002cbc; __addvsi3 = 0x40002c98; __ashldi3 = 0x4000c818; __ashrdi3 = 0x4000c830; __bswapdi2 = 0x40064b08; __bswapsi2 = 0x40064ae0; __clrsbdi2 = 0x40064b7c; __clrsbsi2 = 0x40064b64; __clzdi2 = 0x4000ca50; __clzsi2 = 0x4000c7e8; __cmpdi2 = 0x40063820; __ctzdi2 = 0x4000ca64; __ctzsi2 = 0x4000c7f0; __divdc3 = 0x400645a4; __divdf3 = 0x40002954; __divdi3 = 0x4000ca84; __divsi3 = 0x4000c7b8; __eqdf2 = 0x400636a8; __eqsf2 = 0x40063374; __extendsfdf2 = 0x40002c34; __ffsdi2 = 0x4000ca2c; __ffssi2 = 0x4000c804; __fixdfdi = 0x40002ac4; __fixdfsi = 0x40002a78; __fixsfdi = 0x4000244c; __fixsfsi = 0x4000240c; __fixunsdfsi = 0x40002b30; __fixunssfdi = 0x40002504; __fixunssfsi = 0x400024ac; __floatdidf = 0x4000c988; __floatdisf = 0x4000c8c0; __floatsidf = 0x4000c944; __floatsisf = 0x4000c870; __floatundidf = 0x4000c978; __floatundisf = 0x4000c8b0; __floatunsidf = 0x4000c938; __floatunsisf = 0x4000c864; __gcc_bcmp = 0x40064a70; __gedf2 = 0x40063768; __gesf2 = 0x4006340c; __gtdf2 = 0x400636dc; __gtsf2 = 0x400633a0; __ledf2 = 0x40063704; __lesf2 = 0x400633c0; __lshrdi3 = 0x4000c84c; __ltdf2 = 0x40063790; __ltsf2 = 0x4006342c; __moddi3 = 0x4000cd4c; __modsi3 = 0x4000c7c0; __muldc3 = 0x40063c90; __muldf3 = 0x4006358c; __muldi3 = 0x4000c9fc; __mulsf3 = 0x400632c8; __mulsi3 = 0x4000c7b0; __mulvdi3 = 0x40002d78; __mulvsi3 = 0x40002d60; __nedf2 = 0x400636a8; __negdf2 = 0x400634a0; __negdi2 = 0x4000ca14; __negsf2 = 0x400020c0; __negvdi2 = 0x40002e98; __negvsi2 = 0x40002e78; __nesf2 = 0x40063374; __nsau_data = 0x3ff96544; __paritysi2 = 0x40002f3c; __popcount_tab = 0x3ff96544; __popcountdi2 = 0x40002ef8; __popcountsi2 = 0x40002ed0; __powidf2 = 0x400638e4; __subdf3 = 0x400026e4; __subsf3 = 0x400021d0; __subvdi3 = 0x40002d20; __subvsi3 = 0x40002cf8; __truncdfsf2 = 0x40002b90; __ucmpdi2 = 0x40063840; __udiv_w_sdiv = 0x40064bec; __udivdi3 = 0x4000cff8; __udivmoddi4 = 0x40064bf4; __udivsi3 = 0x4000c7c8; __umoddi3 = 0x4000d280; __umodsi3 = 0x4000c7d0; __umulsidi3 = 0x4000c7d8; __unorddf2 = 0x400637f4; __unordsf2 = 0x40063478; ================================================ FILE: targets/esp32c3-12f.json ================================================ { "inherits": ["esp32c3"], "build-tags": ["esp32c312f"] } ================================================ FILE: targets/esp32c3-supermini.json ================================================ { "inherits": ["esp32c3"], "build-tags": ["esp32c3_supermini"] } ================================================ FILE: targets/esp32c3.json ================================================ { "inherits": ["riscv32"], "features": "+32bit,+c,+m,+zmmul,-a,-b,-d,-e,-experimental-sdext,-experimental-sdtrig,-experimental-smctr,-experimental-ssctr,-experimental-svukte,-experimental-xqcia,-experimental-xqciac,-experimental-xqcicli,-experimental-xqcicm,-experimental-xqcics,-experimental-xqcicsr,-experimental-xqciint,-experimental-xqcilo,-experimental-xqcilsm,-experimental-xqcisls,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-experimental-zvbc32e,-experimental-zvkgs,-f,-h,-relax,-sha,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smdbltrp,-smepmp,-smmpm,-smnpm,-smrnmi,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssdbltrp,-ssnpm,-sspm,-ssqosid,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-supm,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-svvptc,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xesppie,-xmipscmove,-xmipslsp,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zaamo,-zabha,-zacas,-zalrsc,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", "build-tags": ["esp32c3", "esp"], "serial": "usb", "rtlib": "compiler-rt", "libc": "picolibc", "cflags": [ "-march=rv32imc" ], "linkerscript": "targets/esp32c3.ld", "extra-files": [ "src/device/esp/esp32c3.S" ], "binary-format": "esp32c3", "flash-command": "esptool.py --chip=esp32c3 --port {port} write_flash 0x0 {bin}", "serial-port": ["303a:1001"], "openocd-interface": "esp_usb_jtag", "openocd-target": "esp32c3", "openocd-commands": ["gdb_memory_map disable"], "gdb": ["riscv32-esp-elf-gdb"] } ================================================ FILE: targets/esp32c3.ld ================================================ /* Linker script for the ESP32-C3 * * The ESP32-C3 has a rather funky memory layout, more like its Xtensa * predecessors than like other RISC-V chips: * - It has 384kB of regular RAM. This RAM can be used both as data RAM and * instruction RAM, but needs to be accessed via a different address space * (DRAM/IRAM). * - It has another 16kB of RAM, that could be used as regular RAM but is * normally used by the flash cache. * - It has 8MB of address space for the DROM and IROM, but for some reason * this address space is shared. So it isn't possible to map all DROM at * 0x3C000000 and all DRAM at 0x42000000: they would overlap. * - The MMU works in pages of 64kB, which means the bottom 16 bits of the * address in flash and the address in DROM/IROM need to match. * - Memory in DRAM and IRAM is loaded at reset by the ROM bootloader. * Luckily, this doesn't have significant alignment requirements. * * This linker script has been written to carefully work around (or with) these * limitations: * - It adds dummy sections so that the bottom 16 bits of the virtual address * and the physical address (in the generated firmware image) match. * - It also offsets sections that share an address space using those same * dummy sections. * - It sorts the sections by load address, to avoid surprises as esptool.py * also does it. * This way, it's possible to create a very small firmware image that still * conforms to the expectations of esptool.py and the ROM bootloader. */ MEMORY { /* Note: DRAM and IRAM below are actually in the same 384K address space. */ DRAM (rw) : ORIGIN = 0x3FC80000, LENGTH = 384K /* Internal SRAM 1 (data bus) */ IRAM (x) : ORIGIN = 0x40380000, LENGTH = 384K /* Internal SRAM 1 (instruction bus) */ /* Note: DROM and IROM below are actually in the same 8M address space. */ DROM (r) : ORIGIN = 0x3C000000, LENGTH = 8M /* Data bus (read-only) */ IROM (rx) : ORIGIN = 0x42000000, LENGTH = 8M /* Instruction bus */ } /* The entry point. It is set in the image flashed to the chip, so must be * defined. */ ENTRY(call_start_cpu0) SECTIONS { /* Dummy section to make sure the .rodata section starts exactly behind the * image header. */ .rodata_dummy (NOLOAD): ALIGN(4) { . += 0x18; /* image header at start of flash: esp_image_header_t */ . += 0x8; /* DROM segment header (8 bytes) */ } > DROM /* Constant global variables, stored in DROM. */ .rodata : ALIGN(4) { *(.rodata*) . = ALIGN (4); } >DROM /* Put the stack at the bottom of DRAM, so that the application will * crash on stack overflow instead of silently corrupting memory. * See: http://blog.japaric.io/stack-overflow-protection/ * TODO: this might not actually work because memory protection hasn't been set up. */ .stack (NOLOAD) : { . = ALIGN(16); . += _stack_size; _stack_top = .; } >DRAM /* Global variables that are mutable and zero-initialized. * These must be zeroed at startup (unlike data, which is loaded by the * bootloader). */ .bss (NOLOAD) : ALIGN(4) { . = ALIGN (4); _sbss = ABSOLUTE(.); *(.sbss) *(.bss .bss.*) . = ALIGN (4); _ebss = ABSOLUTE(.); } >DRAM /* Mutable global variables. This data (in the DRAM segment) is initialized * by the ROM bootloader. */ .data : ALIGN(4) { . = ALIGN (4); _sdata = ABSOLUTE(.); *(.sdata) *(.data .data.*) *(.dram*) . = ALIGN (4); _edata = ABSOLUTE(.); } >DRAM /* Dummy section to make sure the .init section (in the IRAM segment) is just * behind the DRAM segment. For IRAM and DRAM, we luckily don't have to * worry about 64kB pages or image headers as they're loaded in RAM by the * bootloader (not mapped from flash). */ .iram_dummy (NOLOAD): ALIGN(4) { . += SIZEOF(.stack); . += SIZEOF(.bss); . += SIZEOF(.data); } > IRAM /* IRAM segment. This contains some functions that always need to be loaded * in IRAM, and contains initialization code. * The initialization code is later reclaimed for the heap, so no RAM is * wasted. */ .iram : ALIGN(4) { *(.iram*) *(.wifislprxiram*) *(.wifiextrairam*) *(.wifi0iram*) *(.wifislpiram*) *(.wifirxiram*) __init_start = .; *(.init) __init_end = .; } >IRAM /* Dummy section to put the IROM segment exactly behind the IRAM segment. * This has to follow the app image format exactly. */ .text_dummy (NOLOAD): ALIGN(4) { /* Note: DRAM and DROM are not always present so the header should only * be inserted if it actually exists. */ . += 0x18; /* esp_image_header_t */ . += SIZEOF(.rodata) + ((SIZEOF(.rodata) != 0) ? 0x8 : 0); /* DROM segment (optional) */ . += SIZEOF(.data) + ((SIZEOF(.data) != 0) ? 0x8 : 0); /* DRAM segment (optional) */ . += SIZEOF(.iram) + 0x8; /* IRAM segment */ . += 0x8; /* IROM segment header */ } > IROM /* IROM segment. This contains all the actual code and is placed right after * the DROM segment. */ .text : ALIGN(4) { . = ALIGN (256); *(.text.exception_vectors) . = ALIGN (4); *(.text .text.*) } >IROM /DISCARD/ : { *(.eh_frame) /* causes 'no memory region specified' error in lld */ } /* Check that the boot ROM stack (for the APP CPU) does not overlap with the * data that is loaded by the boot ROM. This is unlikely to happen in * practice. * The magic value comes from here: * https://github.com/espressif/esp-idf/blob/61299f879e/components/bootloader/subproject/main/ld/esp32c3/bootloader.ld#L191 */ ASSERT((_edata + SIZEOF(.iram)) < 0x3FCDE710, "the .iram section overlaps with the stack used by the boot ROM, possibly causing corruption at startup") } /* For the garbage collector. * Note that _heap_start starts after _edata + most of the IRAM section. * It starts just before the initialisation code, which isn't necessary anymore * after startup and can thus be overwritten by the heap. */ _globals_start = _sbss; _globals_end = _edata; _heap_start = _edata + SIZEOF(.iram) - (__init_end - __init_start); _heap_end = ORIGIN(DRAM) + LENGTH(DRAM); _stack_size = 4K; /* ROM functions used for setting up the flash mapping. */ Cache_Invalidate_ICache_All = 0x400004d8; Cache_Suspend_ICache = 0x40000524; Cache_Resume_ICache = 0x40000528; Cache_MMU_Init = 0x4000055c; Cache_Dbus_MMU_Set = 0x40000564; /* From ESP-IDF: * components/esp_rom/esp32c3/ld/esp32c3.rom.libgcc.ld * These are called from LLVM during codegen. The original license is Apache * 2.0. */ __absvdi2 = 0x40000764; __absvsi2 = 0x40000768; __adddf3 = 0x4000076c; __addsf3 = 0x40000770; __addvdi3 = 0x40000774; __addvsi3 = 0x40000778; __ashldi3 = 0x4000077c; __ashrdi3 = 0x40000780; __bswapdi2 = 0x40000784; __bswapsi2 = 0x40000788; __clear_cache = 0x4000078c; __clrsbdi2 = 0x40000790; __clrsbsi2 = 0x40000794; __clzdi2 = 0x40000798; __clzsi2 = 0x4000079c; __cmpdi2 = 0x400007a0; __ctzdi2 = 0x400007a4; __ctzsi2 = 0x400007a8; __divdc3 = 0x400007ac; __divdf3 = 0x400007b0; __divdi3 = 0x400007b4; __divsc3 = 0x400007b8; __divsf3 = 0x400007bc; __divsi3 = 0x400007c0; __eqdf2 = 0x400007c4; __eqsf2 = 0x400007c8; __extendsfdf2 = 0x400007cc; __ffsdi2 = 0x400007d0; __ffssi2 = 0x400007d4; __fixdfdi = 0x400007d8; __fixdfsi = 0x400007dc; __fixsfdi = 0x400007e0; __fixsfsi = 0x400007e4; __fixunsdfsi = 0x400007e8; __fixunssfdi = 0x400007ec; __fixunssfsi = 0x400007f0; __floatdidf = 0x400007f4; __floatdisf = 0x400007f8; __floatsidf = 0x400007fc; __floatsisf = 0x40000800; __floatundidf = 0x40000804; __floatundisf = 0x40000808; __floatunsidf = 0x4000080c; __floatunsisf = 0x40000810; __gcc_bcmp = 0x40000814; __gedf2 = 0x40000818; __gesf2 = 0x4000081c; __gtdf2 = 0x40000820; __gtsf2 = 0x40000824; __ledf2 = 0x40000828; __lesf2 = 0x4000082c; __lshrdi3 = 0x40000830; __ltdf2 = 0x40000834; __ltsf2 = 0x40000838; __moddi3 = 0x4000083c; __modsi3 = 0x40000840; __muldc3 = 0x40000844; __muldf3 = 0x40000848; __muldi3 = 0x4000084c; __mulsc3 = 0x40000850; __mulsf3 = 0x40000854; __mulsi3 = 0x40000858; __mulvdi3 = 0x4000085c; __mulvsi3 = 0x40000860; __nedf2 = 0x40000864; __negdf2 = 0x40000868; __negdi2 = 0x4000086c; __negsf2 = 0x40000870; __negvdi2 = 0x40000874; __negvsi2 = 0x40000878; __nesf2 = 0x4000087c; __paritysi2 = 0x40000880; __popcountdi2 = 0x40000884; __popcountsi2 = 0x40000888; __powidf2 = 0x4000088c; __powisf2 = 0x40000890; __subdf3 = 0x40000894; __subsf3 = 0x40000898; __subvdi3 = 0x4000089c; __subvsi3 = 0x400008a0; __truncdfsf2 = 0x400008a4; __ucmpdi2 = 0x400008a8; __udivdi3 = 0x400008ac; __udivmoddi4 = 0x400008b0; __udivsi3 = 0x400008b4; __udiv_w_sdiv = 0x400008b8; __umoddi3 = 0x400008bc; __umodsi3 = 0x400008c0; __unorddf2 = 0x400008c4; __unordsf2 = 0x400008c8; /* From ESP-IDF: * components/esp_rom/esp32c3/ld/esp32c3.rom.newlib.ld * These are called during codegen and thus it's a good idea to make them always * available. ROM functions may also be faster than functions in IROM (that go * through the flash cache) and are always available in interrupts. */ memset = 0x40000354; memcpy = 0x40000358; memmove = 0x4000035c; /* From ESP-IDF: * components/esp_rom/esp32c3/ld/esp32c3.rom.ld * These are needed for wifi/BLE support and are available on the Apache 2.0 * license. */ /* ROM function interface esp32c3.rom.ld for esp32c3 * * * Generated from ./interface-esp32c3.yml md5sum 93b28a9e1fe42d212018eb4336849208 * * Compatible with ROM where ECO version equal or greater to 0. * * THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT. */ /*************************************** Group common ***************************************/ /* Functions */ rtc_get_reset_reason = 0x40000018; analog_super_wdt_reset_happened = 0x4000001c; jtag_cpu_reset_happened = 0x40000020; rtc_get_wakeup_cause = 0x40000024; rtc_boot_control = 0x40000028; rtc_select_apb_bridge = 0x4000002c; rtc_unhold_all_pads = 0x40000030; set_rtc_memory_crc = 0x40000034; cacl_rtc_memory_crc = 0x40000038; ets_is_print_boot = 0x4000003c; ets_printf = 0x40000040; ets_install_putc1 = 0x40000044; ets_install_uart_printf = 0x40000048; ets_install_putc2 = 0x4000004c; PROVIDE( ets_delay_us = 0x40000050 ); ets_get_stack_info = 0x40000054; ets_install_lock = 0x40000058; ets_backup_dma_copy = 0x4000005c; ets_apb_backup_init_lock_func = 0x40000060; UartRxString = 0x40000064; uart_tx_one_char = 0x40000068; uart_tx_one_char2 = 0x4000006c; uart_rx_one_char = 0x40000070; uart_rx_one_char_block = 0x40000074; uart_rx_readbuff = 0x40000078; uartAttach = 0x4000007c; uart_tx_flush = 0x40000080; uart_tx_wait_idle = 0x40000084; uart_div_modify = 0x40000088; multofup = 0x4000008c; software_reset = 0x40000090; software_reset_cpu = 0x40000094; assist_debug_clock_enable = 0x40000098; assist_debug_record_enable = 0x4000009c; clear_super_wdt_reset_flag = 0x400000a0; disable_default_watchdog = 0x400000a4; send_packet = 0x400000a8; recv_packet = 0x400000ac; GetUartDevice = 0x400000b0; UartDwnLdProc = 0x400000b4; Uart_Init = 0x400000b8; ets_set_user_start = 0x400000bc; /* Data (.data, .bss, .rodata) */ ets_rom_layout_p = 0x3ff1fffc; ets_ops_table_ptr = 0x3fcdfffc; /*************************************** Group miniz ***************************************/ /* Functions */ mz_adler32 = 0x400000c0; mz_crc32 = 0x400000c4; mz_free = 0x400000c8; tdefl_compress = 0x400000cc; tdefl_compress_buffer = 0x400000d0; tdefl_compress_mem_to_heap = 0x400000d4; tdefl_compress_mem_to_mem = 0x400000d8; tdefl_compress_mem_to_output = 0x400000dc; tdefl_get_adler32 = 0x400000e0; tdefl_get_prev_return_status = 0x400000e4; tdefl_init = 0x400000e8; tdefl_write_image_to_png_file_in_memory = 0x400000ec; tdefl_write_image_to_png_file_in_memory_ex = 0x400000f0; tinfl_decompress = 0x400000f4; tinfl_decompress_mem_to_callback = 0x400000f8; tinfl_decompress_mem_to_heap = 0x400000fc; tinfl_decompress_mem_to_mem = 0x40000100; /*************************************** Group tjpgd ***************************************/ /* Functions */ PROVIDE( jd_prepare = 0x40000104 ); PROVIDE( jd_decomp = 0x40000108 ); /*************************************** Group spiflash_legacy ***************************************/ /* Functions */ PROVIDE( esp_rom_spiflash_wait_idle = 0x4000010c ); PROVIDE( esp_rom_spiflash_write_encrypted = 0x40000110 ); PROVIDE( esp_rom_spiflash_write_encrypted_dest = 0x40000114 ); PROVIDE( esp_rom_spiflash_write_encrypted_enable = 0x40000118 ); PROVIDE( esp_rom_spiflash_write_encrypted_disable = 0x4000011c ); PROVIDE( esp_rom_spiflash_erase_chip = 0x40000120 ); PROVIDE( esp_rom_spiflash_erase_block = 0x40000124 ); PROVIDE( esp_rom_spiflash_erase_sector = 0x40000128 ); PROVIDE( esp_rom_spiflash_write = 0x4000012c ); PROVIDE( esp_rom_spiflash_read = 0x40000130 ); PROVIDE( esp_rom_spiflash_config_param = 0x40000134 ); PROVIDE( esp_rom_spiflash_read_user_cmd = 0x40000138 ); PROVIDE( esp_rom_spiflash_select_qio_pins = 0x4000013c ); PROVIDE( esp_rom_spiflash_unlock = 0x40000140 ); PROVIDE( esp_rom_spi_flash_auto_sus_res = 0x40000144 ); PROVIDE( esp_rom_spi_flash_send_resume = 0x40000148 ); PROVIDE( esp_rom_spi_flash_update_id = 0x4000014c ); PROVIDE( esp_rom_spiflash_config_clk = 0x40000150 ); PROVIDE( esp_rom_spiflash_config_readmode = 0x40000154 ); PROVIDE( esp_rom_spiflash_read_status = 0x40000158 ); PROVIDE( esp_rom_spiflash_read_statushigh = 0x4000015c ); PROVIDE( esp_rom_spiflash_write_status = 0x40000160 ); PROVIDE( esp_rom_spiflash_attach = 0x40000164 ); PROVIDE( spi_flash_get_chip_size = 0x40000168 ); PROVIDE( spi_flash_guard_set = 0x4000016c ); PROVIDE( spi_flash_guard_get = 0x40000170 ); PROVIDE( spi_flash_write_config_set = 0x40000174 ); PROVIDE( spi_flash_write_config_get = 0x40000178 ); PROVIDE( spi_flash_safe_write_address_func_set = 0x4000017c ); PROVIDE( spi_flash_unlock = 0x40000180 ); PROVIDE( spi_flash_erase_range = 0x40000184 ); PROVIDE( spi_flash_erase_sector = 0x40000188 ); PROVIDE( spi_flash_write = 0x4000018c ); PROVIDE( spi_flash_read = 0x40000190 ); PROVIDE( spi_flash_write_encrypted = 0x40000194 ); PROVIDE( spi_flash_read_encrypted = 0x40000198 ); PROVIDE( spi_flash_mmap_os_func_set = 0x4000019c ); PROVIDE( spi_flash_mmap_page_num_init = 0x400001a0 ); PROVIDE( spi_flash_mmap = 0x400001a4 ); PROVIDE( spi_flash_mmap_pages = 0x400001a8 ); PROVIDE( spi_flash_munmap = 0x400001ac ); PROVIDE( spi_flash_mmap_dump = 0x400001b0 ); PROVIDE( spi_flash_check_and_flush_cache = 0x400001b4 ); PROVIDE( spi_flash_mmap_get_free_pages = 0x400001b8 ); PROVIDE( spi_flash_cache2phys = 0x400001bc ); PROVIDE( spi_flash_phys2cache = 0x400001c0 ); PROVIDE( spi_flash_disable_cache = 0x400001c4 ); PROVIDE( spi_flash_restore_cache = 0x400001c8 ); PROVIDE( spi_flash_cache_enabled = 0x400001cc ); PROVIDE( spi_flash_enable_cache = 0x400001d0 ); PROVIDE( spi_cache_mode_switch = 0x400001d4 ); PROVIDE( spi_common_set_dummy_output = 0x400001d8 ); PROVIDE( spi_common_set_flash_cs_timing = 0x400001dc ); PROVIDE( esp_enable_cache_flash_wrap = 0x400001e0 ); PROVIDE( SPIEraseArea = 0x400001e4 ); PROVIDE( SPILock = 0x400001e8 ); PROVIDE( SPIMasterReadModeCnfig = 0x400001ec ); PROVIDE( SPI_Common_Command = 0x400001f0 ); PROVIDE( SPI_WakeUp = 0x400001f4 ); PROVIDE( SPI_block_erase = 0x400001f8 ); PROVIDE( SPI_chip_erase = 0x400001fc ); PROVIDE( SPI_init = 0x40000200 ); PROVIDE( SPI_page_program = 0x40000204 ); PROVIDE( SPI_read_data = 0x40000208 ); PROVIDE( SPI_sector_erase = 0x4000020c ); PROVIDE( SPI_write_enable = 0x40000210 ); PROVIDE( SelectSpiFunction = 0x40000214 ); PROVIDE( SetSpiDrvs = 0x40000218 ); PROVIDE( Wait_SPI_Idle = 0x4000021c ); PROVIDE( spi_dummy_len_fix = 0x40000220 ); PROVIDE( Disable_QMode = 0x40000224 ); PROVIDE( Enable_QMode = 0x40000228 ); /* Data (.data, .bss, .rodata) */ PROVIDE( rom_spiflash_legacy_funcs = 0x3fcdfff4 ); PROVIDE( rom_spiflash_legacy_data = 0x3fcdfff0 ); PROVIDE( g_flash_guard_ops = 0x3fcdfff8 ); /*************************************** Group spi_flash_hal ***************************************/ /* Functions */ PROVIDE( spi_flash_hal_poll_cmd_done = 0x4000022c ); PROVIDE( spi_flash_hal_device_config = 0x40000230 ); PROVIDE( spi_flash_hal_configure_host_io_mode = 0x40000234 ); PROVIDE( spi_flash_hal_common_command = 0x40000238 ); PROVIDE( spi_flash_hal_read = 0x4000023c ); PROVIDE( spi_flash_hal_erase_chip = 0x40000240 ); PROVIDE( spi_flash_hal_erase_sector = 0x40000244 ); PROVIDE( spi_flash_hal_erase_block = 0x40000248 ); PROVIDE( spi_flash_hal_program_page = 0x4000024c ); PROVIDE( spi_flash_hal_set_write_protect = 0x40000250 ); PROVIDE( spi_flash_hal_host_idle = 0x40000254 ); /*************************************** Group spi_flash_chips ***************************************/ /* Functions */ PROVIDE( spi_flash_chip_generic_probe = 0x40000258 ); PROVIDE( spi_flash_chip_generic_detect_size = 0x4000025c ); PROVIDE( spi_flash_chip_generic_write = 0x40000260 ); PROVIDE( spi_flash_chip_generic_write_encrypted = 0x40000264 ); PROVIDE( spi_flash_chip_generic_set_write_protect = 0x40000268 ); PROVIDE( spi_flash_common_write_status_16b_wrsr = 0x4000026c ); PROVIDE( spi_flash_chip_generic_reset = 0x40000270 ); PROVIDE( spi_flash_chip_generic_erase_chip = 0x40000274 ); PROVIDE( spi_flash_chip_generic_erase_sector = 0x40000278 ); PROVIDE( spi_flash_chip_generic_erase_block = 0x4000027c ); PROVIDE( spi_flash_chip_generic_page_program = 0x40000280 ); PROVIDE( spi_flash_chip_generic_get_write_protect = 0x40000284 ); PROVIDE( spi_flash_common_read_status_16b_rdsr_rdsr2 = 0x40000288 ); PROVIDE( spi_flash_chip_generic_read_reg = 0x4000028c ); PROVIDE( spi_flash_chip_generic_yield = 0x40000290 ); PROVIDE( spi_flash_generic_wait_host_idle = 0x40000294 ); PROVIDE( spi_flash_chip_generic_wait_idle = 0x40000298 ); PROVIDE( spi_flash_chip_generic_config_host_io_mode = 0x4000029c ); PROVIDE( spi_flash_chip_generic_read = 0x400002a0 ); PROVIDE( spi_flash_common_read_status_8b_rdsr2 = 0x400002a4 ); PROVIDE( spi_flash_chip_generic_get_io_mode = 0x400002a8 ); PROVIDE( spi_flash_common_read_status_8b_rdsr = 0x400002ac ); PROVIDE( spi_flash_common_write_status_8b_wrsr = 0x400002b0 ); PROVIDE( spi_flash_common_write_status_8b_wrsr2 = 0x400002b4 ); PROVIDE( spi_flash_common_set_io_mode = 0x400002b8 ); PROVIDE( spi_flash_chip_generic_set_io_mode = 0x400002bc ); PROVIDE( spi_flash_chip_gd_get_io_mode = 0x400002c0 ); PROVIDE( spi_flash_chip_gd_probe = 0x400002c4 ); PROVIDE( spi_flash_chip_gd_set_io_mode = 0x400002c8 ); /* Data (.data, .bss, .rodata) */ PROVIDE( spi_flash_chip_generic_config_data = 0x3fcdffec ); /*************************************** Group memspi_host ***************************************/ /* Functions */ PROVIDE( memspi_host_read_id_hs = 0x400002cc ); PROVIDE( memspi_host_read_status_hs = 0x400002d0 ); PROVIDE( memspi_host_flush_cache = 0x400002d4 ); PROVIDE( memspi_host_erase_chip = 0x400002d8 ); PROVIDE( memspi_host_erase_sector = 0x400002dc ); PROVIDE( memspi_host_erase_block = 0x400002e0 ); PROVIDE( memspi_host_program_page = 0x400002e4 ); PROVIDE( memspi_host_read = 0x400002e8 ); PROVIDE( memspi_host_set_write_protect = 0x400002ec ); PROVIDE( memspi_host_set_max_read_len = 0x400002f0 ); PROVIDE( memspi_host_read_data_slicer = 0x400002f4 ); PROVIDE( memspi_host_write_data_slicer = 0x400002f8 ); /*************************************** Group esp_flash ***************************************/ /* Functions */ PROVIDE( esp_flash_chip_driver_initialized = 0x400002fc ); PROVIDE( esp_flash_read_id = 0x40000300 ); PROVIDE( esp_flash_get_size = 0x40000304 ); PROVIDE( esp_flash_erase_chip = 0x40000308 ); PROVIDE( rom_esp_flash_erase_region = 0x4000030c ); PROVIDE( esp_flash_get_chip_write_protect = 0x40000310 ); PROVIDE( esp_flash_set_chip_write_protect = 0x40000314 ); PROVIDE( esp_flash_get_protectable_regions = 0x40000318 ); PROVIDE( esp_flash_get_protected_region = 0x4000031c ); PROVIDE( esp_flash_set_protected_region = 0x40000320 ); PROVIDE( esp_flash_read = 0x40000324 ); PROVIDE( esp_flash_write = 0x40000328 ); PROVIDE( esp_flash_write_encrypted = 0x4000032c ); PROVIDE( esp_flash_read_encrypted = 0x40000330 ); PROVIDE( esp_flash_get_io_mode = 0x40000334 ); PROVIDE( esp_flash_set_io_mode = 0x40000338 ); PROVIDE( spi_flash_boot_attach = 0x4000033c ); PROVIDE( spi_flash_dump_counters = 0x40000340 ); PROVIDE( spi_flash_get_counters = 0x40000344 ); PROVIDE( spi_flash_op_counters_config = 0x40000348 ); PROVIDE( spi_flash_reset_counters = 0x4000034c ); /* Data (.data, .bss, .rodata) */ PROVIDE( esp_flash_default_chip = 0x3fcdffe8 ); PROVIDE( esp_flash_api_funcs = 0x3fcdffe4 ); /*************************************** Group cache ***************************************/ /* Functions */ PROVIDE( Cache_Get_ICache_Line_Size = 0x400004b0 ); PROVIDE( Cache_Get_Mode = 0x400004b4 ); PROVIDE( Cache_Address_Through_IBus = 0x400004b8 ); PROVIDE( Cache_Address_Through_DBus = 0x400004bc ); PROVIDE( Cache_Set_Default_Mode = 0x400004c0 ); PROVIDE( Cache_Enable_Defalut_ICache_Mode = 0x400004c4 ); PROVIDE( ROM_Boot_Cache_Init = 0x400004c8 ); PROVIDE( Cache_Invalidate_ICache_Items = 0x400004cc ); PROVIDE( Cache_Op_Addr = 0x400004d0 ); PROVIDE( Cache_Invalidate_Addr = 0x400004d4 ); PROVIDE( Cache_Invalidate_ICache_All = 0x400004d8 ); PROVIDE( Cache_Mask_All = 0x400004dc ); PROVIDE( Cache_UnMask_Dram0 = 0x400004e0 ); PROVIDE( Cache_Suspend_ICache_Autoload = 0x400004e4 ); PROVIDE( Cache_Resume_ICache_Autoload = 0x400004e8 ); PROVIDE( Cache_Start_ICache_Preload = 0x400004ec ); PROVIDE( Cache_ICache_Preload_Done = 0x400004f0 ); PROVIDE( Cache_End_ICache_Preload = 0x400004f4 ); PROVIDE( Cache_Config_ICache_Autoload = 0x400004f8 ); PROVIDE( Cache_Enable_ICache_Autoload = 0x400004fc ); PROVIDE( Cache_Disable_ICache_Autoload = 0x40000500 ); PROVIDE( Cache_Enable_ICache_PreLock = 0x40000504 ); PROVIDE( Cache_Disable_ICache_PreLock = 0x40000508 ); PROVIDE( Cache_Lock_ICache_Items = 0x4000050c ); PROVIDE( Cache_Unlock_ICache_Items = 0x40000510 ); PROVIDE( Cache_Lock_Addr = 0x40000514 ); PROVIDE( Cache_Unlock_Addr = 0x40000518 ); PROVIDE( Cache_Disable_ICache = 0x4000051c ); PROVIDE( Cache_Enable_ICache = 0x40000520 ); PROVIDE( Cache_Suspend_ICache = 0x40000524 ); PROVIDE( Cache_Resume_ICache = 0x40000528 ); PROVIDE( Cache_Freeze_ICache_Enable = 0x4000052c ); PROVIDE( Cache_Freeze_ICache_Disable = 0x40000530 ); PROVIDE( Cache_Pms_Lock = 0x40000534 ); PROVIDE( Cache_Ibus_Pms_Set_Addr = 0x40000538 ); PROVIDE( Cache_Ibus_Pms_Set_Attr = 0x4000053c ); PROVIDE( Cache_Dbus_Pms_Set_Addr = 0x40000540 ); PROVIDE( Cache_Dbus_Pms_Set_Attr = 0x40000544 ); PROVIDE( Cache_Set_IDROM_MMU_Size = 0x40000548 ); PROVIDE( Cache_Get_IROM_MMU_End = 0x4000054c ); PROVIDE( Cache_Get_DROM_MMU_End = 0x40000550 ); PROVIDE( Cache_Owner_Init = 0x40000554 ); PROVIDE( Cache_Occupy_ICache_MEMORY = 0x40000558 ); PROVIDE( Cache_MMU_Init = 0x4000055c ); PROVIDE( Cache_Ibus_MMU_Set = 0x40000560 ); PROVIDE( Cache_Dbus_MMU_Set = 0x40000564 ); PROVIDE( Cache_Count_Flash_Pages = 0x40000568 ); PROVIDE( Cache_Travel_Tag_Memory = 0x4000056c ); PROVIDE( Cache_Get_Virtual_Addr = 0x40000570 ); PROVIDE( Cache_Get_Memory_BaseAddr = 0x40000574 ); PROVIDE( Cache_Get_Memory_Addr = 0x40000578 ); PROVIDE( Cache_Get_Memory_value = 0x4000057c ); /* Data (.data, .bss, .rodata) */ PROVIDE( rom_cache_op_cb = 0x3fcdffd8 ); PROVIDE( rom_cache_internal_table_ptr = 0x3fcdffd4 ); /*************************************** Group clock ***************************************/ /* Functions */ ets_get_apb_freq = 0x40000580; ets_get_cpu_frequency = 0x40000584; ets_update_cpu_frequency = 0x40000588; ets_get_printf_channel = 0x4000058c; ets_get_xtal_div = 0x40000590; ets_set_xtal_div = 0x40000594; ets_get_xtal_freq = 0x40000598; /*************************************** Group gpio ***************************************/ /* Functions */ gpio_input_get = 0x4000059c; gpio_matrix_in = 0x400005a0; gpio_matrix_out = 0x400005a4; gpio_output_disable = 0x400005a8; gpio_output_enable = 0x400005ac; gpio_output_set = 0x400005b0; gpio_pad_hold = 0x400005b4; gpio_pad_input_disable = 0x400005b8; gpio_pad_input_enable = 0x400005bc; gpio_pad_pulldown = 0x400005c0; gpio_pad_pullup = 0x400005c4; gpio_pad_select_gpio = 0x400005c8; gpio_pad_set_drv = 0x400005cc; gpio_pad_unhold = 0x400005d0; gpio_pin_wakeup_disable = 0x400005d4; gpio_pin_wakeup_enable = 0x400005d8; gpio_bypass_matrix_in = 0x400005dc; /*************************************** Group interrupts ***************************************/ /* Functions */ esprv_intc_int_set_priority = 0x400005e0; esprv_intc_int_set_threshold = 0x400005e4; esprv_intc_int_enable = 0x400005e8; esprv_intc_int_disable = 0x400005ec; esprv_intc_int_set_type = 0x400005f0; intr_matrix_set = 0x400005f4; ets_intr_lock = 0x400005f8; ets_intr_unlock = 0x400005fc; PROVIDE( intr_handler_set = 0x40000600 ); ets_isr_attach = 0x40000604; ets_isr_mask = 0x40000608; ets_isr_unmask = 0x4000060c; /*************************************** Group crypto ***************************************/ /* Functions */ md5_vector = 0x40000610; MD5Init = 0x40000614; MD5Update = 0x40000618; MD5Final = 0x4000061c; hmac_md5_vector = 0x40000620; hmac_md5 = 0x40000624; crc32_le = 0x40000628; crc32_be = 0x4000062c; crc16_le = 0x40000630; crc16_be = 0x40000634; crc8_le = 0x40000638; crc8_be = 0x4000063c; esp_crc8 = 0x40000640; ets_sha_enable = 0x40000644; ets_sha_disable = 0x40000648; ets_sha_get_state = 0x4000064c; ets_sha_init = 0x40000650; ets_sha_process = 0x40000654; ets_sha_starts = 0x40000658; ets_sha_update = 0x4000065c; ets_sha_finish = 0x40000660; ets_sha_clone = 0x40000664; ets_hmac_enable = 0x40000668; ets_hmac_disable = 0x4000066c; ets_hmac_calculate_message = 0x40000670; ets_hmac_calculate_downstream = 0x40000674; ets_hmac_invalidate_downstream = 0x40000678; ets_jtag_enable_temporarily = 0x4000067c; ets_aes_enable = 0x40000680; ets_aes_disable = 0x40000684; ets_aes_setkey = 0x40000688; ets_aes_block = 0x4000068c; ets_bigint_enable = 0x40000690; ets_bigint_disable = 0x40000694; ets_bigint_multiply = 0x40000698; ets_bigint_modmult = 0x4000069c; ets_bigint_modexp = 0x400006a0; ets_bigint_wait_finish = 0x400006a4; ets_bigint_getz = 0x400006a8; ets_ds_enable = 0x400006ac; ets_ds_disable = 0x400006b0; ets_ds_start_sign = 0x400006b4; ets_ds_is_busy = 0x400006b8; ets_ds_finish_sign = 0x400006bc; ets_ds_encrypt_params = 0x400006c0; ets_aes_setkey_dec = 0x400006c4; ets_aes_setkey_enc = 0x400006c8; ets_mgf1_sha256 = 0x400006cc; /*************************************** Group efuse ***************************************/ /* Functions */ ets_efuse_read = 0x400006d0; ets_efuse_program = 0x400006d4; ets_efuse_clear_program_registers = 0x400006d8; ets_efuse_write_key = 0x400006dc; ets_efuse_get_read_register_address = 0x400006e0; ets_efuse_get_key_purpose = 0x400006e4; ets_efuse_key_block_unused = 0x400006e8; ets_efuse_find_unused_key_block = 0x400006ec; ets_efuse_rs_calculate = 0x400006f0; ets_efuse_count_unused_key_blocks = 0x400006f4; ets_efuse_secure_boot_enabled = 0x400006f8; ets_efuse_secure_boot_aggressive_revoke_enabled = 0x400006fc; ets_efuse_cache_encryption_enabled = 0x40000700; ets_efuse_download_modes_disabled = 0x40000704; ets_efuse_find_purpose = 0x40000708; ets_efuse_flash_opi_5pads_power_sel_vddspi = 0x4000070c; ets_efuse_force_send_resume = 0x40000710; ets_efuse_get_flash_delay_us = 0x40000714; ets_efuse_get_mac = 0x40000718; ets_efuse_get_spiconfig = 0x4000071c; ets_efuse_usb_print_is_disabled = 0x40000720; /*ets_efuse_get_uart_print_channel = 0x40000724;*/ ets_efuse_usb_serial_jtag_print_is_disabled = 0x40000724; ets_efuse_get_uart_print_control = 0x40000728; ets_efuse_get_wp_pad = 0x4000072c; ets_efuse_legacy_spi_boot_mode_disabled = 0x40000730; ets_efuse_security_download_modes_enabled = 0x40000734; ets_efuse_set_timing = 0x40000738; ets_efuse_jtag_disabled = 0x4000073c; ets_efuse_usb_download_mode_disabled = 0x40000740; ets_efuse_usb_module_disabled = 0x40000744; ets_efuse_usb_device_disabled = 0x40000748; /*************************************** Group secureboot ***************************************/ /* Functions */ ets_emsa_pss_verify = 0x4000074c; ets_rsa_pss_verify = 0x40000750; ets_secure_boot_verify_bootloader_with_keys = 0x40000754; ets_secure_boot_verify_signature = 0x40000758; ets_secure_boot_read_key_digests = 0x4000075c; ets_secure_boot_revoke_public_key_digest = 0x40000760; /*************************************** Group usb_uart ***************************************/ /* Functions */ PROVIDE( usb_uart_rx_one_char = 0x400008cc ); PROVIDE( usb_uart_rx_one_char_block = 0x400008d0 ); PROVIDE( usb_uart_tx_flush = 0x400008d4 ); PROVIDE( usb_uart_tx_one_char = 0x400008d8 ); /* Data (.data, .bss, .rodata) */ PROVIDE( g_uart_print = 0x3fcdffd1 ); PROVIDE( g_usb_print = 0x3fcdffd0 ); /*************************************** Group bluetooth ***************************************/ /* Functions */ bt_rf_coex_get_dft_cfg = 0x400008dc; bt_rf_coex_hooks_p_set = 0x400008e0; btdm_con_maxevtime_cal_impl = 0x400008e4; btdm_controller_get_compile_version_impl = 0x400008e8; btdm_controller_rom_data_init = 0x400008ec; btdm_dis_privacy_err_report_impl = 0x400008f0; btdm_disable_adv_delay_impl = 0x400008f4; btdm_enable_scan_continue_impl = 0x400008f8; btdm_enable_scan_forever_impl = 0x400008fc; btdm_get_power_state_impl = 0x40000900; btdm_get_prevent_sleep_flag_impl = 0x40000904; btdm_power_state_active_impl = 0x40000908; btdm_switch_phy_coded_impl = 0x4000090c; hci_acl_data_handler = 0x40000910; hci_disconnect_cmd_handler = 0x40000914; hci_le_con_upd_cmd_handler = 0x40000918; hci_le_ltk_req_neg_reply_cmd_handler = 0x4000091c; hci_le_ltk_req_reply_cmd_handler = 0x40000920; hci_le_rd_chnl_map_cmd_handler = 0x40000924; hci_le_rd_phy_cmd_handler = 0x40000928; hci_le_rd_rem_feats_cmd_handler = 0x4000092c; hci_le_rem_con_param_req_neg_reply_cmd_handler = 0x40000930; hci_le_rem_con_param_req_reply_cmd_handler = 0x40000934; hci_le_set_data_len_cmd_handler = 0x40000938; hci_le_set_phy_cmd_handler = 0x4000093c; hci_le_start_enc_cmd_handler = 0x40000940; hci_rd_auth_payl_to_cmd_handler = 0x40000944; hci_rd_rem_ver_info_cmd_handler = 0x40000948; hci_rd_rssi_cmd_handler = 0x4000094c; hci_rd_tx_pwr_lvl_cmd_handler = 0x40000950; hci_vs_set_pref_slave_evt_dur_cmd_handler = 0x40000954; hci_vs_set_pref_slave_latency_cmd_handler = 0x40000958; hci_wr_auth_payl_to_cmd_handler = 0x4000095c; ll_channel_map_ind_handler = 0x40000960; ll_connection_param_req_handler = 0x40000964; ll_connection_param_rsp_handler = 0x40000968; ll_connection_update_ind_handler = 0x4000096c; ll_enc_req_handler = 0x40000970; ll_enc_rsp_handler = 0x40000974; ll_feature_req_handler = 0x40000978; ll_feature_rsp_handler = 0x4000097c; ll_length_req_handler = 0x40000980; ll_length_rsp_handler = 0x40000984; ll_min_used_channels_ind_handler = 0x40000988; ll_pause_enc_req_handler = 0x4000098c; ll_pause_enc_rsp_handler = 0x40000990; ll_phy_req_handler = 0x40000994; ll_phy_rsp_handler = 0x40000998; ll_phy_update_ind_handler = 0x4000099c; ll_ping_req_handler = 0x400009a0; ll_ping_rsp_handler = 0x400009a4; ll_slave_feature_req_handler = 0x400009a8; ll_start_enc_req_handler = 0x400009ac; ll_start_enc_rsp_handler = 0x400009b0; ll_terminate_ind_handler = 0x400009b4; ll_version_ind_handler = 0x400009b8; llc_auth_payl_nearly_to_handler = 0x400009bc; llc_auth_payl_real_to_handler = 0x400009c0; llc_encrypt_ind_handler = 0x400009c4; llc_hci_command_handler_wrapper = 0x400009c8; llc_ll_connection_param_req_pdu_send = 0x400009cc; llc_ll_connection_param_rsp_pdu_send = 0x400009d0; llc_ll_connection_update_ind_pdu_send = 0x400009d4; llc_ll_enc_req_pdu_send = 0x400009d8; llc_ll_enc_rsp_pdu_send = 0x400009dc; llc_ll_feature_req_pdu_send = 0x400009e0; llc_ll_feature_rsp_pdu_send = 0x400009e4; llc_ll_length_req_pdu_send = 0x400009e8; llc_ll_length_rsp_pdu_send = 0x400009ec; llc_ll_pause_enc_req_pdu_send = 0x400009f0; llc_ll_pause_enc_rsp_pdu_send = 0x400009f4; llc_ll_phy_req_pdu_send = 0x400009f8; llc_ll_phy_rsp_pdu_send = 0x400009fc; llc_ll_ping_req_pdu_send = 0x40000a00; llc_ll_ping_rsp_pdu_send = 0x40000a04; llc_ll_start_enc_req_pdu_send = 0x40000a08; llc_ll_start_enc_rsp_pdu_send = 0x40000a0c; llc_ll_terminate_ind_pdu_send = 0x40000a10; llc_ll_unknown_rsp_pdu_send = 0x40000a14; llc_llcp_ch_map_update_ind_pdu_send = 0x40000a18; llc_llcp_phy_upd_ind_pdu_send = 0x40000a1c; llc_llcp_version_ind_pdu_send = 0x40000a20; llc_op_ch_map_upd_ind_handler = 0x40000a24; llc_op_con_upd_ind_handler = 0x40000a28; llc_op_disconnect_ind_handler = 0x40000a2c; llc_op_dl_upd_ind_handler = 0x40000a30; llc_op_encrypt_ind_handler = 0x40000a34; llc_op_feats_exch_ind_handler = 0x40000a38; llc_op_le_ping_ind_handler = 0x40000a3c; llc_op_phy_upd_ind_handler = 0x40000a40; llc_op_ver_exch_ind_handler = 0x40000a44; llc_stopped_ind_handler = 0x40000a48; lld_acl_rx_ind_handler = 0x40000a4c; lld_acl_tx_cfm_handler = 0x40000a50; lld_adv_end_ind_handler = 0x40000a54; lld_adv_rep_ind_handler = 0x40000a58; lld_ch_map_upd_cfm_handler = 0x40000a5c; lld_con_estab_ind_handler = 0x40000a60; lld_con_evt_sd_evt_time_set = 0x40000a64; lld_con_offset_upd_ind_handler = 0x40000a68; lld_con_param_upd_cfm_handler = 0x40000a6c; lld_disc_ind_handler = 0x40000a70; lld_init_end_ind_handler = 0x40000a74; lld_llcp_rx_ind_handler_wrapper = 0x40000a78; lld_llcp_tx_cfm_handler = 0x40000a7c; lld_per_adv_end_ind_handler = 0x40000a80; lld_per_adv_rep_ind_handler = 0x40000a84; lld_per_adv_rx_end_ind_handler = 0x40000a88; lld_phy_coded_500k_get = 0x40000a8c; lld_phy_upd_cfm_handler = 0x40000a90; lld_scan_end_ind_handler = 0x40000a94; lld_scan_req_ind_handler = 0x40000a98; lld_sync_start_req_handler = 0x40000a9c; lld_test_end_ind_handler = 0x40000aa0; lld_update_rxbuf_handler = 0x40000aa4; llm_ch_map_update_ind_handler = 0x40000aa8; llm_hci_command_handler_wrapper = 0x40000aac; llm_scan_period_to_handler = 0x40000ab0; r_Add2SelfBigHex256 = 0x40000ab4; r_AddBigHex256 = 0x40000ab8; r_AddBigHexModP256 = 0x40000abc; r_AddP256 = 0x40000ac0; r_AddPdiv2_256 = 0x40000ac4; r_GF_Jacobian_Point_Addition256 = 0x40000ac8; r_GF_Jacobian_Point_Double256 = 0x40000acc; r_GF_Point_Jacobian_To_Affine256 = 0x40000ad0; r_MultiplyBigHexByUint32_256 = 0x40000ad4; r_MultiplyBigHexModP256 = 0x40000ad8; r_MultiplyByU16ModP256 = 0x40000adc; r_SubtractBigHex256 = 0x40000ae0; r_SubtractBigHexMod256 = 0x40000ae4; r_SubtractBigHexUint32_256 = 0x40000ae8; r_SubtractFromSelfBigHex256 = 0x40000aec; r_SubtractFromSelfBigHexSign256 = 0x40000af0; r_aes_alloc = 0x40000af4; r_aes_ccm_continue = 0x40000af8; r_aes_ccm_process_e = 0x40000afc; r_aes_ccm_xor_128_lsb = 0x40000b00; r_aes_ccm_xor_128_msb = 0x40000b04; r_aes_cmac_continue = 0x40000b08; r_aes_cmac_start = 0x40000b0c; r_aes_k1_continue = 0x40000b10; r_aes_k2_continue = 0x40000b14; r_aes_k3_continue = 0x40000b18; r_aes_k4_continue = 0x40000b1c; r_aes_shift_left_128 = 0x40000b20; r_aes_start = 0x40000b24; r_aes_xor_128 = 0x40000b28; r_assert_err = 0x40000b2c; r_assert_param = 0x40000b30; r_assert_warn = 0x40000b34; r_bigHexInversion256 = 0x40000b38; r_ble_sw_cca_check_isr = 0x40000b3c; r_ble_util_buf_acl_tx_alloc = 0x40000b40; r_ble_util_buf_acl_tx_elt_get = 0x40000b44; r_ble_util_buf_acl_tx_free = 0x40000b48; r_ble_util_buf_acl_tx_free_in_isr = 0x40000b4c; r_ble_util_buf_adv_tx_alloc = 0x40000b50; r_ble_util_buf_adv_tx_free = 0x40000b54; r_ble_util_buf_adv_tx_free_in_isr = 0x40000b58; r_ble_util_buf_env_deinit = 0x40000b5c; r_ble_util_buf_env_init = 0x40000b60; r_ble_util_buf_get_rx_buf_nb = 0x40000b64; r_ble_util_buf_get_rx_buf_size = 0x40000b68; r_ble_util_buf_llcp_tx_alloc = 0x40000b6c; r_ble_util_buf_llcp_tx_free = 0x40000b70; r_ble_util_buf_rx_alloc = 0x40000b74; r_ble_util_buf_rx_alloc_in_isr = 0x40000b78; r_ble_util_buf_rx_free = 0x40000b7c; r_ble_util_buf_rx_free_in_isr = 0x40000b80; r_ble_util_buf_set_rx_buf_nb = 0x40000b84; r_ble_util_buf_set_rx_buf_size = 0x40000b88; r_ble_util_data_rx_buf_reset = 0x40000b8c; r_bt_bb_get_intr_mask = 0x40000b90; r_bt_bb_intr_clear = 0x40000b94; r_bt_bb_intr_mask_set = 0x40000b98; r_bt_rf_coex_cfg_set = 0x40000ba0; r_bt_rf_coex_conn_dynamic_pti_en_get = 0x40000ba4; r_bt_rf_coex_ext_adv_dynamic_pti_en_get = 0x40000bac; r_bt_rf_coex_ext_scan_dynamic_pti_en_get = 0x40000bb0; r_bt_rf_coex_legacy_adv_dynamic_pti_en_get = 0x40000bb4; r_bt_rf_coex_per_adv_dynamic_pti_en_get = 0x40000bb8; r_bt_rf_coex_pti_table_get = 0x40000bbc; r_bt_rf_coex_st_param_get = 0x40000bc0; r_bt_rf_coex_st_param_set = 0x40000bc4; r_bt_rf_coex_sync_scan_dynamic_pti_en_get = 0x40000bc8; r_bt_rma_apply_rule_cs_fmt = 0x40000bcc; r_bt_rma_apply_rule_cs_idx = 0x40000bd0; r_bt_rma_configure = 0x40000bd4; r_bt_rma_deregister_rule_cs_fmt = 0x40000bd8; r_bt_rma_deregister_rule_cs_idx = 0x40000bdc; r_bt_rma_get_ant_by_act = 0x40000be0; r_bt_rma_init = 0x40000be4; r_bt_rma_register_rule_cs_fmt = 0x40000be8; r_bt_rma_register_rule_cs_idx = 0x40000bec; r_bt_rtp_apply_rule_cs_fmt = 0x40000bf0; r_bt_rtp_apply_rule_cs_idx = 0x40000bf4; r_bt_rtp_deregister_rule_cs_fmt = 0x40000bf8; r_bt_rtp_deregister_rule_cs_idx = 0x40000bfc; r_bt_rtp_init = 0x40000c04; r_bt_rtp_register_rule_cs_fmt = 0x40000c08; r_bt_rtp_register_rule_cs_idx = 0x40000c0c; r_btdm_isr = 0x40000c10; r_cali_phase_match_p = 0x40000c20; r_cmp_abs_time = 0x40000c24; r_cmp_dest_id = 0x40000c28; r_cmp_timer_id = 0x40000c2c; r_co_bdaddr_compare = 0x40000c30; r_co_ble_pkt_dur_in_us = 0x40000c34; r_co_list_extract = 0x40000c38; r_co_list_extract_after = 0x40000c3c; r_co_list_extract_sublist = 0x40000c40; r_co_list_find = 0x40000c44; r_co_list_init = 0x40000c48; r_co_list_insert_after = 0x40000c4c; r_co_list_insert_before = 0x40000c50; r_co_list_merge = 0x40000c54; r_co_list_pool_init = 0x40000c58; r_co_list_pop_front = 0x40000c5c; r_co_list_push_back = 0x40000c60; r_co_list_push_back_sublist = 0x40000c64; r_co_list_push_front = 0x40000c68; r_co_list_size = 0x40000c6c; r_co_nb_good_le_channels = 0x40000c70; r_co_util_pack = 0x40000c74; r_co_util_read_array_size = 0x40000c78; r_co_util_unpack = 0x40000c7c; r_dbg_env_deinit = 0x40000c80; r_dbg_env_init = 0x40000c84; r_dbg_platform_reset_complete = 0x40000c88; r_dl_upd_proc_start = 0x40000c8c; r_dump_data = 0x40000c90; r_ecc_abort_key256_generation = 0x40000c94; r_ecc_gen_new_public_key = 0x40000c98; r_ecc_gen_new_secret_key = 0x40000c9c; r_ecc_generate_key256 = 0x40000ca0; r_ecc_get_debug_Keys = 0x40000ca4; r_ecc_init = 0x40000ca8; r_ecc_is_valid_point = 0x40000cac; r_ecc_multiplication_event_handler = 0x40000cb0; r_ecc_point_multiplication_win_256 = 0x40000cb4; r_emi_alloc_em_mapping_by_offset = 0x40000cb8; r_emi_base_reg_lut_show = 0x40000cbc; r_emi_em_base_reg_show = 0x40000cc0; r_emi_free_em_mapping_by_offset = 0x40000cc4; r_emi_get_em_mapping_idx_by_offset = 0x40000cc8; r_emi_get_mem_addr_by_offset = 0x40000ccc; r_emi_overwrite_em_mapping_by_offset = 0x40000cd0; r_esp_vendor_hci_command_handler = 0x40000cd4; r_get_stack_usage = 0x40000cd8; r_h4tl_acl_hdr_rx_evt_handler = 0x40000cdc; r_h4tl_cmd_hdr_rx_evt_handler = 0x40000ce0; r_h4tl_cmd_pld_rx_evt_handler = 0x40000ce4; r_h4tl_eif_io_event_post = 0x40000ce8; r_h4tl_eif_register = 0x40000cec; r_h4tl_init = 0x40000cf0; r_h4tl_out_of_sync = 0x40000cf4; r_h4tl_out_of_sync_check = 0x40000cf8; r_h4tl_read_hdr = 0x40000cfc; r_h4tl_read_next_out_of_sync = 0x40000d00; r_h4tl_read_payl = 0x40000d04; r_h4tl_read_start = 0x40000d08; r_h4tl_rx_acl_hdr_extract = 0x40000d0c; r_h4tl_rx_cmd_hdr_extract = 0x40000d10; r_h4tl_rx_done = 0x40000d14; r_h4tl_start = 0x40000d18; r_h4tl_stop = 0x40000d1c; r_h4tl_tx_done = 0x40000d20; r_h4tl_tx_evt_handler = 0x40000d24; r_h4tl_write = 0x40000d28; r_hci_acl_tx_data_alloc = 0x40000d2c; r_hci_acl_tx_data_received = 0x40000d30; r_hci_basic_cmd_send_2_controller = 0x40000d34; r_hci_ble_adv_report_filter_check = 0x40000d38; r_hci_ble_adv_report_tx_check = 0x40000d3c; r_hci_ble_conhdl_register = 0x40000d40; r_hci_ble_conhdl_unregister = 0x40000d44; r_hci_build_acl_data = 0x40000d48; r_hci_build_cc_evt = 0x40000d4c; r_hci_build_cs_evt = 0x40000d50; r_hci_build_evt = 0x40000d54; r_hci_build_le_evt = 0x40000d58; r_hci_cmd_get_max_param_size = 0x40000d5c; r_hci_cmd_received = 0x40000d60; r_hci_cmd_reject = 0x40000d64; r_hci_evt_mask_check = 0x40000d68; r_hci_evt_mask_set = 0x40000d6c; r_hci_fc_acl_buf_size_set = 0x40000d70; r_hci_fc_acl_en = 0x40000d74; r_hci_fc_acl_packet_sent = 0x40000d78; r_hci_fc_check_host_available_nb_acl_packets = 0x40000d7c; r_hci_fc_host_nb_acl_pkts_complete = 0x40000d80; r_hci_fc_init = 0x40000d84; r_hci_look_for_cmd_desc = 0x40000d88; r_hci_look_for_evt_desc = 0x40000d8c; r_hci_look_for_le_evt_desc = 0x40000d90; r_hci_look_for_le_evt_desc_esp = 0x40000d94; r_hci_pack_bytes = 0x40000d98; r_hci_send_2_controller = 0x40000da0; r_hci_send_2_host = 0x40000da4; r_hci_tl_c2h_data_flow_on = 0x40000da8; r_hci_tl_cmd_hdr_rx_evt_handler = 0x40000dac; r_hci_tl_cmd_pld_rx_evt_handler = 0x40000db0; r_hci_tl_get_pkt = 0x40000db4; r_hci_tl_hci_pkt_handler = 0x40000db8; r_hci_tl_hci_tx_done_evt_handler = 0x40000dbc; r_hci_tl_inc_nb_h2c_cmd_pkts = 0x40000dc0; r_hci_tl_save_pkt = 0x40000dc4; r_hci_tl_send = 0x40000dc8; r_hci_tx_done = 0x40000dcc; r_hci_tx_start = 0x40000dd0; r_hci_tx_trigger = 0x40000dd4; r_isValidSecretKey_256 = 0x40000dd8; r_ke_check_malloc = 0x40000ddc; r_ke_event_callback_set = 0x40000de0; r_ke_event_clear = 0x40000de4; r_ke_event_flush = 0x40000de8; r_ke_event_get = 0x40000dec; r_ke_event_get_all = 0x40000df0; r_ke_event_init = 0x40000df4; r_ke_event_schedule = 0x40000df8; r_ke_event_set = 0x40000dfc; r_ke_flush = 0x40000e00; r_ke_free = 0x40000e04; r_ke_handler_search = 0x40000e08; r_ke_init = 0x40000e0c; r_ke_is_free = 0x40000e10; r_ke_malloc = 0x40000e14; r_ke_mem_init = 0x40000e18; r_ke_mem_is_empty = 0x40000e1c; r_ke_mem_is_in_heap = 0x40000e20; r_ke_msg_alloc = 0x40000e24; r_ke_msg_dest_id_get = 0x40000e28; r_ke_msg_discard = 0x40000e2c; r_ke_msg_forward = 0x40000e30; r_ke_msg_forward_new_id = 0x40000e34; r_ke_msg_free = 0x40000e38; r_ke_msg_in_queue = 0x40000e3c; r_ke_msg_save = 0x40000e40; r_ke_msg_send = 0x40000e44; r_ke_msg_send_basic = 0x40000e48; r_ke_msg_src_id_get = 0x40000e4c; r_ke_queue_extract = 0x40000e50; r_ke_queue_insert = 0x40000e54; r_ke_sleep_check = 0x40000e58; r_ke_state_get = 0x40000e5c; r_ke_state_set = 0x40000e60; r_ke_task_check = 0x40000e64; r_ke_task_create = 0x40000e68; r_ke_task_delete = 0x40000e6c; r_ke_task_handler_get = 0x40000e70; r_ke_task_init = 0x40000e74; r_ke_task_msg_flush = 0x40000e78; r_ke_task_saved_update = 0x40000e7c; r_ke_time = 0x40000e84; r_ke_time_cmp = 0x40000e88; r_ke_time_past = 0x40000e8c; r_ke_timer_active = 0x40000e90; r_ke_timer_adjust_all = 0x40000e94; r_ke_timer_clear = 0x40000e98; r_ke_timer_init = 0x40000e9c; r_ke_timer_schedule = 0x40000ea0; r_ke_timer_set = 0x40000ea4; r_led_init = 0x40000ea8; r_led_set_all = 0x40000eac; r_llc_aes_res_cb = 0x40000eb0; r_llc_ch_map_up_proc_err_cb = 0x40000eb4; r_llc_cleanup = 0x40000eb8; r_llc_cmd_cmp_send = 0x40000ebc; r_llc_cmd_stat_send = 0x40000ec0; r_llc_con_move_cbk = 0x40000ec4; r_llc_con_plan_set_update = 0x40000ec8; r_llc_con_upd_param_in_range = 0x40000ecc; r_llc_disconnect = 0x40000ed0; r_llc_disconnect_end = 0x40000ed4; r_llc_disconnect_proc_continue = 0x40000ed8; r_llc_disconnect_proc_err_cb = 0x40000edc; r_llc_dl_chg_check = 0x40000ee0; r_llc_dle_proc_err_cb = 0x40000ee4; r_llc_feats_exch_proc_err_cb = 0x40000ee8; r_llc_hci_cmd_handler_tab_p_get = 0x40000eec; r_llc_hci_con_param_req_evt_send = 0x40000ef4; r_llc_hci_con_upd_info_send = 0x40000ef8; r_llc_hci_disconnected_dis = 0x40000efc; r_llc_hci_dl_upd_info_send = 0x40000f00; r_llc_hci_enc_evt_send = 0x40000f04; r_llc_hci_feats_info_send = 0x40000f08; r_llc_hci_le_phy_upd_cmp_evt_send = 0x40000f0c; r_llc_hci_ltk_request_evt_send = 0x40000f10; r_llc_hci_nb_cmp_pkts_evt_send = 0x40000f14; r_llc_hci_version_info_send = 0x40000f18; r_llc_init_term_proc = 0x40000f1c; r_llc_iv_skd_rand_gen = 0x40000f20; r_llc_le_ping_proc_continue = 0x40000f24; r_llc_le_ping_proc_err_cb = 0x40000f28; r_llc_le_ping_restart = 0x40000f2c; r_llc_le_ping_set = 0x40000f30; r_llc_ll_pause_enc_rsp_ack_handler = 0x40000f34; r_llc_ll_reject_ind_ack_handler = 0x40000f38; r_llc_ll_reject_ind_pdu_send = 0x40000f3c; r_llc_ll_start_enc_rsp_ack_handler = 0x40000f40; r_llc_ll_terminate_ind_ack = 0x40000f44; r_llc_ll_unknown_ind_handler = 0x40000f48; r_llc_llcp_send = 0x40000f4c; r_llc_llcp_state_set = 0x40000f50; r_llc_llcp_trans_timer_set = 0x40000f54; r_llc_llcp_tx_check = 0x40000f58; r_llc_loc_ch_map_proc_continue = 0x40000f5c; r_llc_loc_con_upd_proc_err_cb = 0x40000f64; r_llc_loc_dl_upd_proc_continue = 0x40000f68; r_llc_loc_encrypt_proc_continue = 0x40000f6c; r_llc_loc_encrypt_proc_err_cb = 0x40000f70; r_llc_loc_feats_exch_proc_continue = 0x40000f74; r_llc_loc_phy_upd_proc_err_cb = 0x40000f7c; r_llc_msg_handler_tab_p_get = 0x40000f80; r_llc_pref_param_compute = 0x40000f84; r_llc_proc_collision_check = 0x40000f88; r_llc_proc_err_ind = 0x40000f8c; r_llc_proc_get = 0x40000f90; r_llc_proc_id_get = 0x40000f94; r_llc_proc_reg = 0x40000f98; r_llc_proc_state_get = 0x40000f9c; r_llc_proc_state_set = 0x40000fa0; r_llc_proc_timer_pause_set = 0x40000fa4; r_llc_proc_timer_set = 0x40000fa8; r_llc_proc_unreg = 0x40000fac; r_llc_rem_ch_map_proc_continue = 0x40000fb0; r_llc_rem_con_upd_proc_err_cb = 0x40000fb8; r_llc_rem_dl_upd_proc = 0x40000fbc; r_llc_rem_encrypt_proc_continue = 0x40000fc0; r_llc_rem_encrypt_proc_err_cb = 0x40000fc4; r_llc_rem_phy_upd_proc_continue = 0x40000fc8; r_llc_rem_phy_upd_proc_err_cb = 0x40000fcc; r_llc_role_get = 0x40000fd0; r_llc_sk_gen = 0x40000fd4; r_llc_start = 0x40000fd8; r_llc_stop = 0x40000fdc; r_llc_ver_exch_loc_proc_continue = 0x40000fe0; r_llc_ver_proc_err_cb = 0x40000fe4; r_llcp_pdu_handler_tab_p_get = 0x40000fe8; r_lld_aa_gen = 0x40000fec; r_lld_adv_adv_data_set = 0x40000ff0; r_lld_adv_adv_data_update = 0x40000ff4; r_lld_adv_aux_ch_idx_set = 0x40000ff8; r_lld_adv_aux_evt_canceled_cbk = 0x40000ffc; r_lld_adv_aux_evt_start_cbk = 0x40001000; r_lld_adv_coex_check_ext_adv_synced = 0x40001004; r_lld_adv_coex_env_reset = 0x40001008; r_lld_adv_duration_update = 0x4000100c; r_lld_adv_dynamic_pti_process = 0x40001010; r_lld_adv_end = 0x40001014; r_lld_adv_evt_canceled_cbk = 0x40001018; r_lld_adv_evt_start_cbk = 0x4000101c; r_lld_adv_ext_chain_construct = 0x40001020; r_lld_adv_ext_pkt_prepare = 0x40001024; r_lld_adv_frm_cbk = 0x40001028; r_lld_adv_frm_isr = 0x4000102c; r_lld_adv_frm_skip_isr = 0x40001030; r_lld_adv_init = 0x40001034; r_lld_adv_pkt_rx = 0x40001038; r_lld_adv_pkt_rx_connect_ind = 0x4000103c; r_lld_adv_pkt_rx_send_scan_req_evt = 0x40001040; r_lld_adv_rand_addr_update = 0x40001044; r_lld_adv_restart = 0x40001048; r_lld_adv_scan_rsp_data_set = 0x4000104c; r_lld_adv_scan_rsp_data_update = 0x40001050; r_lld_adv_set_tx_power = 0x40001054; r_lld_adv_start = 0x40001058; r_lld_adv_stop = 0x4000105c; r_lld_adv_sync_info_set = 0x40001060; r_lld_adv_sync_info_update = 0x40001064; r_lld_calc_aux_rx = 0x40001068; r_lld_cca_alloc = 0x4000106c; r_lld_cca_data_reset = 0x40001070; r_lld_cca_free = 0x40001074; r_lld_ch_assess_data_get = 0x40001078; r_lld_ch_idx_get = 0x4000107c; r_lld_ch_map_set = 0x40001080; r_lld_channel_assess = 0x40001084; r_lld_con_activity_act_offset_compute = 0x40001088; r_lld_con_activity_offset_compute = 0x4000108c; r_lld_con_ch_map_update = 0x40001090; r_lld_con_cleanup = 0x40001094; r_lld_con_current_tx_power_get = 0x40001098; r_lld_con_data_flow_set = 0x4000109c; r_lld_con_data_len_update = 0x400010a0; r_lld_con_data_tx = 0x400010a4; r_lld_con_enc_key_load = 0x400010a8; r_lld_con_event_counter_get = 0x400010ac; r_lld_con_evt_canceled_cbk = 0x400010b0; r_lld_con_evt_duration_min_get = 0x400010b4; r_lld_con_evt_max_eff_time_cal = 0x400010b8; r_lld_con_evt_sd_evt_time_get = 0x400010bc; r_lld_con_evt_start_cbk = 0x400010c0; r_lld_con_evt_time_update = 0x400010c4; r_lld_con_free_all_tx_buf = 0x400010c8; r_lld_con_frm_cbk = 0x400010cc; r_lld_con_frm_isr = 0x400010d0; r_lld_con_frm_skip_isr = 0x400010d4; r_lld_con_init = 0x400010d8; r_lld_con_llcp_tx = 0x400010dc; r_lld_con_max_lat_calc = 0x400010e0; r_lld_con_offset_get = 0x400010e4; r_lld_con_param_update = 0x400010e8; r_lld_con_phys_update = 0x400010ec; r_lld_con_pref_slave_evt_dur_set = 0x400010f0; r_lld_con_pref_slave_latency_set = 0x400010f4; r_lld_con_rssi_get = 0x400010f8; r_lld_con_rx = 0x400010fc; r_lld_con_rx_channel_assess = 0x40001100; r_lld_con_rx_enc = 0x40001104; r_lld_con_rx_isr = 0x40001108; r_lld_con_rx_link_info_check = 0x4000110c; r_lld_con_rx_llcp_check = 0x40001110; r_lld_con_rx_sync_time_update = 0x40001114; r_lld_con_set_tx_power = 0x4000111c; r_lld_con_start = 0x40001120; r_lld_con_tx = 0x40001128; r_lld_con_tx_enc = 0x4000112c; r_lld_con_tx_isr = 0x40001130; r_lld_con_tx_len_update = 0x40001134; r_lld_con_tx_len_update_for_intv = 0x40001138; r_lld_con_tx_len_update_for_rate = 0x4000113c; r_lld_con_tx_prog = 0x40001140; r_lld_conn_dynamic_pti_process = 0x40001144; r_lld_continue_scan_rx_isr_end_process = 0x40001148; r_lld_ext_scan_dynamic_pti_process = 0x4000114c; r_lld_hw_cca_end_isr = 0x40001150; r_lld_hw_cca_evt_handler = 0x40001154; r_lld_hw_cca_isr = 0x40001158; r_lld_init_cal_anchor_point = 0x4000115c; r_lld_init_compute_winoffset = 0x40001160; r_lld_init_connect_req_pack = 0x40001164; r_lld_init_end = 0x40001168; r_lld_init_evt_canceled_cbk = 0x4000116c; r_lld_init_evt_start_cbk = 0x40001170; r_lld_init_frm_cbk = 0x40001174; r_lld_init_frm_eof_isr = 0x40001178; r_lld_init_frm_skip_isr = 0x4000117c; r_lld_init_init = 0x40001180; r_lld_init_process_pkt_rx = 0x40001184; r_lld_init_process_pkt_rx_adv_ext_ind = 0x40001188; r_lld_init_process_pkt_rx_adv_ind_or_direct_ind = 0x4000118c; r_lld_init_process_pkt_rx_aux_connect_rsp = 0x40001190; r_lld_init_process_pkt_tx = 0x40001194; r_lld_init_process_pkt_tx_cal_con_timestamp = 0x40001198; r_lld_init_sched = 0x4000119c; r_lld_init_set_tx_power = 0x400011a0; r_lld_init_start = 0x400011a4; r_lld_init_stop = 0x400011a8; r_lld_instant_proc_end = 0x400011ac; r_lld_per_adv_ch_map_update = 0x400011b4; r_lld_per_adv_chain_construct = 0x400011b8; r_lld_per_adv_cleanup = 0x400011bc; r_lld_per_adv_coex_env_reset = 0x400011c0; r_lld_per_adv_data_set = 0x400011c4; r_lld_per_adv_data_update = 0x400011c8; r_lld_per_adv_dynamic_pti_process = 0x400011cc; r_lld_per_adv_evt_canceled_cbk = 0x400011d0; r_lld_per_adv_evt_start_cbk = 0x400011d4; r_lld_per_adv_ext_pkt_prepare = 0x400011d8; r_lld_per_adv_frm_cbk = 0x400011dc; r_lld_per_adv_frm_isr = 0x400011e0; r_lld_per_adv_frm_skip_isr = 0x400011e4; r_lld_per_adv_init = 0x400011e8; r_lld_per_adv_init_info_get = 0x400011ec; r_lld_per_adv_list_add = 0x400011f0; r_lld_per_adv_list_rem = 0x400011f4; r_lld_per_adv_set_tx_power = 0x400011fc; r_lld_per_adv_start = 0x40001200; r_lld_per_adv_stop = 0x40001204; r_lld_per_adv_sync_info_get = 0x40001208; r_lld_process_cca_data = 0x4000120c; r_lld_ral_search = 0x40001210; r_lld_read_clock = 0x40001214; r_lld_res_list_add = 0x40001218; r_lld_res_list_is_empty = 0x40001220; r_lld_res_list_local_rpa_get = 0x40001224; r_lld_res_list_peer_rpa_get = 0x40001228; r_lld_res_list_peer_update = 0x4000122c; r_lld_res_list_priv_mode_update = 0x40001230; r_lld_reset_reg = 0x40001238; r_lld_rpa_renew = 0x4000123c; r_lld_rpa_renew_evt_canceled_cbk = 0x40001240; r_lld_rpa_renew_evt_start_cbk = 0x40001244; r_lld_rpa_renew_instant_cbk = 0x40001248; r_lld_rxdesc_check = 0x4000124c; r_lld_rxdesc_free = 0x40001250; r_lld_scan_create_sync = 0x40001254; r_lld_scan_create_sync_cancel = 0x40001258; r_lld_scan_end = 0x4000125c; r_lld_scan_evt_canceled_cbk = 0x40001260; r_lld_scan_evt_start_cbk = 0x40001264; r_lld_scan_frm_cbk = 0x40001268; r_lld_scan_frm_eof_isr = 0x4000126c; r_lld_scan_frm_rx_isr = 0x40001270; r_lld_scan_frm_skip_isr = 0x40001274; r_lld_scan_init = 0x40001278; r_lld_scan_params_update = 0x4000127c; r_lld_scan_process_pkt_rx_aux_adv_ind = 0x40001288; r_lld_scan_process_pkt_rx_aux_chain_ind = 0x4000128c; r_lld_scan_process_pkt_rx_aux_scan_rsp = 0x40001290; r_lld_scan_process_pkt_rx_ext_adv = 0x40001294; r_lld_scan_process_pkt_rx_ext_adv_ind = 0x40001298; r_lld_scan_process_pkt_rx_legacy_adv = 0x4000129c; r_lld_scan_restart = 0x400012a0; r_lld_scan_sched = 0x400012a4; r_lld_scan_set_tx_power = 0x400012a8; r_lld_scan_start = 0x400012ac; r_lld_scan_stop = 0x400012b0; r_lld_scan_sync_accept = 0x400012b4; r_lld_scan_sync_info_unpack = 0x400012b8; r_lld_scan_trunc_ind = 0x400012bc; r_lld_sw_cca_evt_handler = 0x400012c0; r_lld_sw_cca_isr = 0x400012c4; r_lld_sync_ch_map_update = 0x400012c8; r_lld_sync_cleanup = 0x400012cc; r_lld_sync_evt_canceled_cbk = 0x400012d0; r_lld_sync_evt_start_cbk = 0x400012d4; r_lld_sync_frm_cbk = 0x400012d8; r_lld_sync_frm_eof_isr = 0x400012dc; r_lld_sync_frm_rx_isr = 0x400012e0; r_lld_sync_frm_skip_isr = 0x400012e4; r_lld_sync_init = 0x400012e8; r_lld_sync_process_pkt_rx = 0x400012ec; r_lld_sync_process_pkt_rx_aux_sync_ind = 0x400012f0; r_lld_sync_process_pkt_rx_pkt_check = 0x400012f4; r_lld_sync_scan_dynamic_pti_process = 0x400012f8; r_lld_sync_sched = 0x400012fc; r_lld_sync_start = 0x40001300; r_lld_sync_stop = 0x40001304; r_lld_sync_trunc_ind = 0x40001308; r_lld_test_cleanup = 0x4000130c; r_lld_test_evt_canceled_cbk = 0x40001310; r_lld_test_evt_start_cbk = 0x40001314; r_lld_test_freq2chnl = 0x40001318; r_lld_test_frm_cbk = 0x4000131c; r_lld_test_frm_isr = 0x40001320; r_lld_test_init = 0x40001324; r_lld_test_rx_isr = 0x40001328; r_lld_test_set_tx_power = 0x4000132c; r_lld_test_start = 0x40001330; r_lld_test_stop = 0x40001334; r_lld_update_rxbuf = 0x40001338; r_lld_update_rxbuf_isr = 0x4000133c; r_lld_white_list_add = 0x40001340; r_lld_white_list_rem = 0x40001344; r_llm_activity_free_get = 0x40001348; r_llm_activity_free_set = 0x4000134c; r_llm_activity_syncing_get = 0x40001350; r_llm_adv_con_len_check = 0x40001354; r_llm_adv_hdl_to_id = 0x40001358; r_llm_adv_rep_flow_control_check = 0x4000135c; r_llm_adv_rep_flow_control_update = 0x40001360; r_llm_adv_reports_list_check = 0x40001364; r_llm_adv_set_all_release = 0x40001368; r_llm_adv_set_dft_params = 0x4000136c; r_llm_adv_set_release = 0x40001370; r_llm_aes_res_cb = 0x40001374; r_llm_ble_update_adv_flow_control = 0x40001378; r_llm_ch_map_update = 0x4000137c; r_llm_cmd_cmp_send = 0x40001380; r_llm_cmd_stat_send = 0x40001384; r_llm_dev_list_empty_entry = 0x40001388; r_llm_dev_list_search = 0x4000138c; r_llm_env_adv_dup_filt_deinit = 0x40001390; r_llm_env_adv_dup_filt_init = 0x40001394; r_llm_init_ble_adv_report_flow_contol = 0x40001398; r_llm_is_dev_connected = 0x4000139c; r_llm_is_dev_synced = 0x400013a0; r_llm_is_non_con_act_ongoing_check = 0x400013a4; r_llm_is_wl_accessible = 0x400013a8; r_llm_le_evt_mask_check = 0x400013ac; r_llm_link_disc = 0x400013b4; r_llm_master_ch_map_get = 0x400013b8; r_llm_msg_handler_tab_p_get = 0x400013bc; r_llm_no_activity = 0x400013c0; r_llm_per_adv_slot_dur = 0x400013c4; r_llm_plan_elt_get = 0x400013c8; r_llm_rx_path_comp_get = 0x400013cc; r_llm_scan_start = 0x400013d0; r_llm_scan_sync_acad_attach = 0x400013d4; r_llm_scan_sync_acad_detach = 0x400013d8; r_llm_send_adv_lost_event_to_host = 0x400013dc; r_llm_tx_path_comp_get = 0x400013e0; r_misc_deinit = 0x400013e4; r_misc_free_em_buf_in_isr = 0x400013e8; r_misc_init = 0x400013ec; r_misc_msg_handler_tab_p_get = 0x400013f0; r_notEqual256 = 0x400013f4; r_phy_upd_proc_start = 0x400013f8; r_platform_reset = 0x400013fc; r_rf_em_init = 0x40001404; r_rf_force_agc_enable = 0x40001408; r_rf_reg_rd = 0x4000140c; r_rf_reg_wr = 0x40001410; r_rf_reset = 0x40001414; r_rf_rssi_convert = 0x40001418; r_rf_rw_v9_le_disable = 0x4000141c; r_rf_rw_v9_le_enable = 0x40001420; r_rf_sleep = 0x40001424; r_rf_util_cs_fmt_convert = 0x40001430; r_rw_crypto_aes_ccm = 0x40001434; r_rw_crypto_aes_encrypt = 0x40001438; r_rw_crypto_aes_init = 0x4000143c; r_rw_crypto_aes_k1 = 0x40001440; r_rw_crypto_aes_k2 = 0x40001444; r_rw_crypto_aes_k3 = 0x40001448; r_rw_crypto_aes_k4 = 0x4000144c; r_rw_crypto_aes_rand = 0x40001450; r_rw_crypto_aes_result_handler = 0x40001454; r_rw_crypto_aes_s1 = 0x40001458; r_rw_cryto_aes_cmac = 0x4000145c; r_rw_v9_init_em_radio_table = 0x40001460; r_rwble_sleep_enter = 0x40001468; r_rwble_sleep_wakeup_end = 0x4000146c; r_rwbtdm_isr_wrapper = 0x40001470; r_rwip_active_check = 0x40001474; r_rwip_aes_encrypt = 0x40001478; r_rwip_assert = 0x4000147c; r_rwip_crypt_evt_handler = 0x40001480; r_rwip_crypt_isr_handler = 0x40001484; r_rwip_eif_get = 0x40001488; r_rwip_half_slot_2_lpcycles = 0x4000148c; r_rwip_hus_2_lpcycles = 0x40001490; r_rwip_isr = 0x40001494; r_rwip_lpcycles_2_hus = 0x40001498; r_rwip_prevent_sleep_clear = 0x4000149c; r_rwip_prevent_sleep_set = 0x400014a0; r_rwip_schedule = 0x400014a4; r_rwip_sleep = 0x400014a8; r_rwip_sw_int_handler = 0x400014ac; r_rwip_sw_int_req = 0x400014b0; r_rwip_time_get = 0x400014b4; r_rwip_timer_10ms_handler = 0x400014b8; r_rwip_timer_10ms_set = 0x400014bc; r_rwip_timer_hs_handler = 0x400014c0; r_rwip_timer_hs_set = 0x400014c4; r_rwip_timer_hus_handler = 0x400014c8; r_rwip_timer_hus_set = 0x400014cc; r_rwip_wakeup = 0x400014d0; r_rwip_wakeup_end = 0x400014d4; r_rwip_wlcoex_set = 0x400014d8; r_sch_alarm_clear = 0x400014dc; r_sch_alarm_init = 0x400014e0; r_sch_alarm_prog = 0x400014e4; r_sch_alarm_set = 0x400014e8; r_sch_alarm_timer_isr = 0x400014ec; r_sch_arb_conflict_check = 0x400014f0; r_sch_arb_elt_cancel = 0x400014f4; r_sch_arb_init = 0x400014fc; r_sch_arb_insert = 0x40001500; r_sch_arb_prog_timer = 0x40001504; r_sch_arb_remove = 0x40001508; r_sch_arb_sw_isr = 0x4000150c; r_sch_plan_chk = 0x40001510; r_sch_plan_clock_wrap_offset_update = 0x40001514; r_sch_plan_init = 0x40001518; r_sch_plan_interval_req = 0x4000151c; r_sch_plan_offset_max_calc = 0x40001520; r_sch_plan_offset_req = 0x40001524; r_sch_plan_position_range_compute = 0x40001528; r_sch_plan_rem = 0x4000152c; r_sch_plan_req = 0x40001530; r_sch_prog_init = 0x4000153c; r_sch_prog_push = 0x40001540; r_sch_prog_rx_isr = 0x40001544; r_sch_prog_skip_isr = 0x40001548; r_sch_prog_tx_isr = 0x4000154c; r_sch_slice_bg_add = 0x40001550; r_sch_slice_bg_remove = 0x40001554; r_sch_slice_compute = 0x40001558; r_sch_slice_fg_add = 0x4000155c; r_sch_slice_fg_remove = 0x40001560; r_sch_slice_init = 0x40001564; r_sch_slice_per_add = 0x40001568; r_sch_slice_per_remove = 0x4000156c; r_sdk_config_get_bt_sleep_enable = 0x40001570; r_sdk_config_get_hl_derived_opts = 0x40001574; r_sdk_config_get_opts = 0x40001578; r_sdk_config_get_priv_opts = 0x4000157c; r_sdk_config_set_bt_sleep_enable = 0x40001580; r_sdk_config_set_hl_derived_opts = 0x40001584; r_sdk_config_set_opts = 0x40001588; r_specialModP256 = 0x4000158c; r_unloaded_area_init = 0x40001590; r_vhci_flow_off = 0x40001594; r_vhci_flow_on = 0x40001598; r_vhci_notify_host_send_available = 0x4000159c; r_vhci_send_to_host = 0x400015a0; r_vnd_hci_command_handler = 0x400015a4; r_vshci_init = 0x400015a8; vnd_hci_command_handler_wrapper = 0x400015ac; /* Data (.data, .bss, .rodata) */ bt_rf_coex_cfg_p = 0x3fcdffcc; bt_rf_coex_hooks_p = 0x3fcdffc8; btdm_env_p = 0x3fcdffc4; g_rw_controller_task_handle = 0x3fcdffc0; g_rw_init_sem = 0x3fcdffbc; g_rw_schd_queue = 0x3fcdffb8; lld_init_env = 0x3fcdffb4; lld_rpa_renew_env = 0x3fcdffb0; lld_scan_env = 0x3fcdffac; lld_scan_sync_env = 0x3fcdffa8; lld_test_env = 0x3fcdffa4; p_ble_util_buf_env = 0x3fcdffa0; p_lld_env = 0x3fcdff9c; p_llm_env = 0x3fcdff98; r_h4tl_eif_p = 0x3fcdff94; r_hli_funcs_p = 0x3fcdff90; r_ip_funcs_p = 0x3fcdff8c; r_modules_funcs_p = 0x3fcdff88; r_osi_funcs_p = 0x3fcdff84; r_plf_funcs_p = 0x3fcdff80; vhci_env_p = 0x3fcdff7c; aa_gen = 0x3fcdff78; aes_env = 0x3fcdff6c; bt_rf_coex_cfg_cb = 0x3fcdff1c; btdm_pwr_state = 0x3fcdff18; btdm_slp_err = 0x3fcdff14; ecc_env = 0x3fcdff0c; esp_handler = 0x3fcdff04; esp_vendor_cmd = 0x3fcdfefc; g_adv_delay_dis = 0x3fcdfef8; g_conflict_elt = 0x3fcdfef4; g_eif_api = 0x3fcdfee4; g_event_empty = 0x3fcdfed8; g_llc_state = 0x3fcdfecc; g_llm_state = 0x3fcdfec8; g_max_evt_env = 0x3fcdfec4; g_misc_state = 0x3fcdfec0; g_rma_rule_db = 0x3fcdfea4; g_rtp_rule_db = 0x3fcdfe88; g_scan_forever = 0x3fcdfe85; g_time_msb = 0x3fcdfe84; h4tl_env = 0x3fcdfe5c; hci_env = 0x3fcdfe38; hci_ext_host = 0x3fcdfe34; hci_fc_env = 0x3fcdfe2c; hci_tl_env = 0x3fcdfe00; ke_env = 0x3fcdfdd0; ke_event_env = 0x3fcdfd90; ke_task_env = 0x3fcdfd14; llc_env = 0x3fcdfcec; lld_adv_env = 0x3fcdfcc4; lld_con_env = 0x3fcdfc9c; lld_exp_sync_pos_tab = 0x3fcdfc94; lld_per_adv_env = 0x3fcdfc6c; lld_sync_env = 0x3fcdfc44; llm_le_adv_flow_env = 0x3fcdfc38; rw_sleep_enable = 0x3fcdfc34; rwble_env = 0x3fcdfc2c; rwip_env = 0x3fcdfc10; rwip_param = 0x3fcdfc04; rwip_prog_delay = 0x3fcdfc00; rwip_rf = 0x3fcdfbc8; sch_alarm_env = 0x3fcdfbc0; sch_arb_env = 0x3fcdfbac; sch_plan_env = 0x3fcdfba4; sch_prog_env = 0x3fcdfaa0; sch_slice_env = 0x3fcdfa40; sch_slice_params = 0x3fcdfa38; timer_env = 0x3fcdfa30; unloaded_area = 0x3fcdfa2c; vshci_state = 0x3fcdfa28; TASK_DESC_LLC = 0x3fcdfa1c; TASK_DESC_LLM = 0x3fcdfa10; TASK_DESC_VSHCI = 0x3fcdfa04; co_default_bdaddr = 0x3fcdf9fc; dbg_assert_block = 0x3fcdf9f8; g_bt_plf_log_level = 0x3fcdf9f4; hci_cmd_desc_tab_vs_esp = 0x3fcdf9d0; hci_command_handler_tab_esp = 0x3fcdf9b8; privacy_en = 0x3fcdf9b4; sdk_cfg_priv_opts = 0x3fcdf96c; BasePoint_x_256 = 0x3ff1ffdc; BasePoint_y_256 = 0x3ff1ffbc; DebugE256PublicKey_x = 0x3ff1ff9c; DebugE256PublicKey_y = 0x3ff1ff7c; DebugE256SecretKey = 0x3ff1ff5c; ECC_4Win_Look_up_table = 0x3ff1f7a0; LLM_AA_CT1 = 0x3ff1f79c; LLM_AA_CT2 = 0x3ff1f798; RF_TX_PW_CONV_TBL = 0x3ff1f790; TASK_DESC_MISC = 0x3ff1f784; adv_evt_prop2type = 0x3ff1f768; adv_evt_type2prop = 0x3ff1f760; aes_cmac_zero = 0x3ff1f750; aes_k2_salt = 0x3ff1f740; aes_k3_id64 = 0x3ff1f738; aes_k3_salt = 0x3ff1f728; aes_k4_id6 = 0x3ff1f724; aes_k4_salt = 0x3ff1f714; bigHexP256 = 0x3ff1f6e8; byte_tx_time = 0x3ff1f6e0; co_null_bdaddr = 0x3ff1f6d8; co_phy_mask_to_rate = 0x3ff1f6d0; co_phy_mask_to_value = 0x3ff1f6c8; co_phy_to_rate = 0x3ff1f6c4; co_phy_value_to_mask = 0x3ff1f6c0; co_rate_to_byte_dur_us = 0x3ff1f6b8; co_rate_to_phy = 0x3ff1f6b0; co_rate_to_phy_mask = 0x3ff1f6ac; co_sca2ppm = 0x3ff1f69c; coef_B = 0x3ff1f670; connect_req_dur_tab = 0x3ff1f668; ecc_Jacobian_InfinityPoint256 = 0x3ff1f5e4; em_base_reg_lut = 0x3ff1f518; fixed_tx_time = 0x3ff1f510; h4tl_msgtype2hdrlen = 0x3ff1f508; hci_cmd_desc_root_tab = 0x3ff1f4d8; hci_cmd_desc_tab_ctrl_bb = 0x3ff1f46c; hci_cmd_desc_tab_info_par = 0x3ff1f43c; hci_cmd_desc_tab_le = 0x3ff1f0a0; hci_cmd_desc_tab_lk_ctrl = 0x3ff1f088; hci_cmd_desc_tab_stat_par = 0x3ff1f07c; hci_cmd_desc_tab_vs = 0x3ff1f040; hci_evt_desc_tab = 0x3ff1eff8; hci_evt_le_desc_tab = 0x3ff1ef58; hci_evt_le_desc_tab_esp = 0x3ff1ef50; hci_rsvd_evt_msk = 0x3ff1ef48; lld_aux_phy_to_rate = 0x3ff1ef44; lld_init_max_aux_dur_tab = 0x3ff1ef3c; lld_scan_map_legacy_pdu_to_evt_type = 0x3ff1ef34; lld_scan_max_aux_dur_tab = 0x3ff1ef2c; lld_sync_max_aux_dur_tab = 0x3ff1ef24; llm_local_le_feats = 0x3ff1ef1c; llm_local_le_states = 0x3ff1ef14; llm_local_supp_cmds = 0x3ff1eeec; maxSecretKey_256 = 0x3ff1eecc; max_data_tx_time = 0x3ff1eec4; one_bits = 0x3ff1eeb4; rwip_coex_cfg = 0x3ff1eeac; rwip_priority = 0x3ff1ee94; veryBigHexP256 = 0x3ff1ee48; /* bluetooth hook funcs */ r_llc_loc_encrypt_proc_continue_hook = 0x40001c60; r_llc_loc_phy_upd_proc_continue_hook = 0x40001c64; r_llc_rem_phy_upd_proc_continue_hook = 0x40001c68; r_lld_scan_frm_eof_isr_hook = 0x40001c6c; r_lld_scan_evt_start_cbk_hook = 0x40001c70; r_lld_scan_process_pkt_rx_ext_adv_hook = 0x40001c78; r_lld_scan_sched_hook = 0x40001c7c; r_lld_adv_evt_start_cbk_hook = 0x40001c84; r_lld_adv_aux_evt_start_cbk_hook = 0x40001c88; r_lld_adv_frm_isr_hook = 0x40001c8c; r_lld_adv_start_init_evt_param_hook = 0x40001c90; r_lld_con_evt_canceled_cbk_hook = 0x40001c94; r_lld_con_frm_isr_hook = 0x40001c98; r_lld_con_tx_hook = 0x40001c9c; r_lld_con_rx_hook = 0x40001ca0; r_lld_con_evt_start_cbk_hook = 0x40001ca4; r_lld_con_tx_prog_new_packet_hook = 0x40001cac; r_lld_init_frm_eof_isr_hook = 0x40001cb0; r_lld_init_evt_start_cbk_hook = 0x40001cb4; r_lld_init_sched_hook = 0x40001cbc; r_lld_init_process_pkt_tx_hook = 0x40001cc0; r_lld_per_adv_evt_start_cbk_hook = 0x40001cc4; r_lld_per_adv_frm_isr_hook = 0x40001cc8; r_lld_per_adv_start_hook = 0x40001ccc; r_lld_sync_frm_eof_isr_hook = 0x40001cd0; r_lld_sync_evt_start_cbk_hook = 0x40001cd4; r_lld_sync_start_hook = 0x40001cd8; r_lld_sync_process_pkt_rx_pkt_check_hook = 0x40001cdc; r_sch_arb_insert_hook = 0x40001ce0; r_sch_plan_offset_req_hook = 0x40001ce4; /*************************************** Group rom_pp ***************************************/ /* Functions */ esp_pp_rom_version_get = 0x400015b0; RC_GetBlockAckTime = 0x400015b4; ebuf_list_remove = 0x400015b8; /*esf_buf_alloc = 0x400015bc;*/ GetAccess = 0x400015c8; hal_mac_is_low_rate_enabled = 0x400015cc; hal_mac_tx_get_blockack = 0x400015d0; /* hal_mac_tx_set_ppdu = 0x400015d4;*/ ic_get_trc = 0x400015d8; /* ic_mac_deinit = 0x400015dc; */ ic_mac_init = 0x400015e0; ic_interface_enabled = 0x400015e4; is_lmac_idle = 0x400015e8; /*lmacAdjustTimestamp = 0x400015ec;*/ lmacDiscardAgedMSDU = 0x400015f0; /*lmacDiscardMSDU = 0x400015f4;*/ lmacEndFrameExchangeSequence = 0x400015f8; lmacIsIdle = 0x400015fc; lmacIsLongFrame = 0x40001600; /*lmacMSDUAged = 0x40001604;*/ lmacPostTxComplete = 0x40001608; lmacProcessAllTxTimeout = 0x4000160c; lmacProcessCollisions = 0x40001610; lmacProcessRxSucData = 0x40001614; lmacReachLongLimit = 0x40001618; lmacReachShortLimit = 0x4000161c; lmacRecycleMPDU = 0x40001620; lmacRxDone = 0x40001624; /*lmacSetTxFrame = 0x40001628;*/ /*lmacTxFrame = 0x40001630;*/ mac_tx_set_duration = 0x40001634; /* mac_tx_set_htsig = 0x40001638;*/ mac_tx_set_plcp0 = 0x4000163c; /* mac_tx_set_plcp1 = 0x40001640;*/ mac_tx_set_plcp2 = 0x40001644; /* pm_check_state = 0x40001648; */ pm_disable_dream_timer = 0x4000164c; pm_disable_sleep_delay_timer = 0x40001650; pm_dream = 0x40001654; pm_mac_wakeup = 0x40001658; pm_mac_sleep = 0x4000165c; pm_enable_active_timer = 0x40001660; pm_enable_sleep_delay_timer = 0x40001664; pm_local_tsf_process = 0x40001668; pm_set_beacon_filter = 0x4000166c; pm_is_in_wifi_slice_threshold = 0x40001670; pm_is_waked = 0x40001674; pm_keep_alive = 0x40001678; /* pm_on_beacon_rx = 0x4000167c; */ pm_on_data_rx = 0x40001680; pm_on_tbtt = 0x40001684; /* pm_parse_beacon = 0x40001688;*/ /* pm_process_tim = 0x4000168c; */ /*pm_rx_beacon_process = 0x40001690;*/ /* pm_rx_data_process = 0x40001694; */ /*pm_sleep = 0x40001698;*/ pm_sleep_for = 0x4000169c; /* pm_tbtt_process = 0x400016a0; */ ppAMPDU2Normal = 0x400016a4; /*ppAssembleAMPDU = 0x400016a8;*/ ppCalFrameTimes = 0x400016ac; ppCalSubFrameLength = 0x400016b0; /*ppCalTxAMPDULength = 0x400016b4;*/ ppCheckTxAMPDUlength = 0x400016b8; ppDequeueRxq_Locked = 0x400016bc; ppDequeueTxQ = 0x400016c0; ppEmptyDelimiterLength = 0x400016c4; ppEnqueueRxq = 0x400016c8; ppEnqueueTxDone = 0x400016cc; ppGetTxQFirstAvail_Locked = 0x400016d0; ppGetTxframe = 0x400016d4; ppProcessRxPktHdr = 0x400016e0; ppProcessTxQ = 0x400016e4; ppRecordBarRRC = 0x400016e8; lmacRequestTxopQueue = 0x400016ec; lmacReleaseTxopQueue = 0x400016f0; ppRecycleAmpdu = 0x400016f4; ppRecycleRxPkt = 0x400016f8; ppResortTxAMPDU = 0x400016fc; ppResumeTxAMPDU = 0x40001700; /* ppRxFragmentProc = 0x40001704; */ /* ppRxPkt = 0x40001708; */ ppRxProtoProc = 0x4000170c; ppSearchTxQueue = 0x40001710; ppSearchTxframe = 0x40001714; ppSelectNextQueue = 0x40001718; ppSubFromAMPDU = 0x4000171c; ppTask = 0x40001720; ppTxPkt = 0x40001724; ppTxProtoProc = 0x40001728; ppTxqUpdateBitmap = 0x4000172c; pp_coex_tx_request = 0x40001730; pp_hdrsize = 0x40001734; pp_post = 0x40001738; pp_process_hmac_waiting_txq = 0x4000173c; rcGetAmpduSched = 0x40001740; rcUpdateRxDone = 0x40001744; rc_get_trc = 0x40001748; rc_get_trc_by_index = 0x4000174c; rcAmpduLowerRate = 0x40001750; rcampduuprate = 0x40001754; rcClearCurAMPDUSched = 0x40001758; rcClearCurSched = 0x4000175c; rcClearCurStat = 0x40001760; rcLowerSched = 0x40001768; rcSetTxAmpduLimit = 0x4000176c; /* rcTxUpdatePer = 0x40001770;*/ rcUpdateAckSnr = 0x40001774; /*rcUpdateRate = 0x40001778;*/ /* rcUpdateTxDone = 0x4000177c; */ rcUpdateTxDoneAmpdu2 = 0x40001780; rcUpSched = 0x40001784; rssi_margin = 0x40001788; rx11NRate2AMPDULimit = 0x4000178c; TRC_AMPDU_PER_DOWN_THRESHOLD = 0x40001790; TRC_AMPDU_PER_UP_THRESHOLD = 0x40001794; trc_calc_duration = 0x40001798; trc_isTxAmpduOperational = 0x4000179c; trc_onAmpduOp = 0x400017a0; TRC_PER_IS_GOOD = 0x400017a4; trc_SetTxAmpduState = 0x400017a8; trc_tid_isTxAmpduOperational = 0x400017ac; trcAmpduSetState = 0x400017b0; wDev_AppendRxBlocks = 0x400017b8; wDev_DiscardFrame = 0x400017bc; wDev_GetNoiseFloor = 0x400017c0; wDev_IndicateAmpdu = 0x400017c4; /*wDev_IndicateFrame = 0x400017c8;*/ wdev_bank_store = 0x400017cc; wdev_bank_load = 0x400017d0; wdev_mac_reg_load = 0x400017d4; wdev_mac_reg_store = 0x400017d8; wdev_mac_special_reg_load = 0x400017dc; wdev_mac_special_reg_store = 0x400017e0; wdev_mac_wakeup = 0x400017e4; wdev_mac_sleep = 0x400017e8; hal_mac_is_dma_enable = 0x400017ec; /*wDev_ProcessFiq = 0x400017f0;*/ /*wDev_ProcessRxSucData = 0x400017f4;*/ wdevProcessRxSucDataAll = 0x400017f8; wdev_csi_len_align = 0x400017fc; ppDequeueTxDone_Locked = 0x40001800; /*pm_tx_data_done_process = 0x40001808;*/ config_is_cache_tx_buf_enabled = 0x4000180c; //ppMapWaitTxq = 0x40001810; ppProcessWaitingQueue = 0x40001814; ppDisableQueue = 0x40001818; pm_allow_tx = 0x4000181c; /* Data (.data, .bss, .rodata) */ our_instances_ptr = 0x3ff1ee44; pTxRx = 0x3fcdf968; lmacConfMib_ptr = 0x3fcdf964; our_wait_eb = 0x3fcdf960; our_tx_eb = 0x3fcdf95c; pp_wdev_funcs = 0x3fcdf958; g_osi_funcs_p = 0x3fcdf954; wDevCtrl_ptr = 0x3fcdf950; g_wdev_last_desc_reset_ptr = 0x3ff1ee40; wDevMacSleep_ptr = 0x3fcdf94c; g_lmac_cnt_ptr = 0x3fcdf948; our_controls_ptr = 0x3ff1ee3c; pp_sig_cnt_ptr = 0x3fcdf944; g_eb_list_desc_ptr = 0x3fcdf940; s_fragment_ptr = 0x3fcdf93c; if_ctrl_ptr = 0x3fcdf938; g_intr_lock_mux = 0x3fcdf934; g_wifi_global_lock = 0x3fcdf930; s_wifi_queue = 0x3fcdf92c; pp_task_hdl = 0x3fcdf928; s_pp_task_create_sem = 0x3fcdf924; s_pp_task_del_sem = 0x3fcdf920; g_wifi_menuconfig_ptr = 0x3fcdf91c; xphyQueue = 0x3fcdf918; ap_no_lr_ptr = 0x3fcdf914; rc11BSchedTbl_ptr = 0x3fcdf910; rc11NSchedTbl_ptr = 0x3fcdf90c; rcLoRaSchedTbl_ptr = 0x3fcdf908; BasicOFDMSched_ptr = 0x3fcdf904; trc_ctl_ptr = 0x3fcdf900; g_pm_cnt_ptr = 0x3fcdf8fc; g_pm_ptr = 0x3fcdf8f8; g_pm_cfg_ptr = 0x3fcdf8f4; g_esp_mesh_quick_funcs_ptr = 0x3fcdf8f0; g_txop_queue_status_ptr = 0x3fcdf8ec; g_mac_sleep_en_ptr = 0x3fcdf8e8; g_mesh_is_root_ptr = 0x3fcdf8e4; g_mesh_topology_ptr = 0x3fcdf8e0; g_mesh_init_ps_type_ptr = 0x3fcdf8dc; g_mesh_is_started_ptr = 0x3fcdf8d8; g_config_func = 0x3fcdf8d4; g_net80211_tx_func = 0x3fcdf8d0; g_timer_func = 0x3fcdf8cc; s_michael_mic_failure_cb = 0x3fcdf8c8; wifi_sta_rx_probe_req = 0x3fcdf8c4; g_tx_done_cb_func = 0x3fcdf8c0; g_per_conn_trc = 0x3fcdf874; s_encap_amsdu_func = 0x3fcdf870; /*************************************** Group rom_net80211 ***************************************/ /* Functions */ esp_net80211_rom_version_get = 0x40001820; ampdu_dispatch = 0x40001824; ampdu_dispatch_all = 0x40001828; ampdu_dispatch_as_many_as_possible = 0x4000182c; ampdu_dispatch_movement = 0x40001830; ampdu_dispatch_upto = 0x40001834; chm_is_at_home_channel = 0x40001838; cnx_node_is_existing = 0x4000183c; cnx_node_search = 0x40001840; ic_ebuf_recycle_rx = 0x40001844; ic_ebuf_recycle_tx = 0x40001848; ic_reset_rx_ba = 0x4000184c; ieee80211_align_eb = 0x40001850; ieee80211_ampdu_reorder = 0x40001854; ieee80211_ampdu_start_age_timer = 0x40001858; /*ieee80211_encap_esfbuf = 0x4000185c;*/ ieee80211_is_tx_allowed = 0x40001860; ieee80211_output_pending_eb = 0x40001864; /*ieee80211_output_process = 0x40001868;*/ ieee80211_set_tx_desc = 0x4000186c; rom_sta_input = 0x40001870; wifi_get_macaddr = 0x40001874; wifi_rf_phy_disable = 0x40001878; wifi_rf_phy_enable = 0x4000187c; ic_ebuf_alloc = 0x40001880; ieee80211_classify = 0x40001884; ieee80211_copy_eb_header = 0x40001888; ieee80211_recycle_cache_eb = 0x4000188c; ieee80211_search_node = 0x40001890; roundup2 = 0x40001894; ieee80211_crypto_encap = 0x40001898; /* ieee80211_crypto_decap = 0x4000189c; */ /* ieee80211_decap = 0x400018a0; */ ieee80211_set_tx_pti = 0x400018a4; wifi_is_started = 0x400018a8; /* Data (.data, .bss, .rodata) */ net80211_funcs = 0x3fcdf86c; g_scan = 0x3fcdf868; g_chm = 0x3fcdf864; g_ic_ptr = 0x3fcdf860; g_hmac_cnt_ptr = 0x3fcdf85c; g_tx_cacheq_ptr = 0x3fcdf858; s_netstack_free = 0x3fcdf854; mesh_rxcb = 0x3fcdf850; sta_rxcb = 0x3fcdf84c; /*************************************** Group rom_coexist ***************************************/ /* Functions */ esp_coex_rom_version_get = 0x400018ac; coex_bt_release = 0x400018b0; coex_bt_request = 0x400018b4; coex_core_ble_conn_dyn_prio_get = 0x400018b8; coex_core_event_duration_get = 0x400018bc; coex_core_pti_get = 0x400018c0; coex_core_release = 0x400018c4; coex_core_request = 0x400018c8; coex_core_status_get = 0x400018cc; /*coex_core_timer_idx_get = 0x400018d0;*/ coex_event_duration_get = 0x400018d4; coex_hw_timer_disable = 0x400018d8; coex_hw_timer_enable = 0x400018dc; coex_hw_timer_set = 0x400018e0; coex_schm_interval_set = 0x400018e4; coex_schm_lock = 0x400018e8; coex_schm_unlock = 0x400018ec; coex_status_get = 0x400018f0; coex_wifi_release = 0x400018f4; esp_coex_ble_conn_dynamic_prio_get = 0x400018f8; /* Data (.data, .bss, .rodata) */ coex_env_ptr = 0x3fcdf848; coex_pti_tab_ptr = 0x3fcdf844; coex_schm_env_ptr = 0x3fcdf840; coexist_funcs = 0x3fcdf83c; g_coa_funcs_p = 0x3fcdf838; g_coex_param_ptr = 0x3fcdf834; /*************************************** Group rom_phy ***************************************/ /* Functions */ phy_get_romfuncs = 0x400018fc; rom_abs_temp = 0x40001900; rom_bb_bss_cbw40_dig = 0x40001904; rom_bb_wdg_test_en = 0x40001908; rom_bb_wdt_get_status = 0x4000190c; rom_bb_wdt_int_enable = 0x40001910; rom_bb_wdt_rst_enable = 0x40001914; rom_bb_wdt_timeout_clear = 0x40001918; rom_cbw2040_cfg = 0x4000191c; rom_check_noise_floor = 0x40001920; rom_chip_i2c_readReg = 0x40001924; rom_chip_i2c_writeReg = 0x40001928; rom_correct_rf_ana_gain = 0x4000192c; rom_dc_iq_est = 0x40001930; rom_disable_agc = 0x40001934; rom_en_pwdet = 0x40001938; rom_enable_agc = 0x4000193c; rom_get_bbgain_db = 0x40001940; rom_get_data_sat = 0x40001944; rom_get_i2c_read_mask = 0x40001948; rom_get_pwctrl_correct = 0x4000194c; rom_get_rf_gain_qdb = 0x40001950; rom_i2c_readReg = 0x40001954; rom_i2c_readReg_Mask = 0x40001958; rom_i2c_writeReg = 0x4000195c; rom_i2c_writeReg_Mask = 0x40001960; /* rom_index_to_txbbgain = 0x40001964; */ rom_iq_est_disable = 0x40001968; rom_iq_est_enable = 0x4000196c; rom_linear_to_db = 0x40001970; rom_loopback_mode_en = 0x40001974; rom_mhz2ieee = 0x40001978; rom_noise_floor_auto_set = 0x4000197c; rom_pbus_debugmode = 0x40001980; rom_pbus_force_mode = 0x40001984; rom_pbus_force_test = 0x40001988; rom_pbus_rd = 0x4000198c; rom_pbus_rd_addr = 0x40001990; rom_pbus_rd_shift = 0x40001994; rom_pbus_set_dco = 0x40001998; rom_pbus_set_rxgain = 0x4000199c; rom_pbus_workmode = 0x400019a0; rom_pbus_xpd_rx_off = 0x400019a4; rom_pbus_xpd_rx_on = 0x400019a8; rom_pbus_xpd_tx_off = 0x400019ac; /* rom_pbus_xpd_tx_on = 0x400019b0; */ rom_phy_byte_to_word = 0x400019b4; rom_phy_disable_cca = 0x400019b8; rom_phy_enable_cca = 0x400019bc; rom_phy_get_noisefloor = 0x400019c0; rom_phy_get_rx_freq = 0x400019c4; rom_phy_set_bbfreq_init = 0x400019c8; rom_pow_usr = 0x400019cc; rom_pwdet_sar2_init = 0x400019d0; rom_read_hw_noisefloor = 0x400019d4; rom_read_sar_dout = 0x400019d8; rom_set_cal_rxdc = 0x400019dc; rom_set_chan_cal_interp = 0x400019e0; rom_set_loopback_gain = 0x400019e4; rom_set_noise_floor = 0x400019e8; rom_set_rxclk_en = 0x400019ec; /* rom_set_tx_dig_gain = 0x400019f0; */ /* rom_set_txcap_reg = 0x400019f4; */ rom_set_txclk_en = 0x400019f8; rom_spur_cal = 0x400019fc; rom_spur_reg_write_one_tone = 0x40001a00; rom_target_power_add_backoff = 0x40001a04; rom_tx_pwctrl_bg_init = 0x40001a08; /* rom_txbbgain_to_index = 0x40001a0c; */ rom_wifi_11g_rate_chg = 0x40001a10; rom_write_gain_mem = 0x40001a14; chip726_phyrom_version = 0x40001a18; rom_disable_wifi_agc = 0x40001a1c; rom_enable_wifi_agc = 0x40001a20; rom_set_tx_gain_table = 0x40001a24; rom_bt_index_to_bb = 0x40001a28; rom_bt_bb_to_index = 0x40001a2c; rom_wr_bt_tx_atten = 0x40001a30; rom_wr_bt_tx_gain_mem = 0x40001a34; rom_spur_coef_cfg = 0x40001a38; rom_bb_bss_cbw40 = 0x40001a3c; rom_set_cca = 0x40001a40; rom_tx_paon_set = 0x40001a44; rom_i2cmst_reg_init = 0x40001a48; rom_iq_corr_enable = 0x40001a4c; rom_fe_reg_init = 0x40001a50; /* rom_agc_reg_init = 0x40001a54; */ /* rom_bb_reg_init = 0x40001a58; */ rom_mac_enable_bb = 0x40001a5c; rom_bb_wdg_cfg = 0x40001a60; rom_force_txon = 0x40001a64; rom_fe_txrx_reset = 0x40001a68; rom_set_rx_comp = 0x40001a6c; /* rom_set_pbus_reg = 0x40001a70; */ rom_write_chan_freq = 0x40001a74; /* rom_phy_xpd_rf = 0x40001a78; */ rom_set_xpd_sar = 0x40001a7c; rom_write_dac_gain2 = 0x40001a80; rom_rtc_sar2_init = 0x40001a84; rom_get_target_power_offset = 0x40001a88; /* rom_write_txrate_power_offset = 0x40001a8c; */ rom_get_rate_fcc_index = 0x40001a90; rom_get_rate_target_power = 0x40001a94; rom_write_wifi_dig_gain = 0x40001a98; rom_bt_correct_rf_ana_gain = 0x40001a9c; rom_pkdet_vol_start = 0x40001aa0; rom_read_sar2_code = 0x40001aa4; rom_get_sar2_vol = 0x40001aa8; rom_get_pll_vol = 0x40001aac; rom_get_phy_target_power = 0x40001ab0; /* rom_temp_to_power = 0x40001ab4; */ rom_phy_track_pll_cap = 0x40001ab8; rom_phy_pwdet_always_en = 0x40001abc; rom_phy_pwdet_onetime_en = 0x40001ac0; rom_get_i2c_mst0_mask = 0x40001ac4; rom_get_i2c_hostid = 0x40001ac8; rom_enter_critical_phy = 0x40001acc; rom_exit_critical_phy = 0x40001ad0; rom_chip_i2c_readReg_org = 0x40001ad4; rom_i2c_paral_set_mst0 = 0x40001ad8; rom_i2c_paral_set_read = 0x40001adc; rom_i2c_paral_read = 0x40001ae0; rom_i2c_paral_write = 0x40001ae4; rom_i2c_paral_write_num = 0x40001ae8; rom_i2c_paral_write_mask = 0x40001aec; rom_bb_bss_cbw40_ana = 0x40001af0; rom_chan_to_freq = 0x40001af4; /* rom_open_i2c_xpd = 0x40001af8; */ rom_dac_rate_set = 0x40001afc; /* rom_tsens_read_init = 0x40001b00; */ /* rom_tsens_code_read = 0x40001b04; */ rom_tsens_index_to_dac = 0x40001b08; rom_tsens_index_to_offset = 0x40001b0c; /* rom_tsens_dac_cal = 0x40001b10; */ rom_code_to_temp = 0x40001b14; rom_write_pll_cap_mem = 0x40001b18; rom_pll_correct_dcap = 0x40001b1c; rom_phy_en_hw_set_freq = 0x40001b20; rom_phy_dis_hw_set_freq = 0x40001b24; /* rom_pll_vol_cal = 0x40001b28; */ ================================================ FILE: targets/esp32s3.json ================================================ { "inherits": ["xtensa"], "cpu": "esp32s3", "features": "+atomctl,+bool,+clamps,+coprocessor,+debug,+density,+div32,+esp32s3,+exception,+fp,+highpriinterrupts,+interrupt,+loop,+mac16,+memctl,+minmax,+miscsr,+mul32,+mul32high,+nsa,+prid,+regprotect,+rvector,+s32c1i,+sext,+threadptr,+timerint,+windowed", "build-tags": ["esp32s3", "esp"], "scheduler": "tasks", "serial": "uart", "linker": "ld.lld", "default-stack-size": 2048, "rtlib": "compiler-rt", "libc": "picolibc", "linkerscript": "targets/esp32s3.ld", "extra-files": [ "src/device/esp/esp32.S", "src/internal/task/task_stack_esp32.S" ], "binary-format": "esp32s3", "flash-command": "esptool.py --chip=esp32s3 --port {port} write_flash 0x0000 {bin} -ff 80m -fm dout", "emulator": "qemu-system-xtensa -machine esp32 -nographic -drive file={img},if=mtd,format=raw", "gdb": ["xtensa-esp32-elf-gdb"] } ================================================ FILE: targets/esp32s3.ld ================================================ /* Linker script for the ESP32-S3 */ MEMORY { /* Note: DRAM and IRAM below are actually in the same 416K address space. */ DRAM (rw) : ORIGIN = 0x3FC88000, LENGTH = 416K /* Internal SRAM 1 (data bus) */ IRAM (x) : ORIGIN = 0x40370000, LENGTH = 416K /* Internal SRAM 1 (instruction bus) */ /* Note: DROM and IROM below are actually in the same 32M address space. */ DROM (r) : ORIGIN = 0x3C000000, LENGTH = 32M /* Data bus (read-only) */ IROM (rx) : ORIGIN = 0x42000000, LENGTH = 32M /* Instruction bus */ } /* The entry point. It is set in the image flashed to the chip, so must be * defined. */ ENTRY(call_start_cpu0) SECTIONS { /* Put the stack at the bottom of DRAM, so that the application will * crash on stack overflow instead of silently corrupting memory. * See: http://blog.japaric.io/stack-overflow-protection/ */ .stack (NOLOAD) : { . = ALIGN(16); . += _stack_size; _stack_top = .; } >DRAM /* Constant literals and code. Loaded into IRAM for now. Eventually, most * code should be executed directly from flash. * Note that literals must be before code for the l32r instruction to work. */ .text.call_start_cpu0 : ALIGN(4) { *(.literal.call_start_cpu0) *(.text.call_start_cpu0) } >IRAM AT >DRAM /* All other code and literals */ .text : ALIGN(4) { *(.literal .text) *(.literal.* .text.*) *(.text) *(.text.*) } >IRAM AT >DRAM /* Constant global variables. * They are loaded in DRAM for ease of use. Eventually they should be stored * in flash and loaded directly from there but they're kept in RAM to make * sure they can always be accessed (even in interrupts). */ .rodata : ALIGN(4) { *(.rodata) *(.rodata.*) } >DRAM /* Mutable global variables. */ .data : ALIGN(4) { _sdata = ABSOLUTE(.); *(.data) *(.data.*) _edata = ABSOLUTE(.); } >DRAM /* Check that the boot ROM stack (for the APP CPU) does not overlap with the * data that is loaded by the boot ROM. There may be ways to avoid this * issue if it occurs in practice. * The magic value here is _stack_sentry in the boot ROM ELF file. */ ASSERT(_edata < 0x3ffe1320, "the .data section overlaps with the stack used by the boot ROM, possibly causing corruption at startup") /* Global variables that are mutable and zero-initialized. * These must be zeroed at startup (unlike data, which is loaded by the * bootloader). */ .bss (NOLOAD) : ALIGN(4) { . = ALIGN (4); _sbss = ABSOLUTE(.); *(.bss) *(.bss.*) . = ALIGN (4); _ebss = ABSOLUTE(.); } >DRAM } /* For the garbage collector. */ _globals_start = _sdata; _globals_end = _ebss; _heap_start = _ebss; _heap_end = ORIGIN(DRAM) + LENGTH(DRAM); _stack_size = 4K; /* From ESP-IDF: * components/esp_rom/esp32/ld/esp32.rom.newlib-funcs.ld * This is the subset that is sometimes used by LLVM during codegen, and thus * must always be present. */ memset = 0x400011e8; memcpy = 0x400011f4; memmove = 0x40001200; memcmp = 0x4000120c; /* From ESP-IDF: * components/esp_rom/esp32/ld/esp32.rom.libgcc.ld * These are called from LLVM during codegen. The original license is Apache * 2.0, but I believe that a list of function names and addresses can't really * be copyrighted. */ __absvdi2 = 0x4000216c; __absvsi2 = 0x40002178; __adddf3 = 0x40002184; __addsf3 = 0x40002190; __addvdi3 = 0x4000219c; __addvsi3 = 0x400021a8; __ashldi3 = 0x400021b4; __ashrdi3 = 0x400021c0; __bswapdi2 = 0x400021cc; __bswapsi2 = 0x400021d8; __clear_cache = 0x400021e4; __clrsbdi2 = 0x400021f0; __clrsbsi2 = 0x400021fc; __clzdi2 = 0x40002208; __clzsi2 = 0x40002214; __cmpdi2 = 0x40002220; __ctzdi2 = 0x4000222c; __ctzsi2 = 0x40002238; __divdc3 = 0x40002244; __divdf3 = 0x40002250; __divdi3 = 0x4000225c; __divsc3 = 0x40002268; __divsf3 = 0x40002274; __divsi3 = 0x40002280; __eqdf2 = 0x4000228c; __eqsf2 = 0x40002298; __extendsfdf2 = 0x400022a4; __ffsdi2 = 0x400022b0; __ffssi2 = 0x400022bc; __fixdfdi = 0x400022c8; __fixdfsi = 0x400022d4; __fixsfdi = 0x400022e0; __fixsfsi = 0x400022ec; __fixunsdfsi = 0x400022f8; __fixunssfdi = 0x40002304; __fixunssfsi = 0x40002310; __floatdidf = 0x4000231c; __floatdisf = 0x40002328; __floatsidf = 0x40002334; __floatsisf = 0x40002340; __floatundidf = 0x4000234c; __floatundisf = 0x40002358; __floatunsidf = 0x40002364; __floatunsisf = 0x40002370; __gcc_bcmp = 0x4000237c; __gedf2 = 0x40002388; __gesf2 = 0x40002394; __gtdf2 = 0x400023a0; __gtsf2 = 0x400023ac; __ledf2 = 0x400023b8; __lesf2 = 0x400023c4; __lshrdi3 = 0x400023d0; __ltdf2 = 0x400023dc; __ltsf2 = 0x400023e8; __moddi3 = 0x400023f4; __modsi3 = 0x40002400; __muldc3 = 0x4000240c; __muldf3 = 0x40002418; __muldi3 = 0x40002424; __mulsc3 = 0x40002430; __mulsf3 = 0x4000243c; __mulsi3 = 0x40002448; __mulvdi3 = 0x40002454; __mulvsi3 = 0x40002460; __nedf2 = 0x4000246c; __negdf2 = 0x40002478; __negdi2 = 0x40002484; __negsf2 = 0x40002490; __negvdi2 = 0x4000249c; __negvsi2 = 0x400024a8; __nesf2 = 0x400024b4; __paritysi2 = 0x400024c0; __popcountdi2 = 0x400024cc; __popcountsi2 = 0x400024d8; __powidf2 = 0x400024e4; __powisf2 = 0x400024f0; __subdf3 = 0x400024fc; __subsf3 = 0x40002508; __subvdi3 = 0x40002514; __subvsi3 = 0x40002520; __truncdfsf2 = 0x4000252c; __ucmpdi2 = 0x40002538; __udivdi3 = 0x40002544; __udivmoddi4 = 0x40002550; __udivsi3 = 0x4000255c; __udiv_w_sdiv = 0x40002568; __umoddi3 = 0x40002574; __umodsi3 = 0x40002580; __unorddf2 = 0x4000258c; __unordsf2 = 0x40002598; ================================================ FILE: targets/esp8266.json ================================================ { "inherits": ["xtensa"], "cpu": "esp8266", "features": "+debug,+density,+exception,+extendedl32r,+highpriinterrupts,+interrupt,+mul32,+nsa,+prid,+regprotect,+rvector,+timerint", "build-tags": ["esp8266", "esp"], "scheduler": "tasks", "linker": "ld.lld", "default-stack-size": 2048, "rtlib": "compiler-rt", "libc": "picolibc", "linkerscript": "targets/esp8266.ld", "extra-files": [ "src/device/esp/esp8266.S", "src/internal/task/task_stack_esp8266.S" ], "binary-format": "esp8266", "flash-command": "esptool.py --chip=esp8266 --port {port} write_flash 0x00000 {bin} -fm qio" } ================================================ FILE: targets/esp8266.ld ================================================ /* Linker script for the ESP8266 */ MEMORY { /* Data RAM. Allows byte access. */ DRAM (rw) : ORIGIN = 0x3FFE8000, LENGTH = 80K /* Instruction RAM. */ IRAM (x) : ORIGIN = 0x40100000, LENGTH = 32K } /* The entry point. It is set in the image flashed to the chip, so must be * defined. */ ENTRY(main) SECTIONS { /* Mutable global variables. */ .data : ALIGN(4) { _sdata = ABSOLUTE(.); *(.data) *(.data.*) } >DRAM /* Constant global variables. * Note that they still need to be loaded in RAM because the ESP8266 doesn't * allow byte access to flash. */ .rodata : ALIGN(4) { *(.rodata) *(.rodata.*) } >DRAM /* Global variables that are mutable and zero-initialized. */ .bss (NOLOAD) : ALIGN(4) { . = ALIGN (4); _sbss = ABSOLUTE(.); *(.bss) *(.bss.*) . = ALIGN (4); _ebss = ABSOLUTE(.); } >DRAM /* Constant literals and code. Loaded into IRAM for now. Eventually, most * code should be executed directly from flash. * Note that literals must be before code for the l32r instruction to work. */ .text : ALIGN(4) { *(.literal .text) *(.literal.* .text.*) } >IRAM } _globals_start = _sdata; _globals_end = _ebss; _heap_start = _ebss; _heap_end = ORIGIN(DRAM) + LENGTH(DRAM); /* It appears that the stack is set to 0x3ffffff0 when main is called. * Be conservative and scan all the way up to the end of the RAM. */ _stack_top = 0x40000000; /* Functions normally provided by a libc. * Source: * https://github.com/espressif/ESP8266_NONOS_SDK/blob/master/ld/eagle.rom.addr.v6.ld */ memcpy = 0x4000df48; memmove = 0x4000e04c; memset = 0x4000e190; /* Compiler runtime functions provided by the ROM. * Source: * https://github.com/espressif/ESP8266_NONOS_SDK/blob/master/ld/eagle.rom.addr.v6.ld */ __adddf3 = 0x4000c538; __addsf3 = 0x4000c180; __divdf3 = 0x4000cb94; __divdi3 = 0x4000ce60; __divsi3 = 0x4000dc88; __extendsfdf2 = 0x4000cdfc; __fixdfsi = 0x4000ccb8; __fixunsdfsi = 0x4000cd00; __fixunssfsi = 0x4000c4c4; __floatsidf = 0x4000e2f0; __floatsisf = 0x4000e2ac; __floatunsidf = 0x4000e2e8; __floatunsisf = 0x4000e2a4; __muldf3 = 0x4000c8f0; __muldi3 = 0x40000650; __mulsf3 = 0x4000c3dc; __subdf3 = 0x4000c688; __subsf3 = 0x4000c268; __truncdfsf2 = 0x4000cd5c; __udivdi3 = 0x4000d310; __udivsi3 = 0x4000e21c; __umoddi3 = 0x4000d770; __umodsi3 = 0x4000e268; __umulsidi3 = 0x4000dcf0; /* Proprietary ROM function needed for proper clock configuration. */ rom_i2c_writeReg = 0x400072d8; ================================================ FILE: targets/fe310.json ================================================ { "inherits": ["riscv32"], "cpu": "sifive-e31", "features": "+32bit,+a,+c,+m,+zaamo,+zalrsc,+zmmul,-b,-d,-e,-experimental-sdext,-experimental-sdtrig,-experimental-smctr,-experimental-ssctr,-experimental-svukte,-experimental-xqcia,-experimental-xqciac,-experimental-xqcicli,-experimental-xqcicm,-experimental-xqcics,-experimental-xqcicsr,-experimental-xqciint,-experimental-xqcilo,-experimental-xqcilsm,-experimental-xqcisls,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-experimental-zvbc32e,-experimental-zvkgs,-f,-h,-relax,-sha,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smdbltrp,-smepmp,-smmpm,-smnpm,-smrnmi,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssdbltrp,-ssnpm,-sspm,-ssqosid,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-supm,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-svvptc,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xesppie,-xmipscmove,-xmipslsp,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zabha,-zacas,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", "build-tags": ["fe310", "sifive"] } ================================================ FILE: targets/feather-m0-express.json ================================================ { "inherits": ["atsamd21g18a"], "build-tags": ["feather_m0_express"], "serial": "usb", "serial-port": ["239a:801b"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": ["FEATHERBOOT"], "msd-firmware-name": "firmware.uf2" } ================================================ FILE: targets/feather-m0.json ================================================ { "inherits": ["atsamd21g18a"], "build-tags": ["feather_m0"], "serial": "usb", "serial-port": ["239a:801b", "239a:800b"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": ["FEATHERBOOT"], "msd-firmware-name": "firmware.uf2" } ================================================ FILE: targets/feather-m4-can.json ================================================ { "inherits": ["atsame51j19a"], "build-tags": ["feather_m4_can"], "serial": "usb", "serial-port": ["239a:80cd"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": ["FTHRCANBOOT"], "msd-firmware-name": "firmware.uf2" } ================================================ FILE: targets/feather-m4.json ================================================ { "inherits": ["atsamd51j19a"], "build-tags": ["feather_m4"], "serial": "usb", "serial-port": ["239a:8022"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": ["FEATHERBOOT"], "msd-firmware-name": "firmware.uf2" } ================================================ FILE: targets/feather-nrf52840-sense.json ================================================ { "inherits": ["nrf52840", "nrf52840-s140v6-uf2"], "build-tags": ["feather_nrf52840_sense"], "serial-port": ["239a:8087", "239a:8088"], "msd-volume-name": ["FTHRSNSBOOT"] } ================================================ FILE: targets/feather-nrf52840.json ================================================ { "inherits": ["nrf52840", "nrf52840-s140v6-uf2"], "build-tags": ["feather_nrf52840"], "serial-port": ["239a:8029", "239a:802a"], "msd-volume-name": ["FTHR840BOOT"] } ================================================ FILE: targets/feather-rp2040-boot-stage2.S ================================================ // Adafruit Feather RP2040 Stage 2 Bootloader // // This file defines the parameters specific to the flash-chip found // on the Adafruit Feather RP2040. The generic implementation is in // rp2040-boot-stage2.S // #define BOARD_PICO_FLASH_SPI_CLKDIV 4 #define BOARD_CMD_READ 0xe7 #define BOARD_QUAD_OK 1 #define BOARD_QUAD_ENABLE_STATUS_BYTE 2 #define BOARD_QUAD_ENABLE_BIT_MASK 2 #define BOARD_SPLIT_STATUS_WRITE 1 #define BOARD_WAIT_CYCLES 2 #include "rp2040-boot-stage2.S" ================================================ FILE: targets/feather-rp2040.json ================================================ { "inherits": [ "rp2040" ], "serial-port": ["239a:80f1"], "default-stack-size": 8192, "build-tags": ["feather_rp2040"], "ldflags": [ "--defsym=__flash_size=8192K" ], "extra-files": [ "targets/feather-rp2040-boot-stage2.S" ] } ================================================ FILE: targets/feather-stm32f405.json ================================================ { "inherits": ["cortex-m4"], "build-tags": ["feather_stm32f405", "stm32f405", "stm32f4", "stm32"], "serial": "uart", "automatic-stack-size": false, "linkerscript": "targets/stm32f405.ld", "extra-files": [ "src/device/stm32/stm32f405.s" ], "flash-method": "command", "flash-command": "dfu-util --alt 0 --dfuse-address 0x08000000 --download {bin}", "openocd-transport": "swd", "openocd-interface": "jlink", "openocd-target": "stm32f4x" } ================================================ FILE: targets/gameboy-advance.json ================================================ { "llvm-target": "armv4t-unknown-unknown-eabi", "cpu": "arm7tdmi", "features": "+armv4t,+strict-align,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp", "build-tags": ["gameboyadvance", "arm7tdmi", "baremetal", "linux", "arm"], "goos": "linux", "goarch": "arm", "linker": "ld.lld", "rtlib": "compiler-rt", "libc": "picolibc", "cflags": [ "-Werror", "-fshort-enums", "-fomit-frame-pointer", "-fno-exceptions", "-fno-unwind-tables", "-fno-asynchronous-unwind-tables", "-ffunction-sections", "-fdata-sections" ], "ldflags": [ "--gc-sections" ], "linkerscript": "targets/gameboy-advance.ld", "extra-files": [ "targets/gameboy-advance.s", "src/runtime/asm_arm.S" ], "gdb": ["gdb-multiarch"], "emulator": "mgba -3 {}" } ================================================ FILE: targets/gameboy-advance.ld ================================================ OUTPUT_ARCH(arm) ENTRY(_start) /* Note: iwram is reduced by 96 bytes because the last part of that RAM * (starting at 0x03007FA0) is used for interrupt handling. */ MEMORY { ewram : ORIGIN = 0x02000000, LENGTH = 256K /* on-board work RAM (2 wait states) */ iwram : ORIGIN = 0x03000000, LENGTH = 32K-96 /* in-chip work RAM (faster) */ rom : ORIGIN = 0x08000000, LENGTH = 32M /* flash ROM */ } __stack_size_irq = 1K; __stack_size_usr = 2K; SECTIONS { .text : { KEEP (*(.init)) *(.text) *(.text.*) . = ALIGN(4); } >rom .rodata : { . = ALIGN(4); *(.rodata) *(.rodata.*) . = ALIGN(4); } >rom /* Put the stack at the bottom of RAM, so that the application will * crash on stack overflow instead of silently corrupting memory. * See: http://blog.japaric.io/stack-overflow-protection/ */ .stack (NOLOAD) : { . = ALIGN(4); _stack_top_irq = .; . += __stack_size_irq; _stack_top = .; . += __stack_size_usr; } >iwram /* Start address (in flash) of .data, used by startup code. */ _sidata = LOADADDR(.data); /* Globals with initial value */ .data : { . = ALIGN(4); _sdata = .; /* used by startup code */ *(.data) *(.data.*) *(.iwram .iwram.*) . = ALIGN(4); _edata = .; /* used by startup code */ } >iwram AT>rom /* Zero-initialized globals */ .bss : { . = ALIGN(4); _sbss = .; /* used by startup code */ *(.bss) *(.bss.*) *(COMMON) . = ALIGN(4); _ebss = .; /* used by startup code */ } >iwram /DISCARD/ : { *(.ARM.exidx) /* causes 'no memory region specified' error in lld */ *(.ARM.exidx.*) /* causes spurious 'undefined reference' errors */ } } /* For the memory allocator. */ _heap_start = ORIGIN(ewram); _heap_end = ORIGIN(ewram) + LENGTH(ewram); _globals_start = _sdata; _globals_end = _ebss; ================================================ FILE: targets/gameboy-advance.s ================================================ .section .init .global _start .align .arm _start: b start_vector // ROM header .byte 0x24,0xff,0xae,0x51,0x69,0x9a,0xa2,0x21,0x3d,0x84,0x82,0x0a,0x84,0xe4,0x09,0xad .byte 0x11,0x24,0x8b,0x98,0xc0,0x81,0x7f,0x21,0xa3,0x52,0xbe,0x19,0x93,0x09,0xce,0x20 .byte 0x10,0x46,0x4a,0x4a,0xf8,0x27,0x31,0xec,0x58,0xc7,0xe8,0x33,0x82,0xe3,0xce,0xbf .byte 0x85,0xf4,0xdf,0x94,0xce,0x4b,0x09,0xc1,0x94,0x56,0x8a,0xc0,0x13,0x72,0xa7,0xfc .byte 0x9f,0x84,0x4d,0x73,0xa3,0xca,0x9a,0x61,0x58,0x97,0xa3,0x27,0xfc,0x03,0x98,0x76 .byte 0x23,0x1d,0xc7,0x61,0x03,0x04,0xae,0x56,0xbf,0x38,0x84,0x00,0x40,0xa7,0x0e,0xfd .byte 0xff,0x52,0xfe,0x03,0x6f,0x95,0x30,0xf1,0x97,0xfb,0xc0,0x85,0x60,0xd6,0x80,0x25 .byte 0xa9,0x63,0xbe,0x03,0x01,0x4e,0x38,0xe2,0xf9,0xa2,0x34,0xff,0xbb,0x3e,0x03,0x44 .byte 0x78,0x00,0x90,0xcb,0x88,0x11,0x3a,0x94,0x65,0xc0,0x7c,0x63,0x87,0xf0,0x3c,0xaf .byte 0xd6,0x25,0xe4,0x8b,0x38,0x0a,0xac,0x72,0x21,0xd4,0xf8,0x07 // Game title .byte 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 // Game code .byte 0x00,0x00,0x00,0x00 // Maker code .byte 0x00,0x00 // Fixed value .byte 0x96 // Main unit code .byte 0x00 // Device type (0x00 retail, 0x80 debug) .byte 0x00 // Reserved .byte 0x00,0x00,0x00,0x00,0x00,0x00,0x00 // Software version .byte 0x00 // Complement check .byte 0x51 // Reserved area .space 98 start_vector: // Configure stacks mov r0, #0x12 // Switch to IRQ Mode msr cpsr, r0 ldr sp, =_stack_top_irq // Set IRQ stack mov r0, #0x1f // Switch to System Mode msr cpsr, r0 ldr sp, =_stack_top // Set user stack // Configure interrupt handler mov r0, #0x4000000 // REG_BASE ldr r1, =handleInterruptARM str r1, [r0, #-4] // actually storing to 0x03007FFC due to mirroring // Enable interrupts mov r1, #1 str r1, [r0, #0x208] // 0x04000208 Interrupt Master Enable // Jump to user code (switching to Thumb mode) ldr r3, =main bx r3 // Small interrupt handler that immediately jumps to a function defined in the // program (in Thumb) for further processing. handleInterruptARM: ldr r0, =handleInterrupt bx r0 ================================================ FILE: targets/gemma-m0.json ================================================ { "inherits": ["atsamd21e18a"], "build-tags": ["gemma_m0"], "serial": "usb", "serial-port": ["239a:801e"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": ["GEMMABOOT"], "msd-firmware-name": "firmware.uf2" } ================================================ FILE: targets/gnse.json ================================================ { "inherits": [ "stm32wl5x_cm4" ], "build-tags": [ "gnse" ], "serial": "uart", "flash-method": "openocd", "openocd-interface": "stlink", "openocd-target": "stm32wlx" } ================================================ FILE: targets/gobadge.json ================================================ { "inherits": ["pybadge"] } ================================================ FILE: targets/gopher-arcade.json ================================================ { "inherits": ["attiny85"], "build-tags": ["gopher_arcade"], "ldflags": [ "--defsym=_bootloader_size=2180", "--defsym=_stack_size=128" ], "flash-command": "avrdude -c usbasp -p t85 -B 10 -U flash:w:{hex}:i", "emulator": "simavr -m attiny85 -f 16000000 {}" } ================================================ FILE: targets/gopher-badge.json ================================================ { "inherits": [ "rp2040" ], "serial-port": ["2e8a:0003"], "default-stack-size": 8192, "build-tags": ["gopher_badge"], "ldflags": [ "--defsym=__flash_size=8M" ], "extra-files": [ "targets/pico-boot-stage2.S" ] } ================================================ FILE: targets/gopherbot.json ================================================ { "inherits": ["circuitplay-express"] } ================================================ FILE: targets/gopherbot2.json ================================================ { "inherits": ["circuitplay-bluefruit"] } ================================================ FILE: targets/grandcentral-m4.json ================================================ { "inherits": ["atsamd51p20a"], "build-tags": ["grandcentral_m4"], "serial": "usb", "serial-port": ["239a:8031"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": ["GCM4BOOT"], "msd-firmware-name": "firmware.uf2", "openocd-interface": "jlink" } ================================================ FILE: targets/hifive1b.json ================================================ { "inherits": ["fe310"], "build-tags": ["hifive1b"], "serial": "uart", "linkerscript": "targets/hifive1b.ld", "flash-method": "msd", "msd-volume-name": ["HiFive"], "msd-firmware-name": "firmware.hex", "jlink-device": "fe310" } ================================================ FILE: targets/hifive1b.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x20010000, LENGTH = 0x6a120 RAM (xrw) : ORIGIN = 0x80000000, LENGTH = 0x4000 } _stack_size = 2K; INCLUDE "targets/riscv.ld" ================================================ FILE: targets/hw-651-s110v8.json ================================================ { "inherits": ["hw-651", "nrf51-s110v8"] } ================================================ FILE: targets/hw-651.json ================================================ { "inherits": ["nrf51"], "build-tags": ["hw_651"], "serial": "uart", "flash-method": "openocd", "openocd-interface": "cmsis-dap" } ================================================ FILE: targets/itsybitsy-m0.json ================================================ { "inherits": ["atsamd21g18a"], "build-tags": ["itsybitsy_m0"], "serial": "usb", "serial-port": ["239a:800f", "239a:8012"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": ["ITSYBOOT"], "msd-firmware-name": "firmware.uf2" } ================================================ FILE: targets/itsybitsy-m4.json ================================================ { "inherits": ["atsamd51g19a"], "build-tags": ["itsybitsy_m4"], "serial": "usb", "flash-1200-bps-reset": "true", "flash-method": "msd", "serial-port": ["239a:802b"], "msd-volume-name": ["ITSYM4BOOT"], "msd-firmware-name": "firmware.uf2" } ================================================ FILE: targets/itsybitsy-nrf52840.json ================================================ { "inherits": ["nrf52840", "nrf52840-s140v6-uf2"], "build-tags": ["itsybitsy_nrf52840"], "serial-port": ["239A:8052", "239A:8051"], "msd-volume-name": ["ITSY840BOOT"] } ================================================ FILE: targets/k210.json ================================================ { "inherits": ["riscv64"], "features": "+64bit,+a,+c,+d,+f,+m,+zaamo,+zalrsc,+zicsr,+zifencei,+zmmul,-b,-e,-experimental-sdext,-experimental-sdtrig,-experimental-smctr,-experimental-ssctr,-experimental-svukte,-experimental-xqcia,-experimental-xqciac,-experimental-xqcicli,-experimental-xqcicm,-experimental-xqcics,-experimental-xqcicsr,-experimental-xqciint,-experimental-xqcilo,-experimental-xqcilsm,-experimental-xqcisls,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-experimental-zvbc32e,-experimental-zvkgs,-h,-relax,-sha,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smdbltrp,-smepmp,-smmpm,-smnpm,-smrnmi,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssdbltrp,-ssnpm,-sspm,-ssqosid,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-supm,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-svvptc,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xesppie,-xmipscmove,-xmipslsp,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zabha,-zacas,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", "build-tags": ["k210", "kendryte"], "code-model": "medium" } ================================================ FILE: targets/kb2040.json ================================================ { "inherits": [ "rp2040" ], "serial-port": ["239a:8106"], "build-tags": ["kb2040"], "ldflags": [ "--defsym=__flash_size=8192K" ], "extra-files": [ "targets/qtpy-rp2040-boot-stage2.S" ] } ================================================ FILE: targets/lgt92.json ================================================ { "inherits": [ "stm32l0x2" ], "build-tags": [ "lgt92" ], "serial": "uart", "linkerscript": "targets/stm32l072czt6.ld", "flash-method": "openocd", "openocd-interface": "stlink", "openocd-target": "stm32l0" } ================================================ FILE: targets/lm3s6965.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x00000000, LENGTH = 256K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K } _stack_size = 4K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/lorae5.json ================================================ { "inherits": [ "stm32wle5" ], "build-tags": [ "lorae5" ], "serial": "uart", "flash-method": "openocd", "openocd-interface": "stlink-v2", "openocd-target": "stm32wlx" } ================================================ FILE: targets/m5paper.json ================================================ { "inherits": ["esp32"], "build-tags": ["m5paper"], "serial-port": ["1a86:55d4"] } ================================================ FILE: targets/m5stack-core2.json ================================================ { "inherits": ["esp32"], "build-tags": ["m5stack_core2"], "serial-port": ["10c4:ea60"] } ================================================ FILE: targets/m5stack.json ================================================ { "inherits": ["esp32"], "build-tags": ["m5stack"], "serial-port": ["10c4:ea60"] } ================================================ FILE: targets/m5stamp-c3.json ================================================ { "inherits": ["esp32c3"], "build-tags": ["m5stamp_c3"], "serial": "uart", "serial-port": ["1a86:55d4"] } ================================================ FILE: targets/m5stick-c.json ================================================ { "inherits": ["esp32"], "build-tags": ["m5stick_c"], "serial-port": ["0403:6001"] } ================================================ FILE: targets/macropad-rp2040-boot-stage2.S ================================================ // Adafruit MacroPad RP2040 Stage 2 Bootloader // // This file defines the parameters specific to the flash-chip found // on the Adafruit MacroPad RP2040. The generic implementation is in // rp2040-boot-stage2.S // #define BOARD_PICO_FLASH_SPI_CLKDIV 4 #define BOARD_CMD_READ 0xeb #define BOARD_QUAD_OK 1 #define BOARD_QUAD_ENABLE_STATUS_BYTE 2 #define BOARD_QUAD_ENABLE_BIT_MASK 2 #define BOARD_SPLIT_STATUS_WRITE 0 #define BOARD_WAIT_CYCLES 4 #include "rp2040-boot-stage2.S" ================================================ FILE: targets/macropad-rp2040.json ================================================ { "inherits": [ "rp2040" ], "build-tags": ["macropad_rp2040"], "default-stack-size": 8192, "serial-port": ["239a:8107"], "ldflags": [ "--defsym=__flash_size=8M" ], "extra-files": [ "targets/macropad-rp2040-boot-stage2.S" ] } ================================================ FILE: targets/maixbit.json ================================================ { "inherits": ["k210"], "build-tags": ["maixbit"], "serial": "uart", "linkerscript": "targets/maixbit.ld", "flash-command": "kflash -p {port} --noansi --verbose {bin}" } ================================================ FILE: targets/maixbit.ld ================================================ MEMORY { RAM (xrw) : ORIGIN = 0x80000000, LENGTH = 6M } _stack_size = 2K; SECTIONS { .text : { . = ALIGN(16); KEEP(*(.init)) . = ALIGN(16); *(.text.handleInterruptASM) *(.text) *(.text.*) *(.rodata) *(.rodata.*) . = ALIGN(16); } >RAM /* Start address (in flash) of .data, used by startup code. */ _sidata = LOADADDR(.data); /* Globals with initial value */ .data : { . = ALIGN(16); /* see https://gnu-mcu-eclipse.github.io/arch/riscv/programmer/#the-gp-global-pointer-register */ PROVIDE( __global_pointer$ = . + (4K / 2) ); _sdata = .; /* used by startup code */ *(.sdata) *(.data .data.*) . = ALIGN(16); _edata = .; /* used by startup code */ } >RAM /* Zero-initialized globals */ .bss : { . = ALIGN(16); _sbss = .; /* used by startup code */ *(.sbss) *(.bss .bss.*) *(COMMON) . = ALIGN(16); _ebss = .; /* used by startup code */ } >RAM /DISCARD/ : { *(.eh_frame) /* causes 'no memory region specified' error in lld */ } } PROVIDE(_stack_top = ORIGIN(RAM) + LENGTH(RAM)); /* For the memory allocator. */ _heap_start = _ebss; _heap_end = ORIGIN(RAM) + LENGTH(RAM) - _stack_size; _globals_start = _sdata; _globals_end = _ebss; ================================================ FILE: targets/makerfabs-esp32c3spi35.json ================================================ { "inherits": ["esp32c3"], "build-tags": ["makerfabs_esp32c3spi35"] } ================================================ FILE: targets/matrixportal-m4.json ================================================ { "inherits": ["atsamd51j19a"], "build-tags": ["matrixportal_m4", "ninafw"], "serial": "usb", "serial-port": ["239a:80c9", "239a:80ca"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": ["MATRIXBOOT"], "msd-firmware-name": "firmware.uf2", "openocd-transport": "swd", "openocd-interface": "jlink", "openocd-target": "atsame5x" } ================================================ FILE: targets/mch2022.json ================================================ { "inherits": ["esp32"], "build-tags": ["mch2022"] } ================================================ FILE: targets/mdbt50qrx-uf2.json ================================================ { "inherits": ["nrf52840", "nrf52840-s140v6-uf2"], "build-tags": ["mdbt50qrx"], "serial-port": ["239a:810b", "239a:810c"], "msd-volume-name": ["MDBT50QBOOT"] } ================================================ FILE: targets/metro-m4-airlift.json ================================================ { "inherits": ["atsamd51j19a"], "build-tags": ["metro_m4_airlift", "ninafw"], "serial": "usb", "serial-port": ["239A:8037"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": ["METROM4BOOT"], "msd-firmware-name": "firmware.uf2" } ================================================ FILE: targets/metro-rp2350.json ================================================ { "inherits": [ "rp2350b" ], "build-tags": ["metro_rp2350"], "serial-port": ["239a:814e"], "default-stack-size": 8192, "ldflags": [ "--defsym=__flash_size=16M" ] } ================================================ FILE: targets/microbit-s110v8.json ================================================ { "inherits": ["microbit", "nrf51-s110v8"], "flash-method": "openocd" } ================================================ FILE: targets/microbit-v2-s113v7.json ================================================ { "inherits": ["microbit-v2", "nrf52833-s113v7"], "flash-method": "openocd" } ================================================ FILE: targets/microbit-v2-s140v7.json ================================================ { "inherits": ["microbit-v2", "nrf52833-s140v7"], "flash-method": "openocd" } ================================================ FILE: targets/microbit-v2.json ================================================ { "inherits": ["nrf52833"], "build-tags": ["microbit_v2"], "serial": "uart", "flash-method": "msd", "openocd-interface": "cmsis-dap", "msd-volume-name": ["MICROBIT"], "msd-firmware-name": "firmware.hex" } ================================================ FILE: targets/microbit.json ================================================ { "inherits": ["nrf51"], "build-tags": ["microbit"], "serial": "uart", "flash-method": "msd", "openocd-interface": "cmsis-dap", "msd-volume-name": ["MICROBIT"], "msd-firmware-name": "firmware.hex" } ================================================ FILE: targets/mimxrt1062-teensy40.ld ================================================ MEMORY { ITCM (rwx): ORIGIN = 0x00000000, LENGTH = 0x00080000 /* 512 Kib */ DTCM (rwx): ORIGIN = 0x20000000, LENGTH = 0x00080000 /* 512 Kib */ RAM (rwx): ORIGIN = 0x20200000, LENGTH = 0x00080000 /* 512 Kib */ FLASH (rx): ORIGIN = 0x60000000, LENGTH = 0x001FFFF0 /* 1984 Kib */ } ENTRY(Reset_Handler); _stack_size = 4K; SECTIONS { .text : ALIGN(8) { FILL(0xFFFFFFFF); /* place flash config at beginning of flash device */ KEEP(*(.flash_config)); /* IVT must be located at +4 Kbyte offset from base address of flash. */ . = ORIGIN(FLASH) + 0x1000; KEEP(*(.ivt)); . = ORIGIN(FLASH) + 0x1020; KEEP(*(.boot_data)); . = ORIGIN(FLASH) + 0x2000; _svectors = ABSOLUTE(.); KEEP(*(.isr_vector)); . = ALIGN(8); *(.text.Reset_Handler); . = ALIGN(8); _stext = .; *(.text*); *(.rodata* .constdata*); . = ALIGN(8); _etext = .; } > FLASH .tinygo_stacksizes : ALIGN(8) { *(.tinygo_stacksizes); . = ALIGN(8); } > FLASH .text.padding (NOLOAD) : { . = ALIGN(32768); } > ITCM .stack (NOLOAD) : { . = ALIGN(8); . += _stack_size; _stack_top = .; } > DTCM .data : ALIGN(8) { FILL(0xFFFFFFFF); _sdata = .; *(.data*); . = ALIGN(8); _edata = .; } > DTCM AT > FLASH .bss : ALIGN(8) { _sbss = .; *(.bss*); *(COMMON); . = ALIGN(8); _ebss = .; } > DTCM AT > DTCM /DISCARD/ : { *(.ARM.exidx*); /* causes spurious 'undefined reference' errors */ } _sidata = LOADADDR(.data); _heap_start = ORIGIN(RAM); _heap_end = ORIGIN(RAM) + LENGTH(RAM); _globals_start = _sdata; _globals_end = _ebss; _image_size = SIZEOF(.text) + SIZEOF(.tinygo_stacksizes) + SIZEOF(.data); /* TODO: link .text to ITCM */ _itcm_blocks = (0 + 0x7FFF) >> 15; _flexram_cfg = 0xAAAAAAAA | ((1 << (_itcm_blocks * 2)) - 1); } ================================================ FILE: targets/mksnanov3.json ================================================ { "inherits": ["cortex-m4"], "build-tags": ["mksnanov3", "stm32f407", "stm32f4", "stm32"], "serial": "uart", "linkerscript": "targets/stm32f407.ld", "extra-files": [ "src/device/stm32/stm32f407.s" ], "flash-method": "openocd", "openocd-interface": "stlink", "openocd-target": "stm32f4x", "openocd-commands": ["stm32f4x.cpu configure -event reset-init { adapter speed 1800 }"] } ================================================ FILE: targets/nano-33-ble-s140v6-uf2.json ================================================ { "inherits": ["nrf52840", "nrf52840-s140v6-uf2"], "build-tags": ["nano_33_ble"], "serial-port": ["239a:8063", "239a:0063"], "msd-volume-name": ["NANO33BOOT"] } ================================================ FILE: targets/nano-33-ble-s140v7-uf2.json ================================================ { "inherits": ["nrf52840", "nrf52840-s140v7-uf2"], "build-tags": ["nano_33_ble"], "serial-port": ["239a:8063", "239a:0063"], "msd-volume-name": ["NANO33BOOT"] } ================================================ FILE: targets/nano-33-ble-s140v7.json ================================================ { "inherits": ["nano-33-ble", "nrf52840-s140v7"], "flash-command": "nrfjprog -f nrf52 --sectorerase --program {hex} --reset", "flash-1200-bps-reset": "false", "openocd-interface": "jlink" } ================================================ FILE: targets/nano-33-ble.json ================================================ { "inherits": ["nrf52840"], "build-tags": ["nano_33_ble", "nrf52840_reset_bossa"], "flash-command": "bossac_arduino2 -d -i -e -w -v -R --port={port} {bin}", "serial-port": ["2341:805a", "2341:005a"], "serial": "usb", "flash-1200-bps-reset": "true", "linkerscript": "targets/nano-33-ble.ld" } ================================================ FILE: targets/nano-33-ble.ld ================================================ /* See also https://github.com/arduino/ArduinoCore-mbed/blob/master/variants/ARDUINO_NANO33BLE/linker_script.ld */ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x10000, LENGTH = 0xf0000 RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x40000 } _stack_size = 4K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/nano-rp2040.json ================================================ { "inherits": [ "rp2040" ], "serial-port": ["2341:005e"], "default-stack-size": 8192, "build-tags": ["nano_rp2040", "ninafw", "ninafw_machine_init"], "ldflags": [ "--defsym=__flash_size=16M" ], "extra-files": [ "targets/pico-boot-stage2.S" ] } ================================================ FILE: targets/nicenano.json ================================================ { "inherits": ["nrf52840", "nrf52840-s140v6-uf2"], "build-tags": ["nicenano"], "msd-volume-name": ["NICENANO"] } ================================================ FILE: targets/nintendoswitch.json ================================================ { "llvm-target": "aarch64", "cpu": "cortex-a57", "features": "+aes,+crc,+fp-armv8,+neon,+perfmon,+sha2,+v8a,-fmv", "build-tags": ["nintendoswitch", "arm64"], "scheduler": "tasks", "goos": "linux", "goarch": "arm64", "linker": "ld.lld", "rtlib": "compiler-rt", "libc": "picolibc", "gc": "conservative", "relocation-model": "pic", "default-stack-size": 2048, "cflags": [ "-target", "aarch64-unknown-none", "-fPIE", "-Werror", "-fshort-enums", "-fomit-frame-pointer", "-fno-exceptions", "-fno-unwind-tables", "-fno-asynchronous-unwind-tables", "-ffunction-sections", "-fdata-sections" ], "ldflags": [ "-pie", "-z", "notext" ], "linkerscript": "targets/nintendoswitch.ld", "extra-files": [ "targets/nintendoswitch.s", "src/internal/task/task_stack_arm64.S", "src/runtime/asm_arm64.S", "src/runtime/runtime_nintendoswitch.S" ] } ================================================ FILE: targets/nintendoswitch.ld ================================================ PHDRS { text PT_LOAD FLAGS(5) /* Read | Execute */; rodata PT_LOAD FLAGS(4) /* Read */; data PT_LOAD FLAGS(6) /* Read | Write */; bss PT_LOAD FLAGS(6) /* Read | Write */; dynamic PT_DYNAMIC; } SECTIONS { /* Code and file header */ . = 0; .text : ALIGN(0x1000) { HIDDEN(__text_start = .); KEEP(*(.text.jmp)) . = 0x80; KEEP(*(.text.start)) *(.text .text.*) *(.plt .plt.*) HIDDEN(__text_end = .); HIDDEN(__text_size = . - __text_start); } /* Read-only sections */ . = ALIGN(0x1000); HIDDEN(__rodata_start = .); .rodata : { *(.rodata .rodata.*) } :rodata .mod0 : { KEEP(crt0.nso.o(.data.mod0)) KEEP(crt0.nro.o(.data.mod0)) KEEP(crt0.lib.nro.o(.data.mod0)) } .dynsym : { *(.dynsym) } :rodata .dynstr : { *(.dynstr) } :rodata .rela.dyn : { *(.rela.*) } :rodata HIDDEN(__rodata_end = .); HIDDEN(__rodata_size = . - __rodata_start); /* Read-write sections */ . = ALIGN(0x1000); .data : { HIDDEN(__data_start = .); *(.data .data.*) *(.got .got.*) *(.got.plt .got.plt.*) HIDDEN(__data_end = .); HIDDEN(__data_size = . - __data_start); } :data .dynamic : { HIDDEN(__dynamic_start = .); *(.dynamic) } /* BSS section */ . = ALIGN(0x1000); .bss : { HIDDEN(__bss_start = .); *(.bss .bss.*) *(COMMON) . = ALIGN(8); HIDDEN(__bss_end = .); HIDDEN(__bss_size = . - __bss_start); } : bss /DISCARD/ : { *(.eh_frame) /* This is probably unnecessary and bloats the binary. */ *(.eh_frame_hdr) } } ================================================ FILE: targets/nintendoswitch.s ================================================ // For more information on the .nro file format, see: // https://switchbrew.org/wiki/NRO .section .text.jmp, "x" .global _start _start: b start .word _mod_header - _start .ascii "HOMEBREW" .ascii "NRO0" // magic .word 0 // version (always 0) .word __bss_start - _start // total NRO file size .word 0 // flags (unused) // segment headers .word __text_start - _start .word __text_size .word __rodata_start - _start .word __rodata_size .word __data_start - _start .word __data_size .word __bss_size .word 0 // ModuleId (not supported) . = 0x50; // skip 32 bytes .word 0 // DSO Module Offset (unused) .word 0 // reserved (unused) .section .data.mod0 .word 0, 8 .global _mod_header _mod_header: .ascii "MOD0" .word __dynamic_start - _mod_header .word __bss_start - _mod_header .word __bss_end - _mod_header .word 0, 0 // eh_frame_hdr start/end .word 0 // runtime-generated module object offset .section .text.start, "x" .global start start: // save lr mov x7, x30 // get aslr base bl +4 sub x6, x30, #0x88 // context ptr and main thread handle mov x25, x0 mov x26, x1 // Save ASLR Base to use later mov x0, x6 adrp x4, _saved_return_address str x7, [x4, #:lo12:_saved_return_address] adrp x4, _context str x25, [x4, #:lo12:_context] adrp x4, _main_thread str x26, [x4, #:lo12:_main_thread] // store stack pointer mov x26, sp adrp x4, _stack_top str x26, [x4, #:lo12:_stack_top] // clear .bss adrp x5, __bss_start add x5, x5, #:lo12:__bss_start adrp x6, __bss_end add x6, x6, #:lo12:__bss_end bssloop: cmp x5, x6 b.eq run str xzr, [x5] add x5, x5, 8 b bssloop run: // process .dynamic section // ASLR base on x0 adrp x1, _DYNAMIC add x1, x1, #:lo12:_DYNAMIC bl __dynamic_loader // call entrypoint b main .global __nx_exit .type __nx_exit, %function __nx_exit: // Exit code in x0 // restore stack pointer mov sp, x1 // jump back to loader br x2 .section .data.horizon .align 8 .global _saved_return_address // Saved return address. // This might be different than null when coming from launcher _saved_return_address: .dword 0 .global _context // Homebrew Launcher Context // This might be different than null when not coming from launcher _context: .dword 0 .global _main_thread _main_thread: .dword 0 .global _stack_top _stack_top: .dword 0 ================================================ FILE: targets/nodemcu.json ================================================ { "inherits": ["esp8266"], "build-tags": ["nodemcu"], "serial": "uart" } ================================================ FILE: targets/nrf51-s110v8.json ================================================ { "build-tags": ["softdevice", "s110v8"], "linkerscript": "targets/nrf51-s110v8.ld" } ================================================ FILE: targets/nrf51-s110v8.ld ================================================ MEMORY { /* This SoftDevice requires 96K flash and 8K RAM according to the release * notes of version 8.0.0 */ FLASH_TEXT (rw) : ORIGIN = 0x00000000 + 96K, LENGTH = 256K - 96K RAM (xrw) : ORIGIN = 0x20000000 + 8K, LENGTH = 16K - 8K } _stack_size = 2K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/nrf51.json ================================================ { "inherits": ["cortex-m0"], "build-tags": ["nrf51822", "nrf51", "nrf"], "cflags": [ "-DNRF51", "-I{root}/lib/CMSIS/CMSIS/Include", "-I{root}/lib/nrfx/mdk" ], "linkerscript": "targets/nrf51.ld", "extra-files": [ "lib/nrfx/mdk/system_nrf51.c", "src/device/nrf/nrf51.s" ], "openocd-transport": "swd", "openocd-target": "nrf51" } ================================================ FILE: targets/nrf51.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x00000000, LENGTH = 256K /* .text */ RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 16K } _stack_size = 2K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/nrf52-s132v6.json ================================================ { "build-tags": ["softdevice", "s132v6"], "linkerscript": "targets/nrf52-s132v6.ld" } ================================================ FILE: targets/nrf52-s132v6.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x00000000 + 0x00026000 , LENGTH = 512K - 0x00026000 /* .text */ RAM (xrw) : ORIGIN = 0x20000000 + 0x000039c0, LENGTH = 64K - 0x000039c0 } _stack_size = 4K; /* This value is needed by the Nordic SoftDevice. */ __app_ram_base = ORIGIN(RAM); INCLUDE "targets/arm.ld" ================================================ FILE: targets/nrf52.json ================================================ { "inherits": ["cortex-m4"], "build-tags": ["nrf52", "nrf"], "cflags": [ "-DNRF52832_XXAA", "-I{root}/lib/CMSIS/CMSIS/Include", "-I{root}/lib/nrfx/mdk" ], "linkerscript": "targets/nrf52.ld", "extra-files": [ "lib/nrfx/mdk/system_nrf52.c", "src/device/nrf/nrf52.s" ], "openocd-transport": "swd", "openocd-target": "nrf51" } ================================================ FILE: targets/nrf52.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x00000000, LENGTH = 512K /* .text */ RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K } _stack_size = 2K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/nrf52833-s113v7.json ================================================ { "build-tags": ["softdevice", "s113v7"], "linkerscript": "targets/nrf52833-s113v7.ld", "ldflags": [ "--defsym=__softdevice_stack=0x700" ] } ================================================ FILE: targets/nrf52833-s113v7.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x00000000 + 0x1C000, LENGTH = 0x80000 - 0x1C000 RAM (xrw) : ORIGIN = 0x20000000 + 0x1e20, LENGTH = 0x20000 - 0x1e20 } _stack_size = 4K + __softdevice_stack; /* These values are needed for the Nordic SoftDevice. */ __app_ram_base = ORIGIN(RAM); __softdevice_stack = DEFINED(__softdevice_stack) ? __softdevice_stack : 0; INCLUDE "targets/arm.ld" ================================================ FILE: targets/nrf52833-s140v7.json ================================================ { "build-tags": ["softdevice", "s140v7"], "linkerscript": "targets/nrf52833-s140v7.ld", "ldflags": [ "--defsym=__softdevice_stack=0x700" ] } ================================================ FILE: targets/nrf52833-s140v7.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x00000000 + 0x00027000, LENGTH = 0x80000 - 0x00027000 RAM (xrw) : ORIGIN = 0x20000000 + 0x000039c0, LENGTH = 0x20000 - 0x000039c0 } _stack_size = 4K + __softdevice_stack; /* These values are needed for the Nordic SoftDevice. */ __app_ram_base = ORIGIN(RAM); __softdevice_stack = DEFINED(__softdevice_stack) ? __softdevice_stack : 0; INCLUDE "targets/arm.ld" ================================================ FILE: targets/nrf52833.json ================================================ { "inherits": ["cortex-m4"], "build-tags": ["nrf52833", "nrf"], "cflags": [ "-DNRF52833_XXAA", "-I{root}/lib/CMSIS/CMSIS/Include", "-I{root}/lib/nrfx/mdk" ], "linkerscript": "targets/nrf52833.ld", "extra-files": [ "lib/nrfx/mdk/system_nrf52833.c", "src/device/nrf/nrf52833.s" ], "openocd-transport": "swd", "openocd-target": "nrf52" } ================================================ FILE: targets/nrf52833.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x00000000, LENGTH = 0x80000 RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x20000 } _stack_size = 4K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/nrf52840-mdk-usb-dongle.json ================================================ { "inherits": ["nrf52840", "nrf52840-s140v6-uf2"], "build-tags": ["nrf52840_mdk_usb_dongle"], "msd-volume-name": ["MDK-DONGLE"] } ================================================ FILE: targets/nrf52840-mdk.json ================================================ { "inherits": ["nrf52840"], "build-tags": ["nrf52840_mdk"], "serial": "usb", "flash-method": "openocd", "openocd-interface": "cmsis-dap" } ================================================ FILE: targets/nrf52840-s140v6-uf2-generic.json ================================================ { "inherits": ["nrf52840", "nrf52840-s140v6-uf2"], "build-tags": ["nrf52840_generic"], "serial-port": ["1209:9090"] } ================================================ FILE: targets/nrf52840-s140v6-uf2.json ================================================ { "build-tags": ["nrf52840_reset_uf2", "softdevice", "s140v6"], "linkerscript": "targets/nrf52840-s140v6-uf2.ld", "serial": "usb", "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-firmware-name": "firmware.uf2", "binary-format": "uf2", "uf2-family-id": "0xADA52840" } ================================================ FILE: targets/nrf52840-s140v6-uf2.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x00000000+0x26000, LENGTH = 0xED000-0x26000 /* SoftDevice S140. See https://learn.adafruit.com/introducing-the-adafruit-nrf52840-feather/hathach-memory-map. Application starts at 0x26000; user data starts at 0xED000 */ RAM (xrw) : ORIGIN = 0x20004180, LENGTH = 0x20040000-0x20004180 } _stack_size = 2K; /* This value is needed by the Nordic SoftDevice. */ __app_ram_base = ORIGIN(RAM); INCLUDE "targets/arm.ld" ================================================ FILE: targets/nrf52840-s140v7-uf2.json ================================================ { "inherits": ["nrf52840-s140v7"], "build-tags": ["nrf52840_reset_uf2"], "linkerscript": "targets/nrf52840-s140v7-uf2.ld", "serial": "usb", "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-firmware-name": "firmware.uf2", "binary-format": "uf2", "uf2-family-id": "0xADA52840" } ================================================ FILE: targets/nrf52840-s140v7-uf2.ld ================================================ /* See also https://github.com/Seeed-Studio/ArduinoCore-mbed/blob/master/variants/SEEED_XIAO_NRF52840_SENSE/linker_script.ld */ MEMORY { FLASH_TEXT (rx) : ORIGIN = 0x27000, LENGTH = 0xED000 - 0x27000 RAM (rwx) : ORIGIN = 0x20006000, LENGTH = 0x3A000 } _stack_size = 4K + __softdevice_stack; /* This value is needed by the Nordic SoftDevice. */ __app_ram_base = ORIGIN(RAM); __softdevice_stack = DEFINED(__softdevice_stack) ? __softdevice_stack : 0; INCLUDE "targets/arm.ld" ================================================ FILE: targets/nrf52840-s140v7.json ================================================ { "build-tags": ["softdevice", "s140v7"], "linkerscript": "targets/nrf52840-s140v7.ld", "ldflags": [ "--defsym=__softdevice_stack=0x700" ] } ================================================ FILE: targets/nrf52840-s140v7.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x00000000 + 0x00027000, LENGTH = 1M - 0x00027000 RAM (xrw) : ORIGIN = 0x20000000 + 0x000039c0, LENGTH = 256K - 0x000039c0 } _stack_size = 4K + __softdevice_stack; /* This value is needed by the Nordic SoftDevice. */ __app_ram_base = ORIGIN(RAM); __softdevice_stack = DEFINED(__softdevice_stack) ? __softdevice_stack : 0; INCLUDE "targets/arm.ld" ================================================ FILE: targets/nrf52840.json ================================================ { "inherits": ["cortex-m4"], "build-tags": ["nrf52840", "nrf"], "cflags": [ "-DNRF52840_XXAA", "-I{root}/lib/CMSIS/CMSIS/Include", "-I{root}/lib/nrfx/mdk" ], "linkerscript": "targets/nrf52840.ld", "extra-files": [ "lib/nrfx/mdk/system_nrf52840.c", "src/device/nrf/nrf52840.s" ], "openocd-transport": "swd", "openocd-target": "nrf51" } ================================================ FILE: targets/nrf52840.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x00000000, LENGTH = 1M RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 256K } _stack_size = 4K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/nucleo-f103rb.json ================================================ { "inherits": ["cortex-m3"], "build-tags": ["nucleof103rb", "stm32f103", "stm32f1","stm32"], "serial": "uart", "linkerscript": "targets/stm32f103rb.ld", "extra-files": [ "src/device/stm32/stm32f103.s" ], "flash-method": "openocd", "openocd-interface": "stlink-v2-1", "openocd-target": "stm32f1x" } ================================================ FILE: targets/nucleo-f722ze.json ================================================ { "inherits": ["cortex-m7"], "build-tags": ["nucleof722ze", "stm32f7x2", "stm32f7", "stm32"], "serial": "uart", "linkerscript": "targets/stm32f7x2zetx.ld", "extra-files": [ "src/device/stm32/stm32f7x2.s" ], "flash-method": "openocd", "openocd-interface": "stlink-v2-1", "openocd-target": "stm32f7x" } ================================================ FILE: targets/nucleo-l031k6.json ================================================ { "inherits": ["cortex-m0"], "build-tags": ["nucleol031k6", "stm32l031", "stm32l0x1", "stm32l0", "stm32"], "serial": "uart", "linkerscript": "targets/stm32l031k6.ld", "extra-files": [ "src/device/stm32/stm32l0x1.s" ], "flash-method": "openocd", "openocd-interface": "stlink", "openocd-target": "stm32l0" } ================================================ FILE: targets/nucleo-l432kc.json ================================================ { "inherits": ["cortex-m4"], "build-tags": ["nucleol432kc", "stm32l432", "stm32l4x2", "stm32l4", "stm32"], "serial": "uart", "linkerscript": "targets/stm32l4x2.ld", "extra-files": [ "src/device/stm32/stm32l4x2.s" ], "flash-method": "openocd", "openocd-interface": "stlink-v2-1", "openocd-target": "stm32l4x" } ================================================ FILE: targets/nucleo-l476rg.json ================================================ { "inherits": ["cortex-m4"], "build-tags": ["nucleol476rg", "stm32l476", "stm32l4x6", "stm32l4", "stm32"], "serial": "uart", "linkerscript": "targets/stm32l4x6.ld", "extra-files": [ "src/device/stm32/stm32l4x6.s" ], "flash-method": "openocd", "openocd-interface": "stlink-v2-1", "openocd-target": "stm32l4x" } ================================================ FILE: targets/nucleo-l552ze.json ================================================ { "inherits": ["cortex-m33"], "build-tags": ["nucleol552ze", "stm32l552", "stm32l5x2", "stm32l5", "stm32"], "serial": "uart", "linkerscript": "targets/stm32l5x2xe.ld", "extra-files": [ "src/device/stm32/stm32l552.s" ], "flash-method": "openocd", "openocd-interface": "stlink", "openocd-target": "stm32l5x" } ================================================ FILE: targets/nucleo-wl55jc.json ================================================ { "inherits": [ "stm32wl5x_cm4" ], "build-tags": [ "nucleowl55jc" ], "serial": "uart", "flash-method": "openocd", "openocd-interface": "stlink", "openocd-target": "stm32wlx" } ================================================ FILE: targets/nxpmk66f18.ld ================================================ /* Unused, but here to silence a linker warning. */ ENTRY(Reset_Handler) /* define memory layout */ MEMORY { FLASH_TEXT (rx) : ORIGIN = 0x00000000, LENGTH = 1024K RAM (rwx) : ORIGIN = 0x1FFF0000, LENGTH = 256K } _stack_size = 2K; /* define output sections */ SECTIONS { /* Program code and read-only data goes to FLASH_TEXT. */ .text : { /* vector table MUST start at 0x0 */ . = 0; KEEP(*(.isr_vector)) /* flash configuration MUST be at 0x400 */ . = 0x400; KEEP(*(.flash_config)) /* everything else */ *(.text) *(.text.*) *(.rodata) *(.rodata.*) . = ALIGN(4); } >FLASH_TEXT = 0xFF } INCLUDE "targets/arm.ld" ================================================ FILE: targets/p1am-100.json ================================================ { "inherits": ["atsamd21g18a"], "build-tags": ["p1am_100"], "flash-command": "bossac -d -i -e -w -v -R --port={port} --offset=0x2000 {bin}", "flash-1200-bps-reset": "true" } ================================================ FILE: targets/particle-3rd-gen.json ================================================ { "inherits": ["nrf52840"], "build-tags": ["particle_3rd_gen"], "serial": "uart", "flash-method": "openocd", "openocd-interface": "cmsis-dap" } ================================================ FILE: targets/particle-argon.json ================================================ { "inherits": ["particle-3rd-gen"], "build-tags": ["particle_argon"] } ================================================ FILE: targets/particle-boron.json ================================================ { "inherits": ["particle-3rd-gen"], "build-tags": ["particle_boron"] } ================================================ FILE: targets/particle-xenon.json ================================================ { "inherits": ["particle-3rd-gen"], "build-tags": ["particle_xenon"] } ================================================ FILE: targets/pca10031.json ================================================ { "inherits": ["nrf51"], "build-tags": ["pca10031"], "serial": "uart", "flash-command": "nrfjprog -f nrf51 --sectorerase --program {hex} --reset", "openocd-interface": "cmsis-dap" } ================================================ FILE: targets/pca10040-s132v6.json ================================================ { "inherits": ["pca10040", "nrf52-s132v6"] } ================================================ FILE: targets/pca10040.json ================================================ { "inherits": ["nrf52"], "build-tags": ["pca10040"], "serial": "uart", "flash-method": "openocd", "flash-command": "nrfjprog -f nrf52 --sectorerase --program {hex} --reset", "openocd-interface": "jlink", "openocd-transport": "swd" } ================================================ FILE: targets/pca10056-s140v6-uf2.json ================================================ { "inherits": ["nrf52840", "nrf52840-s140v6-uf2"], "build-tags": ["pca10056"], "serial-port": ["239a:0x0029"], "msd-volume-name": ["NRF52BOOT"] } ================================================ FILE: targets/pca10056-s140v7.json ================================================ { "inherits": ["pca10056", "nrf52840-s140v7"] } ================================================ FILE: targets/pca10056.json ================================================ { "inherits": ["nrf52840"], "build-tags": ["pca10056"], "serial": "uart", "flash-method": "command", "flash-command": "nrfjprog -f nrf52 --sectorerase --program {hex} --reset", "msd-volume-name": ["JLINK"], "msd-firmware-name": "firmware.hex", "openocd-interface": "jlink", "openocd-transport": "swd" } ================================================ FILE: targets/pca10059-s140v7.json ================================================ { "inherits": ["pca10059", "nrf52840-s140v7"] } ================================================ FILE: targets/pca10059.json ================================================ { "inherits": ["nrf52840"], "build-tags": ["pca10059"], "serial": "usb", "linkerscript": "targets/pca10059.ld", "binary-format": "nrf-dfu", "flash-command": "nrfutil dfu usb-serial -pkg {zip} -p {port} -b 115200" } ================================================ FILE: targets/pca10059.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x00001000, LENGTH = 0xDF000 RAM (xrw) : ORIGIN = 0x20000008, LENGTH = 0x3FFF8 } _stack_size = 2K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/pga2350.json ================================================ { "inherits": ["rp2350b"], "build-tags": ["pga2350"], "ldflags": [ "--defsym=__flash_size=16M" ] } ================================================ FILE: targets/pico-boot-stage2.S ================================================ // Raspberry Pi Pico Stage 2 Bootloader // // This file defines the parameters specific to the flash-chip found // on the official Pico boards. The generic implementation is in // rp2040-boot-stage2.S // #define BOARD_PICO_FLASH_SPI_CLKDIV 2 #define BOARD_CMD_READ 0xeb #define BOARD_QUAD_OK 1 #define BOARD_QUAD_ENABLE_STATUS_BYTE 2 #define BOARD_QUAD_ENABLE_BIT_MASK 2 #define BOARD_SPLIT_STATUS_WRITE 0 #define BOARD_WAIT_CYCLES 4 #include "rp2040-boot-stage2.S" ================================================ FILE: targets/pico-plus2.json ================================================ { "inherits": [ "rp2350b" ], "build-tags": ["pico_plus2"], "ldflags": [ "--defsym=__flash_size=16M" ], "serial-port": ["2e8a:000F"], "default-stack-size": 8192 } ================================================ FILE: targets/pico-w.json ================================================ { "inherits": ["pico"], "build-tags": ["pico-w", "cyw43439"] } ================================================ FILE: targets/pico.json ================================================ { "inherits": [ "rp2040" ], "build-tags": ["pico"], "serial-port": ["2e8a:000A"], "default-stack-size": 8192, "ldflags": [ "--defsym=__flash_size=2048K" ], "extra-files": [ "targets/pico-boot-stage2.S" ] } ================================================ FILE: targets/pico2-ice.json ================================================ { "inherits": [ "rp2350b" ], "build-tags": ["pico2_ice"], "serial-port": ["2e8a:000A"], "default-stack-size": 8192, "ldflags": [ "--defsym=__flash_size=4M" ] } ================================================ FILE: targets/pico2-w.json ================================================ { "inherits": ["pico2"], "build-tags": ["pico2-w", "cyw43439"] } ================================================ FILE: targets/pico2.json ================================================ { "inherits": [ "rp2350" ], "build-tags": ["pico2"], "serial-port": ["2e8a:000A"], "default-stack-size": 8192, "ldflags": [ "--defsym=__flash_size=4M" ] } ================================================ FILE: targets/pinetime.json ================================================ { "inherits": ["nrf52"], "build-tags": ["pinetime"], "serial": "none", "flash-method": "openocd", "flash-command": "nrfjprog -f nrf52 --sectorerase --program {hex} --reset", "openocd-interface": "jlink", "openocd-transport": "swd" } ================================================ FILE: targets/pybadge.json ================================================ { "inherits": ["atsamd51j19a"], "build-tags": ["pybadge", "ninafw"], "serial": "usb", "flash-1200-bps-reset": "true", "flash-method": "msd", "serial-port": ["239a:8033"], "msd-volume-name": ["PYBADGEBOOT"], "msd-firmware-name": "arcade.uf2" } ================================================ FILE: targets/pygamer.json ================================================ { "inherits": ["atsamd51j19a"], "build-tags": ["pygamer"], "serial": "usb", "serial-port": ["239a:803d", "239a:803e"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": ["PYGAMERBOOT"], "msd-firmware-name": "arcade.uf2" } ================================================ FILE: targets/pyportal.json ================================================ { "inherits": ["atsamd51j20a"], "build-tags": ["pyportal", "ninafw", "ninafw_machine_init"], "serial": "usb", "flash-1200-bps-reset": "true", "flash-method": "msd", "serial-port": ["239a:8035", "239a:8036"], "msd-volume-name": ["PORTALBOOT"], "msd-firmware-name": "firmware.uf2" } ================================================ FILE: targets/qtpy-esp32c3.json ================================================ { "inherits": ["esp32c3"], "build-tags": ["qtpy_esp32c3"] } ================================================ FILE: targets/qtpy-rp2040-boot-stage2.S ================================================ // Adafruit QT Py RP2040 Stage 2 Bootloader // // This file defines the parameters specific to the flash-chip found // on the Adafruit QT Py RP2040. The generic implementation is in // rp2040-boot-stage2.S // #define BOARD_PICO_FLASH_SPI_CLKDIV 2 #define BOARD_CMD_READ 0xe7 #define BOARD_QUAD_OK 1 #define BOARD_QUAD_ENABLE_STATUS_BYTE 2 #define BOARD_QUAD_ENABLE_BIT_MASK 2 #define BOARD_SPLIT_STATUS_WRITE 1 #define BOARD_WAIT_CYCLES 2 #include "rp2040-boot-stage2.S" ================================================ FILE: targets/qtpy-rp2040.json ================================================ { "inherits": [ "rp2040" ], "serial-port": ["239a:80f7"], "default-stack-size": 8192, "build-tags": ["qtpy_rp2040"], "ldflags": [ "--defsym=__flash_size=8192K" ], "extra-files": [ "targets/qtpy-rp2040-boot-stage2.S" ] } ================================================ FILE: targets/qtpy.json ================================================ { "inherits": ["atsamd21e18a"], "build-tags": ["qtpy"], "serial": "usb", "serial-port": ["239a:80cb"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": ["QTPY_BOOT"], "msd-firmware-name": "firmware.uf2" } ================================================ FILE: targets/rak4631.json ================================================ { "inherits": ["nrf52840", "nrf52840-s140v6-uf2"], "build-tags": ["rak4631"], "serial-port": ["239a:8029"], "msd-volume-name": ["RAK4631"] } ================================================ FILE: targets/reelboard-s140v7.json ================================================ { "inherits": ["reelboard", "nrf52840-s140v7"], "flash-method": "openocd" } ================================================ FILE: targets/reelboard.json ================================================ { "inherits": ["nrf52840"], "build-tags": ["reelboard"], "serial": "uart", "flash-method": "msd", "msd-volume-name": ["reel-board"], "msd-firmware-name": "firmware.hex", "openocd-interface": "cmsis-dap" } ================================================ FILE: targets/riscv-qemu.json ================================================ { "inherits": [ "riscv32" ], "features": "+32bit,+a,+c,+m,+zaamo,+zalrsc,+zihintpause,+zmmul,-b,-d,-e,-experimental-sdext,-experimental-sdtrig,-experimental-smctr,-experimental-ssctr,-experimental-svukte,-experimental-xqcia,-experimental-xqciac,-experimental-xqcicli,-experimental-xqcicm,-experimental-xqcics,-experimental-xqcicsr,-experimental-xqciint,-experimental-xqcilo,-experimental-xqcilsm,-experimental-xqcisls,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-experimental-zvbc32e,-experimental-zvkgs,-f,-h,-relax,-sha,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smdbltrp,-smepmp,-smmpm,-smnpm,-smrnmi,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssdbltrp,-ssnpm,-sspm,-ssqosid,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-supm,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-svvptc,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xesppie,-xmipscmove,-xmipslsp,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zabha,-zacas,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", "build-tags": [ "virt", "qemu" ], "scheduler": "cores", "default-stack-size": 16384, "cflags": [ "-march=rv32imaczihintpause", "-DTINYGO_CORES=4" ], "ldflags": [ "--defsym=__num_stacks=4" ], "linkerscript": "targets/riscv-qemu.ld", "emulator": "qemu-system-riscv32 -machine virt,aclint=on -smp 4 -nographic -bios none -device virtio-rng-device -kernel {}" } ================================================ FILE: targets/riscv-qemu.ld ================================================ /* Memory map: * https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c * Looks like we can use any address starting from 0x80000000 (so 2GB of space). * However, using a large space slows down tests. */ MEMORY { RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 100M } REGION_ALIAS("FLASH_TEXT", RAM) _stack_size = 2K; INCLUDE "targets/riscv.ld" ================================================ FILE: targets/riscv.json ================================================ { "goos": "linux", "goarch": "arm", "build-tags": ["tinygo.riscv", "baremetal", "linux", "arm"], "gc": "conservative", "linker": "ld.lld", "rtlib": "compiler-rt", "libc": "picolibc", "cflags": [ "-Werror", "-mno-relax", "-fno-exceptions", "-fno-unwind-tables", "-fno-asynchronous-unwind-tables", "-ffunction-sections", "-fdata-sections" ], "ldflags": [ "--gc-sections" ], "extra-files": [ "src/device/riscv/start.S", "src/internal/task/task_stack_tinygoriscv.S", "src/runtime/asm_riscv.S", "src/device/riscv/handleinterrupt.S" ], "gdb": ["riscv64-unknown-elf-gdb"] } ================================================ FILE: targets/riscv.ld ================================================ SECTIONS { .text : { . = ALIGN(4); KEEP(*(.init)) . = ALIGN(4); *(.text.handleInterruptASM) *(.text) *(.text.*) *(.rodata) *(.rodata.*) . = ALIGN(4); } >FLASH_TEXT /* Put the stack at the bottom of RAM, so that the application will * crash on stack overflow instead of silently corrupting memory. * See: http://blog.japaric.io/stack-overflow-protection/ */ .stack0 (NOLOAD) : { . = ALIGN(16); . += _stack_size; _stack_top = .; } >RAM .stack1 (NOLOAD) : { . = ALIGN(16); . += DEFINED(__num_stacks) && __num_stacks >= 2 ? _stack_size : 0; _stack1_top = .; } >RAM .stack2 (NOLOAD) : { . = ALIGN(16); . += DEFINED(__num_stacks) && __num_stacks >= 3 ? _stack_size : 0; _stack2_top = .; } >RAM .stack3 (NOLOAD) : { . = ALIGN(16); . += DEFINED(__num_stacks) && __num_stacks >= 4 ? _stack_size : 0; _stack3_top = .; } >RAM /* Start address (in flash) of .data, used by startup code. */ _sidata = LOADADDR(.data); /* Globals with initial value */ .data : { . = ALIGN(4); /* see https://gnu-mcu-eclipse.github.io/arch/riscv/programmer/#the-gp-global-pointer-register */ PROVIDE( __global_pointer$ = . + (4K / 2) ); _sdata = .; /* used by startup code */ *(.sdata) *(.data .data.*) . = ALIGN(4); _edata = .; /* used by startup code */ } >RAM AT>FLASH_TEXT /* Zero-initialized globals */ .bss : { . = ALIGN(4); _sbss = .; /* used by startup code */ *(.sbss) *(.bss .bss.*) *(COMMON) . = ALIGN(4); _ebss = .; /* used by startup code */ } >RAM /DISCARD/ : { *(.eh_frame) /* causes 'no memory region specified' error in lld */ } } /* For the memory allocator. */ _heap_start = ALIGN(_ebss, 16); _heap_end = ORIGIN(RAM) + LENGTH(RAM); _globals_start = _sdata; _globals_end = _ebss; ================================================ FILE: targets/riscv32.json ================================================ { "inherits": ["riscv"], "llvm-target": "riscv32-unknown-none", "cpu": "generic-rv32", "target-abi": "ilp32", "build-tags": ["tinygo.riscv32"], "scheduler": "tasks", "default-stack-size": 2048, "cflags": [ "-march=rv32imac" ], "ldflags": [ "-melf32lriscv", "-mllvm", "-enable-machine-outliner=never" ], "gdb": [ "gdb-multiarch", "gdb" ] } ================================================ FILE: targets/riscv64.json ================================================ { "inherits": ["riscv"], "llvm-target": "riscv64-unknown-none", "cpu": "generic-rv64", "target-abi": "lp64", "build-tags": ["tinygo.riscv64"], "cflags": [ "-march=rv64gc" ], "ldflags": [ "-melf64lriscv" ] } ================================================ FILE: targets/rp2040-boot-stage2.S ================================================ // // Implementation of RP2040 stage 2 boot loader. This code is derived from the // Winbond W25Q080 implementation (as found in the Pico) in the official Pico SDK. // // This implementation has been made 'stand-alone' by including necessary code / // symbols from the included files in the reference implementation directly into // the source. It has also been modified to include the conditional logic from // the CircuitPython implementation that supports additional flash chips. The // CircuitPython source is here: // https://github.com/adafruit/circuitpython/blob/main/ports/raspberrypi/stage2.c.jinja // // This file cannot be assembled directly, instead assemble the board-specific file // (such as pico-boot-stage2.S) which defines the parameters specific to the flash // chip included on that board. // // Care has been taken to preserve ordering and it has been verified the generated // binary is byte-for-byte identical to the reference code binary when assembled for // the Pico. // // Note: the stage 2 boot loader must be 256 bytes in length and have a checksum // present. In TinyGo, the linker script is responsible for allocating 256 bytes // for the .boot2 section and the build logic patches the checksum into the // binary after linking, controlled by the '.json' flag 'rp2040-boot-patch'. // // The stage 2 bootstrap section can be inspected in an elf file using this command: // objdump -s -j .boot2 .elf // // Original Source: // https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/boot_stage2/boot2_w25q080.S // // ---------------------------------------------------------------------------- // Second stage boot code // Copyright (c) 2019-2021 Raspberry Pi (Trading) Ltd. // SPDX-License-Identifier: BSD-3-Clause // // Device: Winbond W25Q080 // Also supports W25Q16JV (which has some different SR instructions) // Also supports AT25SF081 // Also supports S25FL132K0 // // Description: Configures W25Q080 to run in Quad I/O continuous read XIP mode // // Details: * Check status register 2 to determine if QSPI mode is enabled, // and perform an SR2 programming cycle if necessary. // * Use SSI to perform a dummy 0xEB read command, with the mode // continuation bits set, so that the flash will not require // 0xEB instruction prefix on subsequent reads. // * Configure SSI to write address, mode bits, but no instruction. // SSI + flash are now jointly in a state where continuous reads // can take place. // * Jump to exit pointer passed in via lr. Bootrom passes null, // in which case this code uses a default 256 byte flash offset // // Building: * This code must be position-independent, and use stack only // * The code will be padded to a size of 256 bytes, including a // 4-byte checksum. Therefore code size cannot exceed 252 bytes. // ---------------------------------------------------------------------------- // // Expanded include files // #define CMD_WRITE_ENABLE 0x06 #define CMD_READ_STATUS1 0x05 #define CMD_READ_STATUS2 0x35 #define CMD_WRITE_STATUS1 0x01 #define CMD_WRITE_STATUS2 0x31 #define SREG_DATA 0x02 // Enable quad-SPI mode #define XIP_BASE 0x10000000 #define XIP_SSI_BASE 0x18000000 #define PADS_QSPI_BASE 0x40020000 #define PPB_BASE 0xe0000000 #define M0PLUS_VTOR_OFFSET 0x0000ed08 #define PADS_QSPI_GPIO_QSPI_SCLK_DRIVE_LSB 4 #define PADS_QSPI_GPIO_QSPI_SCLK_SLEWFAST_BITS 0x00000001 #define PADS_QSPI_GPIO_QSPI_SCLK_OFFSET 0x00000004 #define PADS_QSPI_GPIO_QSPI_SD0_OFFSET 0x00000008 #define PADS_QSPI_GPIO_QSPI_SD0_SCHMITT_BITS 0x00000002 #define PADS_QSPI_GPIO_QSPI_SD1_OFFSET 0x0000000c #define PADS_QSPI_GPIO_QSPI_SD2_OFFSET 0x00000010 #define PADS_QSPI_GPIO_QSPI_SD3_OFFSET 0x00000014 #define SSI_CTRLR0_OFFSET 0x00000000 #define SSI_CTRLR1_OFFSET 0x00000004 #define SSI_SSIENR_OFFSET 0x00000008 #define SSI_BAUDR_OFFSET 0x00000014 #define SSI_SR_OFFSET 0x00000028 #define SSI_DR0_OFFSET 0x00000060 #define SSI_RX_SAMPLE_DLY_OFFSET 0x000000f0 #define SSI_CTRLR0_DFS_32_LSB 16 #define SSI_CTRLR0_SPI_FRF_VALUE_QUAD 0x2 #define SSI_CTRLR0_SPI_FRF_LSB 21 #define SSI_CTRLR0_TMOD_VALUE_TX_AND_RX 0x0 #define SSI_CTRLR0_TMOD_VALUE_EEPROM_READ 0x3 #define SSI_CTRLR0_TMOD_LSB 8 #define SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_1C2A 0x1 #define SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_2C2A 0x2 #define SSI_SPI_CTRLR0_OFFSET 0x000000f4 #define SSI_SPI_CTRLR0_INST_L_VALUE_NONE 0x0 #define SSI_SPI_CTRLR0_INST_L_VALUE_8B 0x2 #define SSI_SPI_CTRLR0_TRANS_TYPE_LSB 0 #define SSI_SPI_CTRLR0_ADDR_L_LSB 2 #define SSI_SPI_CTRLR0_INST_L_LSB 8 #define SSI_SPI_CTRLR0_WAIT_CYCLES_LSB 11 #define SSI_SPI_CTRLR0_XIP_CMD_LSB 24 #define SSI_SR_BUSY_BITS 0x00000001 #define SSI_SR_TFE_BITS 0x00000004 // ---------------------------------------------------------------------------- // Config section // ---------------------------------------------------------------------------- // It should be possible to support most flash devices by modifying this section // The serial flash interface will run at clk_sys/PICO_FLASH_SPI_CLKDIV. // This must be a positive, even integer. // The bootrom is very conservative with SPI frequency, but here we should be // as aggressive as possible. #define PICO_FLASH_SPI_CLKDIV BOARD_PICO_FLASH_SPI_CLKDIV #if PICO_FLASH_SPI_CLKDIV & 1 #error PICO_FLASH_SPI_CLKDIV must be even #endif #if BOARD_QUAD_OK==1 // Define interface width: single/dual/quad IO #define FRAME_FORMAT SSI_CTRLR0_SPI_FRF_VALUE_QUAD #define TRANSACTION_TYPE SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_2C2A // Note that the INST_L field is used to select what XIP data gets pushed into // the TX FIFO: // INST_L_0_BITS {ADDR[23:0],XIP_CMD[7:0]} Load "mode bits" into XIP_CMD // Anything else {XIP_CMD[7:0],ADDR[23:0]} Load SPI command into XIP_CMD #define INSTRUCTION_LENGTH SSI_SPI_CTRLR0_INST_L_VALUE_NONE #define READ_INSTRUCTION MODE_CONTINUOUS_READ #define ADDR_L 8 // 6 for address, 2 for mode #else #define FRAME_FORMAT SSI_CTRLR0_SPI_FRF_VALUE_STD #define TRANSACTION_TYPE SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_1C1A #define INSTRUCTION_LENGTH SSI_SPI_CTRLR0_INST_L_VALUE_8B #define READ_INSTRUCTION BOARD_CMD_READ #define ADDR_L 6 // * 4 = 24 #endif // The flash-chip specific read isntruction #define CMD_READ BOARD_CMD_READ // "Mode bits" are 8 special bits sent immediately after // the address bits in a "Read Data Fast Quad I/O" command sequence. // On W25Q080, the four LSBs are don't care, and if MSBs == 0xa, the // next read does not require the 0xeb instruction prefix. #define MODE_CONTINUOUS_READ 0xa0 // How many clocks of Hi-Z following the mode bits. For W25Q080, 4 dummy cycles // are required. #define WAIT_CYCLES BOARD_WAIT_CYCLES // If defined, we will read status reg, compare to SREG_DATA, and overwrite // with our value if the SR doesn't match. // We do a two-byte write to SR1 (01h cmd) rather than a one-byte write to // SR2 (31h cmd) as the latter command isn't supported by WX25Q080. // This isn't great because it will remove block protections. // A better solution is to use a volatile SR write if your device supports it. #define PROGRAM_STATUS_REG .syntax unified .cpu cortex-m0plus .thumb .section .boot2, "ax" // The exit point is passed in lr. If entered from bootrom, this will be the // flash address immediately following this second stage (0x10000100). // Otherwise it will be a return address -- second stage being called as a // function by user code, after copying out of XIP region. r3 holds SSI base, // r0...2 used as temporaries. Other GPRs not used. .global _stage2_boot .type _stage2_boot,%function .thumb_func _stage2_boot: push {lr} // Set pad configuration: // - SCLK 8mA drive, no slew limiting // - SDx disable input Schmitt to reduce delay ldr r3, =PADS_QSPI_BASE movs r0, #(2 << PADS_QSPI_GPIO_QSPI_SCLK_DRIVE_LSB | PADS_QSPI_GPIO_QSPI_SCLK_SLEWFAST_BITS) str r0, [r3, #PADS_QSPI_GPIO_QSPI_SCLK_OFFSET] ldr r0, [r3, #PADS_QSPI_GPIO_QSPI_SD0_OFFSET] movs r1, #PADS_QSPI_GPIO_QSPI_SD0_SCHMITT_BITS bics r0, r1 #if BOARD_QUAD_OK==1 str r0, [r3, #PADS_QSPI_GPIO_QSPI_SD0_OFFSET] #endif str r0, [r3, #PADS_QSPI_GPIO_QSPI_SD1_OFFSET] #if BOARD_QUAD_OK==1 str r0, [r3, #PADS_QSPI_GPIO_QSPI_SD2_OFFSET] str r0, [r3, #PADS_QSPI_GPIO_QSPI_SD3_OFFSET] #endif ldr r3, =XIP_SSI_BASE // Disable SSI to allow further config movs r1, #0 str r1, [r3, #SSI_SSIENR_OFFSET] // Set baud rate movs r1, #PICO_FLASH_SPI_CLKDIV str r1, [r3, #SSI_BAUDR_OFFSET] // Set 1-cycle sample delay. If PICO_FLASH_SPI_CLKDIV == 2 then this means, // if the flash launches data on SCLK posedge, we capture it at the time that // the next SCLK posedge is launched. This is shortly before that posedge // arrives at the flash, so data hold time should be ok. For // PICO_FLASH_SPI_CLKDIV > 2 this pretty much has no effect. movs r1, #1 movs r2, #SSI_RX_SAMPLE_DLY_OFFSET // == 0xf0 so need 8 bits of offset significance str r1, [r3, r2] // On QSPI parts we usually need a 01h SR-write command to enable QSPI mode // (i.e. turn WPn and HOLDn into IO2/IO3) #ifdef PROGRAM_STATUS_REG program_sregs: #define CTRL0_SPI_TXRX \ (7 << SSI_CTRLR0_DFS_32_LSB) | /* 8 bits per data frame */ \ (SSI_CTRLR0_TMOD_VALUE_TX_AND_RX << SSI_CTRLR0_TMOD_LSB) ldr r1, =(CTRL0_SPI_TXRX) str r1, [r3, #SSI_CTRLR0_OFFSET] // Enable SSI and select slave 0 movs r1, #1 str r1, [r3, #SSI_SSIENR_OFFSET] // Check whether SR needs updating #if BOARD_QUAD_OK==1 # if BOARD_QUAD_ENABLE_STATUS_BYTE==1 movs r0, #CMD_READ_STATUS1 # elif BOARD_QUAD_ENABLE_STATUS_BYTE==2 movs r0, #CMD_READ_STATUS2 # endif bl read_flash_sreg movs r2, #BOARD_QUAD_ENABLE_BIT_MASK cmp r0, r2 beq skip_sreg_programming // Send write enable command movs r1, #CMD_WRITE_ENABLE str r1, [r3, #SSI_DR0_OFFSET] // Poll for completion and discard RX bl wait_ssi_ready ldr r1, [r3, #SSI_DR0_OFFSET] // Send status write command followed by data bytes # if BOARD_SPLIT_STATUS_WRITE==1 # if BOARD_QUAD_ENABLE_STATUS_BYTE==1 movs r1, #CMD_WRITE_STATUS1 # elif BOARD_QUAD_ENABLE_STATUS_BYTE==2 movs r1, #CMD_WRITE_STATUS2 # endif str r1, [r3, #SSI_DR0_OFFSET] str r2, [r3, #SSI_DR0_OFFSET] bl wait_ssi_ready //ldr r1, [r3, #SSI_DR0_OFFSET] ldr r1, [r3, #SSI_DR0_OFFSET] ldr r1, [r3, #SSI_DR0_OFFSET] # else movs r1, #CMD_WRITE_STATUS1 str r1, [r3, #SSI_DR0_OFFSET] # if BOARD_QUAD_ENABLE_STATUS_BYTE==2 movs r0, #0 str r0, [r3, #SSI_DR0_OFFSET] # endif str r2, [r3, #SSI_DR0_OFFSET] bl wait_ssi_ready ldr r1, [r3, #SSI_DR0_OFFSET] ldr r1, [r3, #SSI_DR0_OFFSET] # if BOARD_QUAD_ENABLE_STATUS_BYTE==2 ldr r1, [r3, #SSI_DR0_OFFSET] # endif # endif // Poll status register for write completion 1: movs r0, #CMD_READ_STATUS1 bl read_flash_sreg movs r1, #1 tst r0, r1 bne 1b #endif skip_sreg_programming: // Disable SSI again so that it can be reconfigured movs r1, #0 str r1, [r3, #SSI_SSIENR_OFFSET] #endif // Currently the flash expects an 8 bit serial command prefix on every // transfer, which is a waste of cycles. Perform a dummy Fast Read Quad I/O // command, with mode bits set such that the flash will not expect a serial // command prefix on *subsequent* transfers. We don't care about the results // of the read, the important part is the mode bits. dummy_read: #define CTRLR0_ENTER_XIP \ (FRAME_FORMAT /* Quad I/O mode */ \ << SSI_CTRLR0_SPI_FRF_LSB) | \ (31 << SSI_CTRLR0_DFS_32_LSB) | /* 32 data bits */ \ (SSI_CTRLR0_TMOD_VALUE_EEPROM_READ /* Send INST/ADDR, Receive Data */ \ << SSI_CTRLR0_TMOD_LSB) ldr r1, =(CTRLR0_ENTER_XIP) str r1, [r3, #SSI_CTRLR0_OFFSET] movs r1, #0x0 // NDF=0 (single 32b read) str r1, [r3, #SSI_CTRLR1_OFFSET] #if BOARD_QUAD_OK==1 #define SPI_CTRLR0_ENTER_XIP \ (ADDR_L << SSI_SPI_CTRLR0_ADDR_L_LSB) | /* Address + mode bits */ \ (WAIT_CYCLES << SSI_SPI_CTRLR0_WAIT_CYCLES_LSB) | /* Hi-Z dummy clocks following address + mode */ \ (SSI_SPI_CTRLR0_INST_L_VALUE_8B \ << SSI_SPI_CTRLR0_INST_L_LSB) | /* 8-bit instruction */ \ (SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_1C2A /* Send Command in serial mode then address in Quad I/O mode */ \ << SSI_SPI_CTRLR0_TRANS_TYPE_LSB) ldr r1, =(SPI_CTRLR0_ENTER_XIP) ldr r0, =(XIP_SSI_BASE + SSI_SPI_CTRLR0_OFFSET) // SPI_CTRL0 Register str r1, [r0] movs r1, #1 // Re-enable SSI str r1, [r3, #SSI_SSIENR_OFFSET] movs r1, #CMD_READ str r1, [r3, #SSI_DR0_OFFSET] // Push SPI command into TX FIFO movs r1, #MODE_CONTINUOUS_READ // 32-bit: 24 address bits (we don't care, so 0) and M[7:4]=1010 str r1, [r3, #SSI_DR0_OFFSET] // Push Address into TX FIFO - this will trigger the transaction // Poll for completion bl wait_ssi_ready // The flash is in a state where we can blast addresses in parallel, and get // parallel data back. Now configure the SSI to translate XIP bus accesses // into QSPI transfers of this form. movs r1, #0 str r1, [r3, #SSI_SSIENR_OFFSET] // Disable SSI (and clear FIFO) to allow further config #endif // Note that the INST_L field is used to select what XIP data gets pushed into // the TX FIFO: // INST_L_0_BITS {ADDR[23:0],XIP_CMD[7:0]} Load "mode bits" into XIP_CMD // Anything else {XIP_CMD[7:0],ADDR[23:0]} Load SPI command into XIP_CMD configure_ssi: #define SPI_CTRLR0_XIP \ (READ_INSTRUCTION /* Mode bits to keep flash in continuous read mode */ \ << SSI_SPI_CTRLR0_XIP_CMD_LSB) | \ (ADDR_L << SSI_SPI_CTRLR0_ADDR_L_LSB) | /* Total number of address + mode bits */ \ (WAIT_CYCLES << SSI_SPI_CTRLR0_WAIT_CYCLES_LSB) | /* Hi-Z dummy clocks following address + mode */ \ (INSTRUCTION_LENGTH /* Do not send a command, instead send XIP_CMD as mode bits after address */ \ << SSI_SPI_CTRLR0_INST_L_LSB) | \ (TRANSACTION_TYPE /* Send Address in Quad I/O mode (and Command but that is zero bits long) */ \ << SSI_SPI_CTRLR0_TRANS_TYPE_LSB) ldr r1, =(SPI_CTRLR0_XIP) ldr r0, =(XIP_SSI_BASE + SSI_SPI_CTRLR0_OFFSET) str r1, [r0] movs r1, #1 str r1, [r3, #SSI_SSIENR_OFFSET] // Re-enable SSI // Bus accesses to the XIP window will now be transparently serviced by the // external flash on cache miss. We are ready to run code from flash. // // Helper Includes // // // #include "boot2_helpers/exit_from_boot2.S" // // If entered from the bootrom, lr (which we earlier pushed) will be 0, // and we vector through the table at the start of the main flash image. // Any regular function call will have a nonzero value for lr. check_return: pop {r0} cmp r0, #0 beq vector_into_flash bx r0 vector_into_flash: ldr r0, =(XIP_BASE + 0x100) ldr r1, =(PPB_BASE + M0PLUS_VTOR_OFFSET) str r0, [r1] ldmia r0, {r0, r1} msr msp, r0 bx r1 // // #include "boot2_helpers/wait_ssi_ready.S" // wait_ssi_ready: push {r0, r1, lr} // Command is complete when there is nothing left to send // (TX FIFO empty) and SSI is no longer busy (CSn deasserted) 1: ldr r1, [r3, #SSI_SR_OFFSET] movs r0, #SSI_SR_TFE_BITS tst r1, r0 beq 1b movs r0, #SSI_SR_BUSY_BITS tst r1, r0 bne 1b pop {r0, r1, pc} #ifdef PROGRAM_STATUS_REG // // #include "boot2_helpers/read_flash_sreg.S" // // Pass status read cmd into r0. // Returns status value in r0. .global read_flash_sreg .type read_flash_sreg,%function .thumb_func read_flash_sreg: push {r1, lr} str r0, [r3, #SSI_DR0_OFFSET] // Dummy byte: str r0, [r3, #SSI_DR0_OFFSET] bl wait_ssi_ready // Discard first byte and combine the next two ldr r0, [r3, #SSI_DR0_OFFSET] ldr r0, [r3, #SSI_DR0_OFFSET] pop {r1, pc} #endif .global literals literals: .ltorg .end ================================================ FILE: targets/rp2040.json ================================================ { "inherits": ["cortex-m0plus"], "build-tags": ["rp2040", "rp"], "scheduler": "cores", "flash-1200-bps-reset": "true", "flash-method": "msd", "serial": "usb", "msd-volume-name": ["RPI-RP2"], "msd-firmware-name": "firmware.uf2", "binary-format": "uf2", "uf2-family-id": "0xe48bff56", "rp2040-boot-patch": true, "extra-files": [ "src/device/rp/rp2040.s" ], "ldflags": [ "--defsym=__num_stacks=2" ], "linkerscript": "targets/rp2040.ld", "openocd-interface": "picoprobe", "openocd-transport": "swd", "openocd-target": "rp2040" } ================================================ FILE: targets/rp2040.ld ================================================ MEMORY { /* Reserve exactly 256 bytes at start of flash for second stage bootloader */ BOOT2_TEXT (rx) : ORIGIN = 0x10000000, LENGTH = 256 FLASH_TEXT (rx) : ORIGIN = 0x10000000 + 256, LENGTH = __flash_size - 256 RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 256k } _stack_size = 2K; SECTIONS { /* Second stage bootloader is prepended to the image. It must be 256 bytes and checksummed. The gap to the checksum is zero-padded. */ .boot2 : { __boot2_start__ = .; KEEP (*(.boot2)); /* Explicitly allocate space for CRC32 checksum at end of second stage bootloader */ . = __boot2_start__ + 256 - 4; LONG(0) } > BOOT2_TEXT = 0x0 /* The second stage will always enter the image at the start of .text. The debugger will use the ELF entry point, which is the _entry_point symbol if present, otherwise defaults to start of .text. This can be used to transfer control back to the bootrom on debugger launches only, to perform proper flash setup. */ } INCLUDE "targets/arm.ld" ================================================ FILE: targets/rp2350.json ================================================ { "inherits": ["cortex-m33"], "build-tags": ["rp2350", "rp"], "scheduler": "cores", "flash-1200-bps-reset": "true", "flash-method": "msd", "serial": "usb", "msd-volume-name": ["RP2350"], "msd-firmware-name": "firmware.uf2", "binary-format": "uf2", "uf2-family-id": "0xe48bff59","comment":"See page 393 of RP2350 datasheet: RP2350 Arm Secure image (i.e. one intended to be booted directly by the bootrom)", "extra-files": [ "src/device/rp/rp2350.s", "targets/rp2350_embedded_block.s" ], "ldflags": [ "--defsym=__flash_size=2M", "--defsym=__num_stacks=2" ], "linkerscript": "targets/rp2350.ld", "openocd-interface": "picoprobe", "openocd-transport": "swd", "openocd-target": "rp2350" } ================================================ FILE: targets/rp2350.ld ================================================ /* See Rust for a more complete reference: https://github.com/rp-rs/rp-hal/blob/main/rp235x-hal-examples/memory.x */ MEMORY { /* 2MiB safe default. */ FLASH : ORIGIN = 0x10000000, LENGTH = __flash_size /* RAM consists of 8 banks, SRAM0..SRAM7 with striped mapping. */ SRAM : ORIGIN = 0x20000000, LENGTH = 512k /* Banks 8 and 9 use direct mapping which can be specailized for applications where predictable access time is beneficial. i.e: Separate stacks for core0 and core1. */ SRAM4 : ORIGIN = 0x20080000, LENGTH = 4k SRAM5 : ORIGIN = 0x20081000, LENGTH = 4k FLASH_TEXT (rx) : ORIGIN = 0x10000000, LENGTH = __flash_size RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 512k } _stack_size = 2K; SECTIONS { } INCLUDE "targets/arm.ld" ================================================ FILE: targets/rp2350_embedded_block.s ================================================ // Minimum viable block image from datasheet section 5.9.5.1, "Minimum Arm IMAGE_DEF" .section .after_isr_vector, "a" .p2align 2 embedded_block: .word 0xffffded3 .word 0x10210142 .word 0x000001ff .word 0x00000000 .word 0xab123579 embedded_block_end: ================================================ FILE: targets/rp2350b.json ================================================ { "inherits": ["rp2350"], "build-tags": ["rp2350b"], "serial-port": ["2e8a:000f"] } ================================================ FILE: targets/simavr.json ================================================ { "inherits": ["atmega1284p"], "scheduler": "tasks", "default-stack-size": 384 } ================================================ FILE: targets/stm32.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x08000000, LENGTH = 64K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K } _stack_size = 2K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/stm32f103rb.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x08000000, LENGTH = 128K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K } _stack_size = 2K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/stm32f405.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x08000000, LENGTH = 1M RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K } _stack_size = 4K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/stm32f407.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x08000000, LENGTH = 1M RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K } _stack_size = 4K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/stm32f469.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x08000000, LENGTH = 2M RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 320K } _stack_size = 4K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/stm32f469disco.json ================================================ { "inherits": ["cortex-m4"], "build-tags": ["stm32f469disco", "stm32f469", "stm32f4", "stm32"], "serial": "uart", "linkerscript": "targets/stm32f469.ld", "extra-files": [ "src/device/stm32/stm32f469.s" ], "flash-method": "openocd", "openocd-interface": "stlink", "openocd-target": "stm32f4x" } ================================================ FILE: targets/stm32f4disco-1.json ================================================ { "inherits": ["stm32f4disco"], "openocd-interface": "stlink-v2-1" } ================================================ FILE: targets/stm32f4disco.json ================================================ { "inherits": ["cortex-m4"], "build-tags": ["stm32f4disco", "stm32f407", "stm32f4", "stm32"], "serial": "uart", "linkerscript": "targets/stm32f407.ld", "extra-files": [ "src/device/stm32/stm32f407.s" ], "flash-method": "openocd", "openocd-interface": "stlink-v2", "openocd-target": "stm32f4x" } ================================================ FILE: targets/stm32f7x2zetx.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x08000000, LENGTH = 512K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 256K } _stack_size = 4K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/stm32l031k6.ld ================================================ MEMORY { FLASH_TEXT (rx) : ORIGIN = 0x08000000, LENGTH = 32K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 8K } _stack_size = 2K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/stm32l031x6.ld ================================================ MEMORY { FLASH_TEXT (rx) : ORIGIN = 0x08000000, LENGTH = 32K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 8K } _stack_size = 2K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/stm32l072czt6.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x08000000, LENGTH = 128K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K } _stack_size = 2K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/stm32l0x1.json ================================================ { "inherits": [ "cortex-m0plus" ], "build-tags": [ "stm32l031", "stm32l0x1", "stm32l0", "stm32" ], "linkerscript": "targets/stm32l031x6.ld", "extra-files": [ "src/device/stm32/stm32l0x1.s" ], "flash-method": "openocd", "openocd-interface": "cmsis-dap", "openocd-target": "stm32l0" } ================================================ FILE: targets/stm32l0x2.json ================================================ { "inherits": [ "cortex-m0plus" ], "build-tags": [ "stm32l0", "stm32l0x2", "stm32" ], "extra-files": [ "src/device/stm32/stm32l0x2.s" ] } ================================================ FILE: targets/stm32l4x2.ld ================================================ MEMORY { FLASH_TEXT (rx) : ORIGIN = 0x08000000, LENGTH = 256K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K } _stack_size = 4K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/stm32l4x5.ld ================================================ MEMORY { FLASH_TEXT (rx) : ORIGIN = 0x08000000, LENGTH = 2048K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 640K } _stack_size = 4K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/stm32l4x6.ld ================================================ MEMORY { FLASH_TEXT (rx) : ORIGIN = 0x08000000, LENGTH = 1024K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 96K RAM2 (xrw) : ORIGIN = 0x10000000, LENGTH = 32K } _stack_size = 4K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/stm32l5x2xe.ld ================================================ MEMORY { FLASH_TEXT (rx) : ORIGIN = 0x08000000, LENGTH = 512K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K } _stack_size = 4K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/stm32wl5x_cm4.json ================================================ { "inherits": ["cortex-m4"], "build-tags": [ "stm32wl5x_cm4","stm32wlx", "stm32"], "extra-files": [ "src/device/stm32/stm32wl5x_cm4.s" ], "linkerscript": "targets/stm32wlx.ld" } ================================================ FILE: targets/stm32wle5.json ================================================ { "inherits": ["cortex-m4"], "build-tags": [ "stm32wle5","stm32wlx", "stm32"], "extra-files": [ "src/device/stm32/stm32wle5.s" ], "linkerscript": "targets/stm32wlx.ld" } ================================================ FILE: targets/stm32wlx.ld ================================================ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x08000000, LENGTH = 256K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K } _stack_size = 4K; INCLUDE "targets/arm.ld" ================================================ FILE: targets/swan.json ================================================ { "inherits": ["cortex-m4"], "build-tags": ["swan", "stm32l4r5", "stm32l4x5", "stm32l4", "stm32"], "serial": "uart", "linkerscript": "targets/stm32l4x5.ld", "extra-files": [ "src/device/stm32/stm32l4x5.s" ], "flash-method": "command", "flash-command": "dfu-util --alt 0 --dfuse-address 0x08000000 --download {bin}", "openocd-interface": "stlink", "openocd-target": "stm32l4x" } ================================================ FILE: targets/teensy36.json ================================================ { "inherits": ["cortex-m4"], "build-tags": ["teensy36", "teensy", "mk66f18", "nxp"], "serial": "uart", "linkerscript": "targets/nxpmk66f18.ld", "extra-files": [ "src/device/nxp/mk66f18.s", "targets/teensy36.s" ], "flash-command": "teensy_loader_cli -mmcu=mk66fx1m0 -v -w {hex}" } ================================================ FILE: targets/teensy36.s ================================================ .section .flash_config .global __flash_config __flash_config: .byte 0xFF .byte 0xFF .byte 0xFF .byte 0xFF .byte 0xFF .byte 0xFF .byte 0xFF .byte 0xFF .byte 0xFF .byte 0xFF .byte 0xFF .byte 0xFF .byte 0xDE .byte 0xF9 .byte 0xFF .byte 0xFF ================================================ FILE: targets/teensy40.json ================================================ { "inherits": ["cortex-m7"], "build-tags": ["teensy40", "teensy", "mimxrt1062", "nxp"], "serial": "uart", "automatic-stack-size": false, "linkerscript": "targets/mimxrt1062-teensy40.ld", "extra-files": [ "src/device/nxp/mimxrt1062.s", "targets/teensy40.s" ], "flash-command": "teensy_loader_cli -mmcu=imxrt1062 -v -w {hex}" } ================================================ FILE: targets/teensy40.s ================================================ // ----------------------------------------------------------------------------- // file: teensy40.s // desc: various startup and configuration data for Teensy 4.0. // ----------------------------------------------------------------------------- // References // i.MX RT1060 Processor Reference Manual // - Section 9.7.1 "Image Vector Table and Boot Data" // Teensyduino 1.53 by Paul Stoffregen (PJRC) // - cores/teensy4/bootdata.c // - cores/teensy4/startup.c // ----------------------------------------------------------------------------- .section .boot_data .global __boot_data __boot_data: .word 0x60000000 // boot start location .word _image_size // flash size .word 0 // plugin flag, use 0 to indicate normal (non-plugin) ROM image .section .ivt .global __ivt __ivt: .word 0x402000D1 // header (version 4.0) .word _svectors // image entry function .word 0 // reserved .word 0 // DCD info (optional, set to 0|NULL if unused) .word __boot_data // boot data struct .word __ivt // self .word 0 // command sequence file (CSF) not provided in image .word 0 // reserved .section .flash_config .global __flash_config __flash_config: // 448 byte common FlexSPI configuration block, 8.6.3.1 page 223 (RT1060 rev 0) // MCU_Flashloader_Reference_Manual.pdf, 8.2.1, Table 8-2, page 72-75 .word 0x42464346 // Tag 0x00 .word 0x56010000 // Version .word 0 // reserved .word 0x00020101 // columnAdressWidth,dataSetupTime,dataHoldTime,readSampleClkSrc .word 0x00000000 // waitTimeCfgCommands,-,deviceModeCfgEnable .word 0 // deviceModeSeq .word 0 // deviceModeArg .word 0x00000000 // -,-,-,configCmdEnable .word 0 // configCmdSeqs 0x20 .word 0 .word 0 .word 0 .word 0 // cfgCmdArgs 0x30 .word 0 .word 0 .word 0 .word 0x00000000 // controllerMiscOption 0x40 .word 0x00030401 // lutCustomSeqEnable,serialClkFreq,sflashPadType,deviceType .word 0 // reserved .word 0 // reserved .word 0x00200000 // sflashA1Size (Teensy 4.0) 0x50 //.word 0x00800000 // sflashA1Size (Teensy 4.1) 0x50 .word 0 // sflashA2Size .word 0 // sflashB1Size .word 0 // sflashB2Size .word 0 // csPadSettingOverride 0x60 .word 0 // sclkPadSettingOverride .word 0 // dataPadSettingOverride .word 0 // dqsPadSettingOverride .word 0 // timeoutInMs 0x70 .word 0 // commandInterval .word 0 // dataValidTime .word 0x00000000 // busyBitPolarity,busyOffset .word 0x0A1804EB // lookupTable[0] 0x80 .word 0x26043206 // lookupTable[1] .word 0 // lookupTable[2] .word 0 // lookupTable[3] .word 0x24040405 // lookupTable[4] 0x90 .word 0 // lookupTable[5] .word 0 // lookupTable[6] .word 0 // lookupTable[7] .word 0 // lookupTable[8] 0xA0 .word 0 // lookupTable[9] .word 0 // lookupTable[10] .word 0 // lookupTable[11] .word 0x00000406 // lookupTable[12] 0xB0 .word 0 // lookupTable[13] .word 0 // lookupTable[14] .word 0 // lookupTable[15] .word 0 // lookupTable[16] 0xC0 .word 0 // lookupTable[17] .word 0 // lookupTable[18] .word 0 // lookupTable[19] .word 0x08180420 // lookupTable[20] 0xD0 .word 0 // lookupTable[21] .word 0 // lookupTable[22] .word 0 // lookupTable[23] .word 0 // lookupTable[24] 0xE0 .word 0 // lookupTable[25] .word 0 // lookupTable[26] .word 0 // lookupTable[27] .word 0 // lookupTable[28] 0xF0 .word 0 // lookupTable[29] .word 0 // lookupTable[30] .word 0 // lookupTable[31] .word 0x081804D8 // lookupTable[32] 0x100 .word 0 // lookupTable[33] .word 0 // lookupTable[34] .word 0 // lookupTable[35] .word 0x08180402 // lookupTable[36] 0x110 .word 0x00002004 // lookupTable[37] .word 0 // lookupTable[38] .word 0 // lookupTable[39] .word 0 // lookupTable[40] 0x120 .word 0 // lookupTable[41] .word 0 // lookupTable[42] .word 0 // lookupTable[43] .word 0x00000460 // lookupTable[44] 0x130 .word 0 // lookupTable[45] .word 0 // lookupTable[46] .word 0 // lookupTable[47] .word 0 // lookupTable[48] 0x140 .word 0 // lookupTable[49] .word 0 // lookupTable[50] .word 0 // lookupTable[51] .word 0 // lookupTable[52] 0x150 .word 0 // lookupTable[53] .word 0 // lookupTable[54] .word 0 // lookupTable[55] .word 0 // lookupTable[56] 0x160 .word 0 // lookupTable[57] .word 0 // lookupTable[58] .word 0 // lookupTable[59] .word 0 // lookupTable[60] 0x170 .word 0 // lookupTable[61] .word 0 // lookupTable[62] .word 0 // lookupTable[63] .word 0 // LUT 0: Read 0x180 .word 0 // LUT 1: ReadStatus .word 0 // LUT 3: WriteEnable .word 0 // LUT 5: EraseSector .word 0 // LUT 9: PageProgram 0x190 .word 0 // LUT 11: ChipErase .word 0 // LUT 15: Dummy .word 0 // LUT unused? .word 0 // LUT unused? 0x1A0 .word 0 // LUT unused? .word 0 // LUT unused? .word 0 // LUT unused? .word 0 // reserved 0x1B0 .word 0 // reserved .word 0 // reserved .word 0 // reserved // 64 byte Serial NOR configuration block (8.6.3.2, page 346) .word 256 // pageSize 0x1C0 .word 4096 // sectorSize .word 1 // ipCmdSerialClkFreq .word 0 // reserved .word 0x00010000 // block size 0x1D0 .word 0 // reserved .word 0 // reserved .word 0 // reserved .word 0 // reserved 0x1E0 .word 0 // reserved .word 0 // reserved .word 0 // reserved .word 0 // reserved 0x1F0 .word 0 // reserved .word 0 // reserved .word 0 // reserved ================================================ FILE: targets/teensy41.json ================================================ { "inherits": ["cortex-m7"], "build-tags": ["teensy41", "teensy", "mimxrt1062", "nxp"], "serial": "uart", "automatic-stack-size": false, "linkerscript": "targets/mimxrt1062-teensy40.ld", "extra-files": [ "src/device/nxp/mimxrt1062.s", "targets/teensy40.s" ], "flash-command": "teensy_loader_cli -mmcu=imxrt1062 -v -w {hex}" } ================================================ FILE: targets/thingplus-rp2040.json ================================================ { "inherits": [ "rp2040" ], "serial-port": ["1b4f:0026"], "build-tags": ["thingplus_rp2040"], "ldflags": [ "--defsym=__flash_size=16M" ], "extra-files": [ "targets/pico-boot-stage2.S" ] } ================================================ FILE: targets/thumby.json ================================================ { "inherits": [ "rp2040" ], "serial-port": ["2e8a:0005"], "build-tags": ["thumby"], "default-stack-size": 8192, "ldflags": [ "--defsym=__flash_size=2048K" ], "extra-files": [ "targets/pico-boot-stage2.S" ] } ================================================ FILE: targets/tiny2350.json ================================================ { "inherits": [ "rp2350" ], "build-tags": ["tiny2350"], "ldflags": [ "--defsym=__flash_size=4M" ], "serial-port": ["2e8a:000f"], "default-stack-size": 8192 } ================================================ FILE: targets/tkey.json ================================================ { "inherits": ["riscv32"], "build-tags": ["tkey"], "features": "+32bit,+c,+zmmul,-a,-b,-d,-e,-experimental-sdext,-experimental-sdtrig,-experimental-smctr,-experimental-ssctr,-experimental-svukte,-experimental-xqcia,-experimental-xqciac,-experimental-xqcicli,-experimental-xqcicm,-experimental-xqcics,-experimental-xqcicsr,-experimental-xqciint,-experimental-xqcilo,-experimental-xqcilsm,-experimental-xqcisls,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-experimental-zvbc32e,-experimental-zvkgs,-f,-h,-m,-relax,-sha,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smdbltrp,-smepmp,-smmpm,-smnpm,-smrnmi,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssdbltrp,-ssnpm,-sspm,-ssqosid,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-supm,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-svvptc,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xesppie,-xmipscmove,-xmipslsp,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zaamo,-zabha,-zacas,-zalrsc,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", "cflags": [ "-march=rv32iczmmul" ], "linkerscript": "targets/tkey.ld", "scheduler": "none", "gc": "conservative", "flash-command": "tkey-runapp {bin}", "serial": "uart" } ================================================ FILE: targets/tkey.ld ================================================ MEMORY { RAM (rwx) : ORIGIN = 0x40000000, LENGTH = 0x20000 /* 128 KB */ } REGION_ALIAS("FLASH_TEXT", RAM); _stack_size = 2K; INCLUDE "targets/riscv.ld" ================================================ FILE: targets/trinket-m0.json ================================================ { "inherits": ["atsamd21e18a"], "build-tags": ["trinket_m0"], "serial": "usb", "serial-port": ["239a:801e"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": ["TRINKETBOOT"], "msd-firmware-name": "firmware.uf2" } ================================================ FILE: targets/trinkey-qt2040-boot-stage2.S ================================================ // Adafruit Trinkey QT2040 Stage 2 Bootloader // // This file defines the parameters specific to the flash-chip found // on the Adafruit Trinkey QT2040. The generic implementation is in // rp2040-boot-stage2.S // #define BOARD_PICO_FLASH_SPI_CLKDIV 4 #define BOARD_CMD_READ 0xe7 #define BOARD_QUAD_OK 1 #define BOARD_QUAD_ENABLE_STATUS_BYTE 2 #define BOARD_QUAD_ENABLE_BIT_MASK 2 #define BOARD_SPLIT_STATUS_WRITE 1 #define BOARD_WAIT_CYCLES 2 #include "rp2040-boot-stage2.S" ================================================ FILE: targets/trinkey-qt2040.json ================================================ { "inherits": [ "rp2040" ], "serial-port": ["239a:8109"], "default-stack-size": 8192, "build-tags": ["trinkey_qt2040"], "ldflags": [ "--defsym=__flash_size=8192K" ], "extra-files": [ "targets/trinkey-qt2040-boot-stage2.S" ] } ================================================ FILE: targets/tufty2040.json ================================================ { "inherits": [ "rp2040" ], "serial-port": ["2e8a:0003"], "default-stack-size": 8192, "build-tags": ["tufty2040"], "ldflags": [ "--defsym=__flash_size=1020K" ], "extra-files": [ "targets/pico-boot-stage2.S" ] } ================================================ FILE: targets/wasi.json ================================================ { "inherits": ["wasip1"] } ================================================ FILE: targets/wasip1.json ================================================ { "llvm-target": "wasm32-unknown-wasi", "cpu": "generic", "features": "+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types", "build-tags": ["tinygo.wasm"], "goos": "wasip1", "goarch": "wasm", "linker": "wasm-ld", "libc": "wasi-libc", "rtlib": "compiler-rt", "gc": "precise", "scheduler": "asyncify", "default-stack-size": 65536, "cflags": [ "-mbulk-memory", "-mnontrapping-fptoint", "-mno-multivalue", "-mno-reference-types", "-msign-ext" ], "ldflags": [ "--stack-first", "--no-demangle" ], "extra-files": [ "src/runtime/asm_tinygowasm.S", "src/runtime/gc_boehm.c" ], "emulator": "wasmtime run --dir={tmpDir}::/tmp {}" } ================================================ FILE: targets/wasip2.json ================================================ { "llvm-target": "wasm32-unknown-wasi", "cpu": "generic", "features": "+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types", "build-tags": ["tinygo.wasm", "wasip2"], "buildmode": "c-shared", "goos": "linux", "goarch": "arm", "linker": "wasm-ld", "libc": "wasmbuiltins", "rtlib": "compiler-rt", "gc": "precise", "scheduler": "asyncify", "default-stack-size": 65536, "cflags": [ "-mbulk-memory", "-mnontrapping-fptoint", "-mno-multivalue", "-mno-reference-types", "-msign-ext" ], "ldflags": [ "--stack-first", "--no-demangle", "--no-entry" ], "extra-files": [ "src/runtime/asm_tinygowasm.S" ], "emulator": "wasmtime run --wasm component-model -Sinherit-network -Sallow-ip-name-lookup --dir={tmpDir}::/tmp {}", "wit-package": "{root}/lib/wasi-cli/wit/", "wit-world": "wasi:cli/command" } ================================================ FILE: targets/wasm-undefined.txt ================================================ syscall/js.copyBytesToGo syscall/js.copyBytesToJS syscall/js.finalizeRef syscall/js.stringVal syscall/js.valueCall syscall/js.valueDelete syscall/js.valueGet syscall/js.valueIndex syscall/js.valueInstanceOf syscall/js.valueInvoke syscall/js.valueLength syscall/js.valueLoadString syscall/js.valueNew syscall/js.valuePrepareString syscall/js.valueSet syscall/js.valueSetIndex ================================================ FILE: targets/wasm-unknown.json ================================================ { "llvm-target": "wasm32-unknown-unknown", "cpu": "mvp", "features": "+nontrapping-fptoint,+sign-ext,-bulk-memory,-multivalue,-reference-types", "build-tags": ["tinygo.wasm", "wasm_unknown"], "buildmode": "c-shared", "goos": "linux", "goarch": "arm", "linker": "wasm-ld", "rtlib": "compiler-rt", "libc": "wasmbuiltins", "scheduler": "none", "gc": "leaking", "default-stack-size": 4096, "cflags": [ "-mnontrapping-fptoint", "-mno-bulk-memory", "-mno-multivalue", "-mno-reference-types", "-msign-ext" ], "ldflags": [ "--stack-first", "--no-demangle", "--no-entry" ], "extra-files": [ "src/runtime/asm_tinygowasm.S" ], "emulator": "wasmtime run --dir={tmpDir}::/tmp {}" } ================================================ FILE: targets/wasm.json ================================================ { "llvm-target": "wasm32-unknown-wasi", "cpu": "generic", "features": "+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types", "build-tags": ["tinygo.wasm"], "goos": "js", "goarch": "wasm", "linker": "wasm-ld", "libc": "wasi-libc", "rtlib": "compiler-rt", "gc": "precise", "scheduler": "asyncify", "default-stack-size": 65536, "cflags": [ "-mbulk-memory", "-mnontrapping-fptoint", "-mno-multivalue", "-mno-reference-types", "-msign-ext" ], "ldflags": [ "--allow-undefined-file={root}/targets/wasm-undefined.txt", "--stack-first", "--no-demangle" ], "extra-files": [ "src/runtime/asm_tinygowasm.S", "src/runtime/gc_boehm.c" ], "emulator": "node {root}/targets/wasm_exec.js {}" } ================================================ FILE: targets/wasm_exec.js ================================================ // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // // This file has been modified for use by the TinyGo compiler. (() => { // Map multiple JavaScript environments to a single common API, // preferring web standards over Node.js API. // // Environments considered: // - Browsers // - Node.js // - Electron // - Parcel if (typeof global !== "undefined") { // global already exists } else if (typeof window !== "undefined") { window.global = window; } else if (typeof self !== "undefined") { self.global = self; } else { throw new Error("cannot export Go (neither global, window nor self is defined)"); } if (!global.require && typeof require !== "undefined") { global.require = require; } if (!global.fs && global.require) { global.fs = require("node:fs"); } const enosys = () => { const err = new Error("not implemented"); err.code = "ENOSYS"; return err; }; if (!global.fs) { let outputBuf = ""; global.fs = { constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused writeSync(fd, buf) { outputBuf += decoder.decode(buf); const nl = outputBuf.lastIndexOf("\n"); if (nl != -1) { console.log(outputBuf.substr(0, nl)); outputBuf = outputBuf.substr(nl + 1); } return buf.length; }, write(fd, buf, offset, length, position, callback) { if (offset !== 0 || length !== buf.length || position !== null) { callback(enosys()); return; } const n = this.writeSync(fd, buf); callback(null, n); }, chmod(path, mode, callback) { callback(enosys()); }, chown(path, uid, gid, callback) { callback(enosys()); }, close(fd, callback) { callback(enosys()); }, fchmod(fd, mode, callback) { callback(enosys()); }, fchown(fd, uid, gid, callback) { callback(enosys()); }, fstat(fd, callback) { callback(enosys()); }, fsync(fd, callback) { callback(null); }, ftruncate(fd, length, callback) { callback(enosys()); }, lchown(path, uid, gid, callback) { callback(enosys()); }, link(path, link, callback) { callback(enosys()); }, lstat(path, callback) { callback(enosys()); }, mkdir(path, perm, callback) { callback(enosys()); }, open(path, flags, mode, callback) { callback(enosys()); }, read(fd, buffer, offset, length, position, callback) { callback(enosys()); }, readdir(path, callback) { callback(enosys()); }, readlink(path, callback) { callback(enosys()); }, rename(from, to, callback) { callback(enosys()); }, rmdir(path, callback) { callback(enosys()); }, stat(path, callback) { callback(enosys()); }, symlink(path, link, callback) { callback(enosys()); }, truncate(path, length, callback) { callback(enosys()); }, unlink(path, callback) { callback(enosys()); }, utimes(path, atime, mtime, callback) { callback(enosys()); }, }; } if (!global.process) { global.process = { getuid() { return -1; }, getgid() { return -1; }, geteuid() { return -1; }, getegid() { return -1; }, getgroups() { throw enosys(); }, pid: -1, ppid: -1, umask() { throw enosys(); }, cwd() { throw enosys(); }, chdir() { throw enosys(); }, } } if (!global.crypto) { const nodeCrypto = require("node:crypto"); global.crypto = { getRandomValues(b) { nodeCrypto.randomFillSync(b); }, }; } if (!global.performance) { global.performance = { now() { const [sec, nsec] = process.hrtime(); return sec * 1000 + nsec / 1000000; }, }; } if (!global.TextEncoder) { global.TextEncoder = require("node:util").TextEncoder; } if (!global.TextDecoder) { global.TextDecoder = require("node:util").TextDecoder; } // End of polyfills for common API. const encoder = new TextEncoder("utf-8"); const decoder = new TextDecoder("utf-8"); let reinterpretBuf = new DataView(new ArrayBuffer(8)); var logLine = []; const wasmExit = {}; // thrown to exit via proc_exit (not an error) global.Go = class { constructor() { this._callbackTimeouts = new Map(); this._nextCallbackTimeoutID = 1; const mem = () => { // The buffer may change when requesting more memory. return new DataView(this._inst.exports.memory.buffer); } const unboxValue = (v_ref) => { reinterpretBuf.setBigInt64(0, v_ref, true); const f = reinterpretBuf.getFloat64(0, true); if (f === 0) { return undefined; } if (!isNaN(f)) { return f; } const id = v_ref & 0xffffffffn; return this._values[id]; } const loadValue = (addr) => { let v_ref = mem().getBigUint64(addr, true); return unboxValue(v_ref); } const boxValue = (v) => { const nanHead = 0x7FF80000n; if (typeof v === "number") { if (isNaN(v)) { return nanHead << 32n; } if (v === 0) { return (nanHead << 32n) | 1n; } reinterpretBuf.setFloat64(0, v, true); return reinterpretBuf.getBigInt64(0, true); } switch (v) { case undefined: return 0n; case null: return (nanHead << 32n) | 2n; case true: return (nanHead << 32n) | 3n; case false: return (nanHead << 32n) | 4n; } let id = this._ids.get(v); if (id === undefined) { id = this._idPool.pop(); if (id === undefined) { id = BigInt(this._values.length); } this._values[id] = v; this._goRefCounts[id] = 0; this._ids.set(v, id); } this._goRefCounts[id]++; let typeFlag = 1n; switch (typeof v) { case "string": typeFlag = 2n; break; case "symbol": typeFlag = 3n; break; case "function": typeFlag = 4n; break; } return id | ((nanHead | typeFlag) << 32n); } const storeValue = (addr, v) => { let v_ref = boxValue(v); mem().setBigUint64(addr, v_ref, true); } const loadSlice = (array, len, cap) => { return new Uint8Array(this._inst.exports.memory.buffer, array, len); } const loadSliceOfValues = (array, len, cap) => { const a = new Array(len); for (let i = 0; i < len; i++) { a[i] = loadValue(array + i * 8); } return a; } const loadString = (ptr, len) => { return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr, len)); } const timeOrigin = Date.now() - performance.now(); this.importObject = { wasi_snapshot_preview1: { // https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_write fd_write: function(fd, iovs_ptr, iovs_len, nwritten_ptr) { let nwritten = 0; if (fd == 1) { for (let iovs_i=0; iovs_i 0, // dummy fd_fdstat_get: () => 0, // dummy fd_seek: () => 0, // dummy proc_exit: (code) => { this.exited = true; this.exitCode = code; this._resolveExitPromise(); throw wasmExit; }, random_get: (bufPtr, bufLen) => { crypto.getRandomValues(loadSlice(bufPtr, bufLen)); return 0; }, }, gojs: { // func ticks() int64 "runtime.ticks": () => { return BigInt((timeOrigin + performance.now()) * 1e6); }, // func sleepTicks(timeout int64) "runtime.sleepTicks": (timeout) => { // Do not sleep, only reactivate scheduler after the given timeout. setTimeout(() => { if (this.exited) return; try { this._inst.exports.go_scheduler(); } catch (e) { if (e !== wasmExit) throw e; } }, Number(timeout)/1e6); }, // func finalizeRef(v ref) "syscall/js.finalizeRef": (v_ref) => { // Note: TinyGo does not support finalizers so this is only called // for one specific case, by js.go:jsString. and can/might leak memory. const id = v_ref & 0xffffffffn; if (this._goRefCounts?.[id] !== undefined) { this._goRefCounts[id]--; if (this._goRefCounts[id] === 0) { const v = this._values[id]; this._values[id] = null; this._ids.delete(v); this._idPool.push(id); } } else { console.error("syscall/js.finalizeRef: unknown id", id); } }, // func stringVal(value string) ref "syscall/js.stringVal": (value_ptr, value_len) => { value_ptr >>>= 0; const s = loadString(value_ptr, value_len); return boxValue(s); }, // func valueGet(v ref, p string) ref "syscall/js.valueGet": (v_ref, p_ptr, p_len) => { let prop = loadString(p_ptr, p_len); let v = unboxValue(v_ref); let result = Reflect.get(v, prop); return boxValue(result); }, // func valueSet(v ref, p string, x ref) "syscall/js.valueSet": (v_ref, p_ptr, p_len, x_ref) => { const v = unboxValue(v_ref); const p = loadString(p_ptr, p_len); const x = unboxValue(x_ref); Reflect.set(v, p, x); }, // func valueDelete(v ref, p string) "syscall/js.valueDelete": (v_ref, p_ptr, p_len) => { const v = unboxValue(v_ref); const p = loadString(p_ptr, p_len); Reflect.deleteProperty(v, p); }, // func valueIndex(v ref, i int) ref "syscall/js.valueIndex": (v_ref, i) => { return boxValue(Reflect.get(unboxValue(v_ref), i)); }, // valueSetIndex(v ref, i int, x ref) "syscall/js.valueSetIndex": (v_ref, i, x_ref) => { Reflect.set(unboxValue(v_ref), i, unboxValue(x_ref)); }, // func valueCall(v ref, m string, args []ref) (ref, bool) "syscall/js.valueCall": (ret_addr, v_ref, m_ptr, m_len, args_ptr, args_len, args_cap) => { const v = unboxValue(v_ref); const name = loadString(m_ptr, m_len); const args = loadSliceOfValues(args_ptr, args_len, args_cap); try { const m = Reflect.get(v, name); storeValue(ret_addr, Reflect.apply(m, v, args)); mem().setUint8(ret_addr + 8, 1); } catch (err) { storeValue(ret_addr, err); mem().setUint8(ret_addr + 8, 0); } }, // func valueInvoke(v ref, args []ref) (ref, bool) "syscall/js.valueInvoke": (ret_addr, v_ref, args_ptr, args_len, args_cap) => { try { const v = unboxValue(v_ref); const args = loadSliceOfValues(args_ptr, args_len, args_cap); storeValue(ret_addr, Reflect.apply(v, undefined, args)); mem().setUint8(ret_addr + 8, 1); } catch (err) { storeValue(ret_addr, err); mem().setUint8(ret_addr + 8, 0); } }, // func valueNew(v ref, args []ref) (ref, bool) "syscall/js.valueNew": (ret_addr, v_ref, args_ptr, args_len, args_cap) => { const v = unboxValue(v_ref); const args = loadSliceOfValues(args_ptr, args_len, args_cap); try { storeValue(ret_addr, Reflect.construct(v, args)); mem().setUint8(ret_addr + 8, 1); } catch (err) { storeValue(ret_addr, err); mem().setUint8(ret_addr+ 8, 0); } }, // func valueLength(v ref) int "syscall/js.valueLength": (v_ref) => { return unboxValue(v_ref).length; }, // valuePrepareString(v ref) (ref, int) "syscall/js.valuePrepareString": (ret_addr, v_ref) => { const s = String(unboxValue(v_ref)); const str = encoder.encode(s); storeValue(ret_addr, str); mem().setInt32(ret_addr + 8, str.length, true); }, // valueLoadString(v ref, b []byte) "syscall/js.valueLoadString": (v_ref, slice_ptr, slice_len, slice_cap) => { const str = unboxValue(v_ref); loadSlice(slice_ptr, slice_len, slice_cap).set(str); }, // func valueInstanceOf(v ref, t ref) bool "syscall/js.valueInstanceOf": (v_ref, t_ref) => { return unboxValue(v_ref) instanceof unboxValue(t_ref); }, // func copyBytesToGo(dst []byte, src ref) (int, bool) "syscall/js.copyBytesToGo": (ret_addr, dest_addr, dest_len, dest_cap, src_ref) => { let num_bytes_copied_addr = ret_addr; let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable const dst = loadSlice(dest_addr, dest_len); const src = unboxValue(src_ref); if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { mem().setUint8(returned_status_addr, 0); // Return "not ok" status return; } const toCopy = src.subarray(0, dst.length); dst.set(toCopy); mem().setUint32(num_bytes_copied_addr, toCopy.length, true); mem().setUint8(returned_status_addr, 1); // Return "ok" status }, // copyBytesToJS(dst ref, src []byte) (int, bool) // Originally copied from upstream Go project, then modified: // https://github.com/golang/go/blob/3f995c3f3b43033013013e6c7ccc93a9b1411ca9/misc/wasm/wasm_exec.js#L404-L416 "syscall/js.copyBytesToJS": (ret_addr, dst_ref, src_addr, src_len, src_cap) => { let num_bytes_copied_addr = ret_addr; let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable const dst = unboxValue(dst_ref); const src = loadSlice(src_addr, src_len); if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { mem().setUint8(returned_status_addr, 0); // Return "not ok" status return; } const toCopy = src.subarray(0, dst.length); dst.set(toCopy); mem().setUint32(num_bytes_copied_addr, toCopy.length, true); mem().setUint8(returned_status_addr, 1); // Return "ok" status }, } }; // Go 1.20 uses 'env'. Go 1.21 uses 'gojs'. // For compatibility, we use both as long as Go 1.20 is supported. this.importObject.env = this.importObject.gojs; } async run(instance) { this._inst = instance; this._values = [ // JS values that Go currently has references to, indexed by reference id NaN, 0, null, true, false, global, this, ]; this._goRefCounts = []; // number of references that Go has to a JS value, indexed by reference id this._ids = new Map(); // mapping from JS values to reference ids this._idPool = []; // unused ids that have been garbage collected this.exited = false; // whether the Go program has exited this.exitCode = 0; if (this._inst.exports._start) { let exitPromise = new Promise((resolve, reject) => { this._resolveExitPromise = resolve; }); // Run program, but catch the wasmExit exception that's thrown // to return back here. try { this._inst.exports._start(); } catch (e) { if (e !== wasmExit) throw e; } await exitPromise; return this.exitCode; } else { this._inst.exports._initialize(); } } _resume() { if (this.exited) { throw new Error("Go program has already exited"); } try { this._inst.exports.resume(); } catch (e) { if (e !== wasmExit) throw e; } if (this.exited) { this._resolveExitPromise(); } } _makeFuncWrapper(id) { const go = this; return function () { const event = { id: id, this: this, args: arguments }; go._pendingEvent = event; go._resume(); return event.result; }; } } if ( global.require && global.require.main === module && global.process && global.process.versions && !global.process.versions.electron ) { if (process.argv.length != 3) { console.error("usage: go_js_wasm_exec [wasm binary] [arguments]"); process.exit(1); } const go = new Go(); WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then(async (result) => { let exitCode = await go.run(result.instance); process.exit(exitCode); }).catch((err) => { console.error(err); process.exit(1); }); } })(); ================================================ FILE: targets/waveshare-rp2040-tiny.json ================================================ { "inherits": [ "rp2040" ], "serial-port": ["2e8a:0003"], "default-stack-size": 8192, "build-tags": ["waveshare_rp2040_tiny"], "ldflags": [ "--defsym=__flash_size=1020K" ], "extra-files": [ "targets/pico-boot-stage2.S" ] } ================================================ FILE: targets/waveshare-rp2040-zero.json ================================================ { "inherits": [ "rp2040" ], "serial-port": ["2e8a:0003"], "default-stack-size": 8192, "build-tags": ["waveshare_rp2040_zero"], "ldflags": [ "--defsym=__flash_size=1020K" ], "extra-files": [ "targets/pico-boot-stage2.S" ] } ================================================ FILE: targets/wioterminal.json ================================================ { "inherits": ["atsamd51p19a"], "build-tags": ["wioterminal"], "serial": "usb", "serial-port": ["2886:802d"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": ["Arduino"], "msd-firmware-name": "firmware.uf2", "openocd-verify": true } ================================================ FILE: targets/x9pro.json ================================================ { "inherits": ["nrf52"], "build-tags": ["x9pro"], "serial": "uart", "flash-method": "openocd", "flash-command": "nrfjprog -f nrf52 --sectorerase --program {hex} --reset", "openocd-interface": "jlink", "openocd-transport": "swd" } ================================================ FILE: targets/xiao-ble.json ================================================ { "inherits": ["nrf52840", "nrf52840-s140v7-uf2"], "build-tags": ["xiao_ble"], "serial-port": ["2886:8045"], "msd-volume-name": ["XIAO-SENSE"] } ================================================ FILE: targets/xiao-esp32c3.json ================================================ { "inherits": ["esp32c3"], "build-tags": ["xiao_esp32c3"] } ================================================ FILE: targets/xiao-esp32s3.json ================================================ { "inherits": ["esp32s3"], "build-tags": ["xiao_esp32s3"] } ================================================ FILE: targets/xiao-rp2040.json ================================================ { "inherits": [ "rp2040" ], "serial-port": ["2e8a:000a"], "default-stack-size": 8192, "build-tags": ["xiao_rp2040"], "ldflags": [ "--defsym=__flash_size=1020K" ], "extra-files": [ "targets/pico-boot-stage2.S" ] } ================================================ FILE: targets/xiao.json ================================================ { "inherits": ["atsamd21g18a"], "build-tags": ["xiao"], "serial": "usb", "serial-port": ["2886:802f"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": ["Arduino","Seeed XIAO"], "msd-firmware-name": "firmware.uf2" } ================================================ FILE: targets/xtensa.json ================================================ { "llvm-target": "xtensa", "goos": "linux", "goarch": "arm", "build-tags": ["xtensa", "baremetal", "linux", "arm"], "gc": "conservative", "scheduler": "none", "cflags": [ "-Werror", "-fshort-enums", "-Wno-macro-redefined", "-fno-exceptions", "-fno-unwind-tables", "-fno-asynchronous-unwind-tables", "-ffunction-sections", "-fdata-sections" ], "ldflags": [ "--gc-sections" ] } ================================================ FILE: testdata/alias.go ================================================ package main type x struct{} func (x x) name() string { return "x" } type y = x type a struct { n int } func (a a) fruit() string { return "apple" } type b = a type fruit interface { fruit() string } type f = fruit func main() { // test a basic alias println(y{}.name()) // test using a type alias value as an interface var v f = b{} println(v.fruit()) // test comparing an alias interface with the referred-to type println(a{} == b{}) println(a{2} == b{3}) } ================================================ FILE: testdata/alias.txt ================================================ x apple true false ================================================ FILE: testdata/atomic.go ================================================ package main import ( "sync/atomic" "unsafe" "runtime/volatile" ) func main() { i32 := int32(-5) println("AddInt32:", atomic.AddInt32(&i32, 8), i32) i64 := int64(-5) println("AddInt64:", atomic.AddInt64(&i64, 8), i64) u32 := uint32(5) println("AddUint32:", atomic.AddUint32(&u32, 8), u32) u64 := uint64(5) println("AddUint64:", atomic.AddUint64(&u64, 8), u64) uptr := uintptr(5) println("AddUintptr:", uint64(atomic.AddUintptr(&uptr, 8)), uint64(uptr)) println("SwapInt32:", atomic.SwapInt32(&i32, 33), i32) println("SwapInt64:", atomic.SwapInt64(&i64, 33), i64) println("SwapUint32:", atomic.SwapUint32(&u32, 33), u32) println("SwapUint64:", atomic.SwapUint64(&u64, 33), u64) println("SwapUintptr:", uint64(atomic.SwapUintptr(&uptr, 33)), uint64(uptr)) ptr := unsafe.Pointer(&i32) println("SwapPointer:", atomic.SwapPointer(&ptr, unsafe.Pointer(&u32)) == unsafe.Pointer(&i32), ptr == unsafe.Pointer(&u32)) i32 = int32(-5) println("CompareAndSwapInt32:", atomic.CompareAndSwapInt32(&i32, 5, 3), i32) println("CompareAndSwapInt32:", atomic.CompareAndSwapInt32(&i32, -5, 3), i32) i64 = int64(-5) println("CompareAndSwapInt64:", atomic.CompareAndSwapInt64(&i64, 5, 3), i64) println("CompareAndSwapInt64:", atomic.CompareAndSwapInt64(&i64, -5, 3), i64) u32 = uint32(5) println("CompareAndSwapUint32:", atomic.CompareAndSwapUint32(&u32, 4, 3), u32) println("CompareAndSwapUint32:", atomic.CompareAndSwapUint32(&u32, 5, 3), u32) u64 = uint64(5) println("CompareAndSwapUint64:", atomic.CompareAndSwapUint64(&u64, 4, 3), u64) println("CompareAndSwapUint64:", atomic.CompareAndSwapUint64(&u64, 5, 3), u64) uptr = uintptr(5) println("CompareAndSwapUintptr:", atomic.CompareAndSwapUintptr(&uptr, 4, 3), uint64(uptr)) println("CompareAndSwapUintptr:", atomic.CompareAndSwapUintptr(&uptr, 5, 3), uint64(uptr)) ptr = unsafe.Pointer(&i32) println("CompareAndSwapPointer:", atomic.CompareAndSwapPointer(&ptr, unsafe.Pointer(&u32), unsafe.Pointer(&i64)), ptr == unsafe.Pointer(&i32)) println("CompareAndSwapPointer:", atomic.CompareAndSwapPointer(&ptr, unsafe.Pointer(&i32), unsafe.Pointer(&i64)), ptr == unsafe.Pointer(&i64)) println("LoadInt32:", atomic.LoadInt32(&i32)) println("LoadInt64:", atomic.LoadInt64(&i64)) println("LoadUint32:", atomic.LoadUint32(&u32)) println("LoadUint64:", atomic.LoadUint64(&u64)) println("LoadUintptr:", uint64(atomic.LoadUintptr(&uptr))) println("LoadPointer:", atomic.LoadPointer(&ptr) == unsafe.Pointer(&i64)) atomic.StoreInt32(&i32, -20) println("StoreInt32:", i32) atomic.StoreInt64(&i64, -20) println("StoreInt64:", i64) atomic.StoreUint32(&u32, 20) println("StoreUint32:", u32) atomic.StoreUint64(&u64, 20) println("StoreUint64:", u64) atomic.StoreUintptr(&uptr, 20) println("StoreUintptr:", uint64(uptr)) atomic.StorePointer(&ptr, unsafe.Pointer(&uptr)) println("StorePointer:", ptr == unsafe.Pointer(&uptr)) // test atomic.Value load/store operations testValue(int(3), int(-2)) testValue("", "foobar", "baz") // Test atomic and volatile operations as deferred values. testDefer() } func testValue(values ...interface{}) { var av atomic.Value for _, val := range values { av.Store(val) loadedVal := av.Load() if loadedVal != val { println("val store/load didn't work, expected", val, "but got", loadedVal) } } } func testDefer() { n1 := int32(5) n2 := uint32(6) defer func() { println("deferred atomic add:", n1) println("deferred volatile store:", n2) }() defer atomic.AddInt32(&n1, 3) defer volatile.StoreUint32(&n2, 22) } ================================================ FILE: testdata/atomic.txt ================================================ AddInt32: 3 3 AddInt64: 3 3 AddUint32: 13 13 AddUint64: 13 13 AddUintptr: 13 13 SwapInt32: 3 33 SwapInt64: 3 33 SwapUint32: 13 33 SwapUint64: 13 33 SwapUintptr: 13 33 SwapPointer: true true CompareAndSwapInt32: false -5 CompareAndSwapInt32: true 3 CompareAndSwapInt64: false -5 CompareAndSwapInt64: true 3 CompareAndSwapUint32: false 5 CompareAndSwapUint32: true 3 CompareAndSwapUint64: false 5 CompareAndSwapUint64: true 3 CompareAndSwapUintptr: false 5 CompareAndSwapUintptr: true 3 CompareAndSwapPointer: false true CompareAndSwapPointer: true true LoadInt32: 3 LoadInt64: 3 LoadUint32: 3 LoadUint64: 3 LoadUintptr: 3 LoadPointer: true StoreInt32: -20 StoreInt64: -20 StoreUint32: 20 StoreUint64: 20 StoreUintptr: 20 StorePointer: true deferred atomic add: 8 deferred volatile store: 22 ================================================ FILE: testdata/binop.go ================================================ package main func main() { println("string equality") println(a == "a") println(a == "b") println(a != "a") println(a != "b") println("string inequality") println(a > "b") println("b" > a) println("b" > "b") println(a <= "b") println("b" <= a) println("b" <= "b") println(a > "b") println("b" > a) println("b" > "b") println(a <= "b") println("b" <= a) println("b" <= "b") println(a < "aa") println("aa" < a) println("ab" < "aa") println("aa" < "ab") h := "hello" println("h < h", h < h) println("h <= h", h <= h) println("h == h", h == h) println("h >= h", h >= h) println("h > h", h > h) println("array equality") println(a1 == [2]int{1, 2}) println(a1 != [2]int{1, 2}) println(a1 == [2]int{1, 3}) println(a1 == [2]int{2, 2}) println(a1 == [2]int{2, 1}) println(a1 != [2]int{2, 1}) println("struct equality") println(s1 == Struct1{3, true}) println(s1 == Struct1{4, true}) println(s1 == Struct1{3, false}) println(s1 == Struct1{4, false}) println(s1 != Struct1{3, true}) println(s1 != Struct1{4, true}) println(s1 != Struct1{3, false}) println(s1 != Struct1{4, false}) println("blank fields in structs") println(s2 == Struct2{"foo", 0.0, 5}) println(s2 == Struct2{"foo", 0.0, 7}) println(s2 == Struct2{"foo", 1.0, 5}) println(s2 == Struct2{"foo", 1.0, 7}) println("complex numbers") println(c64 == 3+2i) println(c64 == 4+2i) println(c64 == 3+3i) println(c64 != 3+2i) println(c64 != 4+2i) println(c64 != 3+3i) println(c128 == 3+2i) println(c128 == 4+2i) println(c128 == 3+3i) println(c128 != 3+2i) println(c128 != 4+2i) println(c128 != 3+3i) println("shifts") println(shlSimple == 4) println(shlOverflow == 0) println(shrSimple == 1) println(shrOverflow == 0) println(ashrNeg == -1) println(ashrOverflow == 0) println(ashrNegOverflow == -1) // fix for a bug in constant numbers println("constant number") x := uint32(5) println(uint32(x) / (20e0 / 1)) // check for signed integer overflow println("-2147483648 / -1:", sdiv32(-2147483648, -1)) println("-2147483648 % -1:", srem32(-2147483648, -1)) type foo struct { n int itf interface{} } var a interface{} = foo{3, float32(5)} var b interface{} = foo{3, float32(3)} var b2 interface{} = foo{3, float32(3)} println("interface equality") println("a==b", a == b) println("b==b2", b == b2) } var x = true var y = false var a = "a" var s1 = Struct1{3, true} var s2 = Struct2{"foo", 0.0, 5} var a1 = [2]int{1, 2} var c64 = 3 + 2i var c128 = 4 + 3i type Int int type Struct1 struct { i Int b bool } type Struct2 struct { s string _ float64 i int } func shl(x uint, y uint) uint { return x << y } func shr(x uint, y uint) uint { return x >> y } func ashr(x int, y uint) int { return x >> y } func sdiv32(x, y int32) int32 { return x / y } func srem32(x, y int32) int32 { return x % y } var shlSimple = shl(2, 1) var shlOverflow = shl(2, 1000) var shrSimple = shr(2, 1) var shrOverflow = shr(2, 1000000) var ashrNeg = ashr(-1, 1) var ashrOverflow = ashr(1, 1000000) var ashrNegOverflow = ashr(-1, 1000000) ================================================ FILE: testdata/binop.txt ================================================ string equality true false false true string inequality false true false true false true false true false true false true true false false true h < h false h <= h true h == h true h >= h true h > h false array equality true false false false false true struct equality true false false false false true true true blank fields in structs true false true false complex numbers true false false false true true false false false true true true shifts true true true true true true true constant number 0 -2147483648 / -1: -2147483648 -2147483648 % -1: 0 interface equality a==b false b==b2 true ================================================ FILE: testdata/calls.go ================================================ package main type Thing struct { name string } type ThingOption func(*Thing) func WithName(name string) ThingOption { return func(t *Thing) { t.name = name } } func NewThing(opts ...ThingOption) *Thing { t := &Thing{} for _, opt := range opts { opt(t) } return t } func (t Thing) String() string { return t.name } func (t Thing) Print(arg string) { println("Thing.Print:", t.name, "arg:", arg) } type Printer interface { Print(string) } func main() { thing := &Thing{"foo"} // function pointers runFunc(hello, 5) // must be indirect to avoid obvious inlining // deferred functions testDefer() // defers in loop testDeferLoop() //defer func variable call testDeferFuncVar() //More complicated func variable call testMultiFuncVar() // Take a bound method and use it as a function pointer. // This function pointer needs a context pointer. testBound(thing.String) // closures func() { println("thing inside closure:", thing.String()) }() runFunc(func(i int) { println("inside fp closure:", thing.String(), i) }, 3) // functional arguments thingFunctionalArgs1 := NewThing() thingFunctionalArgs1.Print("functional args 1") thingFunctionalArgs2 := NewThing(WithName("named thing")) thingFunctionalArgs2.Print("functional args 2") // regression testing regression1033() //Test deferred builtins testDeferBuiltinClose() testDeferBuiltinDelete() // Check for issue 1304. // There are two fields in this struct, one of which is zero-length so the // other covers the entire struct. This led to a verification error for // debug info, which used DW_OP_LLVM_fragment for a field that practically // covered the entire variable. var x issue1304 x.call() if testDeferElse(false) != 0 { println("else defer returned wrong value") } } func runFunc(f func(int), arg int) { f(arg) } func hello(n int) { println("hello from function pointer:", n) } func testDefer() { defer exportedDefer() i := 1 defer deferred("...run as defer", i) i++ defer func() { println("...run closure deferred:", i) }() i++ defer deferred("...run as defer", i) i++ var t Printer = &Thing{"foo"} defer t.Print("bar") println("deferring...") d := dumb{} defer d.Value(0) } func testDeferLoop() { for j := 0; j < 4; j++ { defer deferred("loop", j) } } func testDeferFuncVar() { dummy, f := deferFunc() dummy++ defer f(1) } func testMultiFuncVar() { f := multiFuncDefer() defer f(1) } func testDeferBuiltinClose() { i := make(chan int) func() { defer close(i) }() if n, ok := <-i; n != 0 || ok { println("expected to read 0 from closed channel") } } func testDeferBuiltinDelete() { m := map[int]int{3: 30, 5: 50} func() { defer delete(m, 3) if m[3] != 30 { println("expected m[3] to be 30") } }() if m[3] != 0 { println("expected m[3] to be 0") } } type dumb struct { } func (*dumb) Value(key interface{}) interface{} { return nil } func deferred(msg string, i int) { println(msg, i) } //export __exportedDefer func exportedDefer() { println("...exported defer") } func deferFunc() (int, func(int)) { return 0, func(i int) { println("...extracted defer func ", i) } } func multiFuncDefer() func(int) { i := 0 if i > 0 { return func(i int) { println("Should not have gotten here. i = ", i) } } return func(i int) { println("Called the correct function. i = ", i) } } func testBound(f func() string) { println("bound method:", f()) } // regression1033 is a regression test for https://github.com/tinygo-org/tinygo/issues/1033. // In previous versions of the compiler, a deferred call to an interface would create an instruction that did not dominate its uses. func regression1033() { foo(&Bar{}) } type Bar struct { empty bool } func (b *Bar) Close() error { return nil } type Closer interface { Close() error } func foo(bar *Bar) error { var a int if !bar.empty { a = 10 if a != 5 { return nil } } var c Closer = bar defer c.Close() return nil } type issue1304 struct { a [0]int // zero-length field b int // field 'b' covers entire struct } func (x issue1304) call() { // nothing to do } type recursiveFuncType func(recursiveFuncType) var recursiveFunction recursiveFuncType //go:noinline func testDeferElse(b bool) (r int) { if !b { defer func() { r = 0 }() } return 1 } ================================================ FILE: testdata/calls.txt ================================================ hello from function pointer: 5 deferring... Thing.Print: foo arg: bar ...run as defer 3 ...run closure deferred: 4 ...run as defer 1 ...exported defer loop 3 loop 2 loop 1 loop 0 ...extracted defer func 1 Called the correct function. i = 1 bound method: foo thing inside closure: foo inside fp closure: foo 3 Thing.Print: arg: functional args 1 Thing.Print: named thing arg: functional args 2 ================================================ FILE: testdata/cgo/extra.go ================================================ package main // Make sure CGo supports multiple files. // #include "test.h" // int fortytwo(void); // static float headerfunc_static(float a) { return a - 1; } // static void headerfunc_void(int a, int *ptr) { *ptr = a; } import "C" func headerfunc_2() { // Call headerfunc_static that is different from the headerfunc_static in // the main.go file. // The upstream CGo implementation does not handle this case correctly. println("static headerfunc 2:", C.headerfunc_static(5)) // Test function without return value. var n C.int C.headerfunc_void(3, &n) println("static headerfunc void:", n) // anonymous structs and enums in multiple Go files var _ C.teststruct var _ C.testenum } ================================================ FILE: testdata/cgo/main.c ================================================ #include #include "main.h" #include int global = 3; bool globalBool = 1; bool globalBool2 = 10; // test narrowing float globalFloat = 3.1; double globalDouble = 3.2; _Complex float globalComplexFloat = 4.1+3.3i; _Complex double globalComplexDouble = 4.2+3.4i; _Complex double globalComplexLongDouble = 4.3+3.5i; char globalChar = 100; void *globalVoidPtrSet = &global; void *globalVoidPtrNull; int64_t globalInt64 = -(2LL << 40); collection_t globalStruct = {256, -123456, 3.14, 88}; int globalStructSize = sizeof(globalStruct); short globalArray[3] = {5, 6, 7}; joined_t globalUnion; int globalUnionSize = sizeof(globalUnion); option_t globalOption = optionG; bitfield_t globalBitfield = {244, 15, 1, 2, 47, 5}; int cflagsConstant = SOME_CONSTANT; int smallEnumWidth = sizeof(option2_t); char globalChars[] = {2, 0, 4, 8}; int fortytwo() { return 42; } int add(int a, int b) { return a + b; } int doCallback(int a, int b, binop_t callback) { return callback(a, b); } int variadic0() { return 1; } int variadic2(int x, int y, ...) { return x * y; } void store(int value, int *ptr) { *ptr = value; } void unionSetShort(short s) { globalUnion.s = s; } void unionSetFloat(float f) { globalUnion.f = f; } void unionSetData(short f0, short f1, short f2) { globalUnion.data[0] = 5; globalUnion.data[1] = 8; globalUnion.data[2] = 1; } void arraydecay(int buf1[5], int buf2[3][8], int buf3[4][7][2]) { // Do nothing. } double doSqrt(double x) { return sqrt(x); } void printf_single_int(char *format, int arg) { printf(format, arg); } int set_errno(int err) { errno = err; return -1; } ================================================ FILE: testdata/cgo/main.go ================================================ package main /* #include #include int fortytwo(void); #include "main.h" #include "test.h" int mul(int, int); #include #cgo CFLAGS: -DSOME_CONSTANT=17 #define someDefine -5 + 2 * 7 bool someBool; */ import "C" // int headerfunc(int a) { return a + 1; } // static int headerfunc_static(int a) { return a - 1; } import "C" import ( "syscall" "unsafe" ) func main() { println("fortytwo:", C.fortytwo()) println("add:", C.add(C.int(3), 5)) var x C.myint = 3 println("myint:", x, C.myint(5)) println("myint size:", int(unsafe.Sizeof(x))) var y C.longlong = -(1 << 40) println("longlong:", y) println("global:", C.global) println("defined ints:", C.CONST_INT, C.CONST_INT2) println("defined floats:", C.CONST_FLOAT, C.CONST_FLOAT2) println("defined string:", C.CONST_STRING) println("defined char:", C.CONST_CHAR) println("defined expr:", C.someDefine) var ptr C.intPointer var n C.int = 15 ptr = C.intPointer(&n) println("15:", *ptr) C.store(25, &n) println("25:", *ptr) cb := C.binop_t(C.add) println("callback 1:", C.doCallback(20, 30, cb)) cb = C.binop_t(C.mul) println("callback 2:", C.doCallback(20, 30, cb)) genericCallbackCall[int]() // variadic functions println("variadic0:", C.variadic0()) println("variadic2:", C.variadic2(3, 5)) // functions in the header C snippet println("headerfunc:", C.headerfunc(5)) println("static headerfunc:", C.headerfunc_static(5)) headerfunc_2() // equivalent types var goInt8 int8 = 5 var _ C.int8_t = goInt8 var _ bool = C.someBool var _ C._Bool = C.someBool // more globals println("bool:", C.globalBool, C.globalBool2 == true) println("float:", C.globalFloat) println("double:", C.globalDouble) println("complex float:", C.globalComplexFloat) println("complex double:", C.globalComplexDouble) println("complex long double:", C.globalComplexLongDouble) println("char match:", C.globalChar == 100) var voidPtr unsafe.Pointer = C.globalVoidPtrNull println("void* match:", voidPtr == nil, C.globalVoidPtrNull == nil, (*C.int)(C.globalVoidPtrSet) == &C.global) println("int64_t match:", C.globalInt64 == C.int64_t(-(2<<40))) // complex types println("struct:", C.int(unsafe.Sizeof(C.globalStruct)) == C.globalStructSize, C.globalStruct.s, C.globalStruct.l, C.globalStruct.f) var _ [3]C.short = C.globalArray println("array:", C.globalArray[0], C.globalArray[1], C.globalArray[2]) println("union:", C.int(unsafe.Sizeof(C.globalUnion)) == C.globalUnionSize) C.unionSetShort(22) println("union s:", *C.globalUnion.unionfield_s()) C.unionSetFloat(C.float(6.28)) println("union f (C.float):", *C.globalUnion.unionfield_f()) C.unionSetFloat(float32(3.14)) println("union f (float32):", *C.globalUnion.unionfield_f()) C.unionSetData(5, 8, 1) data := C.globalUnion.unionfield_data() println("union global data:", data[0], data[1], data[2]) returnedUnion := printUnion(C.globalUnion) println("union field:", *returnedUnion.unionfield_f()) var _ C.union_joined = C.globalUnion printBitfield(&C.globalBitfield) C.globalBitfield.set_bitfield_a(7) C.globalBitfield.set_bitfield_b(0) C.globalBitfield.set_bitfield_c(0xff) printBitfield(&C.globalBitfield) // elaborated type p := C.struct_point2d{x: 3, y: 5} println("struct:", p.x, p.y) // multiple anonymous structs (inside a typedef) var _ C.point2d_t = C.point2d_t{x: 3, y: 5} var _ C.point3d_t = C.point3d_t{x: 3, y: 5, z: 7} // nested structs/unions var _ C.tagged_union_t var _ C.nested_struct_t var _ C.nested_union_t // recursive types, test using a linked list list := &C.list_t{n: 3, next: &C.struct_list_t{n: 6, next: &C.list_t{n: 7, next: nil}}} for list != nil { println("n in chain:", list.n) list = (*C.list_t)(list.next) } // named enum var _ C.enum_option = C.optionA var _ C.option_t = C.optionA println("option:", C.globalOption) println("option A:", C.optionA) println("option B:", C.optionB) println("option C:", C.optionC) println("option D:", C.optionD) println("option E:", C.optionE) println("option F:", C.optionF) println("option G:", C.optionG) // anonymous enum var _ C.option2_t = C.option2A var _ C.option3_t = C.option3A println("option 2A:", C.option2A) println("option 3A:", C.option3A) // anonymous structs and enums in multiple Go files var _ C.teststruct var _ C.testenum // Check that enums are considered the same width in C and CGo. println("enum width matches:", unsafe.Sizeof(C.option2_t(0)) == uintptr(C.smallEnumWidth)) // Check whether CFLAGS are correctly passed on to compiled C files. println("CFLAGS value:", C.cflagsConstant) // Check array-to-pointer decaying. This signature: // void arraydecay(int buf1[5], int buf2[3][8], int buf3[4][7][2]); // decays to: // void arraydecay(int *buf1, int *buf2[8], int *buf3[7][2]); C.arraydecay((*C.int)(nil), (*[8]C.int)(nil), (*[7][2]C.int)(nil)) // Test CGo builtins like C.CString. cstr := C.CString("string passed to C") println("cstr length:", C.strlen(cstr)) gostr := C.GoString(cstr) println("C.CString:", gostr) charBuf := C.GoBytes(unsafe.Pointer(&C.globalChars[0]), 4) println("C.charBuf:", charBuf[0], charBuf[1], charBuf[2], charBuf[3]) binaryString := C.GoStringN(&C.globalChars[0], 4) println("C.CStringN:", len(binaryString), binaryString[0], binaryString[1], binaryString[2], binaryString[3]) // Test whether those builtins also work with zero length data. println("C.GoString(nil):", C.GoString(nil)) println("len(C.GoStringN(nil, 0)):", len(C.GoStringN(nil, 0))) println("len(C.GoBytes(nil, 0)):", len(C.GoBytes(nil, 0))) println("len(C.GoBytes(C.CBytes(nil),0)):", len(C.GoBytes(C.CBytes(nil), 0))) println(`rountrip CBytes:`, C.GoString((*C.char)(C.CBytes([]byte("hello\000"))))) // Check that errno is returned from the second return value, and that it // matches the errno value that was just set. _, errno := C.set_errno(C.EINVAL) println("EINVAL:", errno == syscall.EINVAL) _, errno = C.set_errno(C.EAGAIN) println("EAGAIN:", errno == syscall.EAGAIN) // libc: test whether C functions work at all. buf1 := []byte("foobar\x00") buf2 := make([]byte, len(buf1)) C.strcpy((*C.char)(unsafe.Pointer(&buf2[0])), (*C.char)(unsafe.Pointer(&buf1[0]))) println("copied string:", string(buf2[:C.strlen((*C.char)(unsafe.Pointer(&buf2[0])))])) // libc: test libm functions (normally bundled in libc) println("CGo sqrt(3):", C.sqrt(3)) println("C sqrt(3):", C.doSqrt(3)) // libc: test basic stdio functionality putsBuf := []byte("line written using C puts\x00") C.puts((*C.char)(unsafe.Pointer(&putsBuf[0]))) // libc: test whether printf works in C. printfBuf := []byte("line written using C printf with value=%d\n\x00") C.printf_single_int((*C.char)(unsafe.Pointer(&printfBuf[0])), -21) } func printUnion(union C.joined_t) C.joined_t { data := union.unionfield_data() println("union local data: ", data[0], data[1], data[2]) *union.unionfield_s() = -33 println("union s:", data[0] == -33) *union.unionfield_f() = 6.28 println("union f:", *union.unionfield_f()) return union } //export mul func mul(a, b C.int) C.int { return a * b } func printBitfield(bitfield *C.bitfield_t) { println("bitfield a:", bitfield.bitfield_a()) println("bitfield b:", bitfield.bitfield_b()) println("bitfield c:", bitfield.bitfield_c()) println("bitfield d:", bitfield.d) println("bitfield e:", bitfield.e) } type Int interface { int | uint } func genericCallbackCall[T Int]() { println("callback inside generic function:", C.doCallback(20, 30, C.binop_t(C.add))) } ================================================ FILE: testdata/cgo/main.h ================================================ #include #include #include typedef short myint; typedef short unusedTypedef; int add(int a, int b); int unusedFunction(void); typedef int (*binop_t) (int, int); int doCallback(int a, int b, binop_t cb); typedef int * intPointer; void store(int value, int *ptr); int variadic0(); int variadic2(int x, int y, ...); # define CONST_INT 5 # define CONST_INT2 5llu # define CONST_FLOAT 5.8 # define CONST_FLOAT2 5.8f # define CONST_CHAR 'c' # define CONST_STRING "defined string" // this signature should not be included by CGo void unusedFunction2(int x, __builtin_va_list args); typedef struct collection { short s; long l; float f; unsigned char c; } collection_t; struct point2d { int x; int y; }; typedef struct { int x; int y; } point2d_t; typedef struct { int x; int y; int z; } point3d_t; typedef struct { int tag; union { int a; int b; } u; } tagged_union_t; typedef struct { int x; struct { char red; char green; char blue; } color; } nested_struct_t; typedef union { int x; struct { char a; char b; }; } nested_union_t; // linked list typedef struct list_t { int n; struct list_t *next; } list_t; typedef union joined { myint s; float f; short data[3]; } joined_t; void unionSetShort(short s); void unionSetFloat(float f); void unionSetData(short f0, short f1, short f2); typedef enum option { optionA, optionB, optionC = -5, optionD, optionE = 10, optionF, optionG, } option_t; typedef enum { option2A = 20, } option2_t; typedef enum { option3A = 21, } option3_t; typedef struct { unsigned char start; unsigned char a : 5; unsigned char b : 1; unsigned char c : 2; unsigned char :0; // new field unsigned char d : 6; unsigned char e : 3; // Note that C++ allows bitfields bigger than the underlying type. } bitfield_t; // test globals and datatypes extern int global; extern int unusedGlobal; extern bool globalBool; extern bool globalBool2; extern float globalFloat; extern double globalDouble; extern _Complex float globalComplexFloat; extern _Complex double globalComplexDouble; extern _Complex double globalComplexLongDouble; extern char globalChar; extern void *globalVoidPtrSet; extern void *globalVoidPtrNull; extern int64_t globalInt64; extern collection_t globalStruct; extern int globalStructSize; extern short globalArray[3]; extern joined_t globalUnion; extern int globalUnionSize; extern option_t globalOption; extern bitfield_t globalBitfield; extern int smallEnumWidth; extern int cflagsConstant; extern char globalChars[4]; // test duplicate definitions int add(int a, int b); extern int global; // Test array decaying into a pointer. typedef int arraydecay_buf3[4][7][2]; void arraydecay(int buf1[5], int buf2[3][8], arraydecay_buf3 buf3); double doSqrt(double); void printf_single_int(char *format, int arg); int set_errno(int err); ================================================ FILE: testdata/cgo/out.txt ================================================ fortytwo: 42 add: 8 myint: 3 5 myint size: 2 longlong: -1099511627776 global: 3 defined ints: 5 5 defined floats: +5.800000e+000 +5.800000e+000 defined string: defined string defined char: 99 defined expr: 9 15: 15 25: 25 callback 1: 50 callback 2: 600 callback inside generic function: 50 variadic0: 1 variadic2: 15 headerfunc: 6 static headerfunc: 4 static headerfunc 2: +4.000000e+000 static headerfunc void: 3 bool: true true float: +3.100000e+000 double: +3.200000e+000 complex float: (+4.100000e+000+3.300000e+000i) complex double: (+4.200000e+000+3.400000e+000i) complex long double: (+4.300000e+000+3.500000e+000i) char match: true void* match: true true true int64_t match: true struct: true 256 -123456 +3.140000e+000 array: 5 6 7 union: true union s: 22 union f (C.float): +6.280000e+000 union f (float32): +3.140000e+000 union global data: 5 8 1 union local data: 5 8 1 union s: true union f: +6.280000e+000 union field: +6.280000e+000 bitfield a: 15 bitfield b: 1 bitfield c: 2 bitfield d: 47 bitfield e: 5 bitfield a: 7 bitfield b: 0 bitfield c: 3 bitfield d: 47 bitfield e: 5 struct: 3 5 n in chain: 3 n in chain: 6 n in chain: 7 option: 12 option A: 0 option B: 1 option C: -5 option D: -4 option E: 10 option F: 11 option G: 12 option 2A: 20 option 3A: 21 enum width matches: true CFLAGS value: 17 cstr length: 18 C.CString: string passed to C C.charBuf: 2 0 4 8 C.CStringN: 4 2 0 4 8 C.GoString(nil): len(C.GoStringN(nil, 0)): 0 len(C.GoBytes(nil, 0)): 0 len(C.GoBytes(C.CBytes(nil),0)): 0 rountrip CBytes: hello EINVAL: true EAGAIN: true copied string: foobar CGo sqrt(3): +1.732051e+000 C sqrt(3): +1.732051e+000 line written using C puts line written using C printf with value=-21 ================================================ FILE: testdata/cgo/test.h ================================================ #pragma once typedef struct { int a; int b; } teststruct; typedef enum { v0, v1 } testenum; ================================================ FILE: testdata/channel.go ================================================ package main import ( "runtime" "sync" "sync/atomic" "time" ) var wg sync.WaitGroup type intchan chan int func main() { ch := make(chan int, 2) ch <- 1 println("len, cap of channel:", len(ch), cap(ch), ch == nil) ch = make(chan int) println("len, cap of channel:", len(ch), cap(ch), ch == nil) wg.Add(1) go sender(ch) n, ok := <-ch println("recv from open channel:", n, ok) for n := range ch { println("received num:", n) } wg.Wait() n, ok = <-ch println("recv from closed channel:", n, ok) // Test various channel size types. _ = make(chan int, int8(2)) _ = make(chan int, int16(2)) _ = make(chan int, int32(2)) _ = make(chan int, int64(2)) _ = make(chan int, uint8(2)) _ = make(chan int, uint16(2)) _ = make(chan int, uint32(2)) _ = make(chan int, uint64(2)) // Test that named channels don't crash the compiler. named := make(intchan, 1) named <- 3 <-named select { case <-named: default: } // Test bigger values ch2 := make(chan complex128) wg.Add(1) go sendComplex(ch2) println("complex128:", <-ch2) wg.Wait() // Test multi-sender. ch = make(chan int) wg.Add(3) go fastsender(ch, 10) go fastsender(ch, 23) go fastsender(ch, 40) slowreceiver(ch) wg.Wait() // Test multi-receiver. ch = make(chan int) wg.Add(3) var result atomic.Uint32 go fastreceiveradd(ch, &result) go fastreceiveradd(ch, &result) go fastreceiveradd(ch, &result) slowsender(ch) wg.Wait() println("sum of sums:", result.Load()) // Test iterator style channel. ch = make(chan int) wg.Add(1) go iterator(ch, 100) sum := 0 for i := range ch { sum += i } wg.Wait() println("sum(100):", sum) // Test simple selects. wg.Add(1) go selectDeadlock() wg.Wait() wg.Add(1) go selectNoOp() wg.Wait() // Test select with a single send operation (transformed into chan send). ch = make(chan int) wg.Add(1) go fastreceiver(ch) select { case ch <- 5: } close(ch) wg.Wait() println("did send one") // Test select with a single recv operation (transformed into chan recv). select { case n := <-ch: println("select one n:", n) } // Test select recv with channel that has one entry. ch = make(chan int) wg.Add(1) go func(ch chan int) { runtime.Gosched() ch <- 55 wg.Done() }(ch) select { case make(chan int) <- 3: println("unreachable") case <-(chan int)(nil): println("unreachable") case n := <-ch: println("select n from chan:", n) case n := <-make(chan int): println("unreachable:", n) } wg.Wait() // Test select recv with closed channel. close(ch) select { case make(chan int) <- 3: println("unreachable") case n := <-ch: println("select n from closed chan:", n) case n := <-make(chan int): println("unreachable:", n) } // Test select send. ch = make(chan int) wg.Add(1) go fastreceiver(ch) select { case ch <- 235: println("select send") case n := <-make(chan int): println("unreachable:", n) } close(ch) wg.Wait() // test non-concurrent buffered channels ch = make(chan int, 2) ch <- 1 ch <- 2 println("non-concurrent channel receive:", <-ch) println("non-concurrent channel receive:", <-ch) // test closing channels with buffered data ch <- 3 ch <- 4 close(ch) println("closed buffered channel receive:", <-ch) println("closed buffered channel receive:", <-ch) println("closed buffered channel receive:", <-ch) // test using buffered channels as regular channels with special properties wg.Add(6) ch = make(chan int, 2) go send(ch) go send(ch) go send(ch) go send(ch) go receive(ch) go receive(ch) wg.Wait() close(ch) var count int for range ch { count++ } println("hybrid buffered channel receive:", count) // test blocking selects ch = make(chan int) sch1 := make(chan int) sch2 := make(chan int) sch3 := make(chan int) wg.Add(3) go func() { defer wg.Done() time.Sleep(time.Millisecond) sch1 <- 1 }() go func() { defer wg.Done() time.Sleep(time.Millisecond) sch2 <- 2 }() go func() { defer wg.Done() // merge sch2 and sch3 into ch for i := 0; i < 2; i++ { var v int select { case v = <-sch1: case v = <-sch2: } select { case sch3 <- v: panic("sent to unused channel") case ch <- v: } } }() sum = 0 for i := 0; i < 2; i++ { select { case sch3 <- sum: panic("sent to unused channel") case v := <-ch: sum += v } } wg.Wait() println("blocking select sum:", sum) } func send(ch chan<- int) { ch <- 1 wg.Done() } func receive(ch <-chan int) { <-ch wg.Done() } func sender(ch chan int) { for i := 1; i <= 8; i++ { if i == 4 { time.Sleep(time.Millisecond) println("slept") } ch <- i } close(ch) wg.Done() } func sendComplex(ch chan complex128) { ch <- 7 + 10.5i wg.Done() } func fastsender(ch chan int, n int) { ch <- n ch <- n + 1 wg.Done() } func slowreceiver(ch chan int) { sum := 0 for i := 0; i < 6; i++ { sum += <-ch time.Sleep(time.Microsecond) } println("sum of n:", sum) } func slowsender(ch chan int) { for n := 0; n < 6; n++ { time.Sleep(time.Microsecond) ch <- 12 + n } } func fastreceiver(ch chan int) { sum := 0 for i := 0; i < 2; i++ { n := <-ch sum += n } println("sum:", sum) wg.Done() } func fastreceiveradd(ch chan int, result *atomic.Uint32) { sum := 0 for i := 0; i < 2; i++ { n := <-ch sum += n } result.Add(uint32(sum)) wg.Done() } func iterator(ch chan int, top int) { for i := 0; i < top; i++ { ch <- i } close(ch) wg.Done() } func selectDeadlock() { println("deadlocking") wg.Done() select {} println("unreachable") } func selectNoOp() { println("select no-op") select { default: } println("after no-op") wg.Done() } ================================================ FILE: testdata/channel.txt ================================================ len, cap of channel: 1 2 false len, cap of channel: 0 0 false recv from open channel: 1 true received num: 2 received num: 3 slept received num: 4 received num: 5 received num: 6 received num: 7 received num: 8 recv from closed channel: 0 false complex128: (+7.000000e+000+1.050000e+001i) sum of n: 149 sum of sums: 87 sum(100): 4950 deadlocking select no-op after no-op sum: 5 did send one select one n: 0 select n from chan: 55 select n from closed chan: 0 select send sum: 235 non-concurrent channel receive: 1 non-concurrent channel receive: 2 closed buffered channel receive: 3 closed buffered channel receive: 4 closed buffered channel receive: 0 hybrid buffered channel receive: 2 blocking select sum: 3 ================================================ FILE: testdata/corpus.yaml ================================================ # This contains code from https://github.com/dgryski/tinygo-test-corpus # The MIT License (MIT) # Copyright (c) 2020 Damian Gryski # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. - repo: github.com/buger/jsonparser - repo: github.com/dgryski/go-bloomindex tags: noasm - repo: github.com/dgryski/go-arc - repo: github.com/dgryski/go-camellia - repo: github.com/dgryski/go-change - repo: github.com/dgryski/go-chaskey tags: appengine noasm skipwasi: true # siphash has build tag issues - repo: github.com/dgryski/go-clefia - repo: github.com/dgryski/go-clockpro - repo: github.com/dgryski/go-cobs - repo: github.com/dgryski/go-cuckoof tags: pureno noasm - repo: github.com/dgryski/go-discreterand - repo: github.com/dgryski/go-expirecache - repo: github.com/dgryski/go-factor - repo: github.com/dgryski/go-farm tags: noasm - repo: github.com/dgryski/go-fuzzstr - repo: github.com/dgryski/go-hollow - repo: github.com/dgryski/go-idea - repo: github.com/dgryski/go-interp skipwasi: true # too slow on wasi slow: true - repo: github.com/dgryski/go-intpat - repo: github.com/dgryski/go-jump - repo: github.com/dgryski/go-kcipher2 - repo: github.com/dgryski/go-ketama - repo: github.com/dgryski/go-krcrypt - repo: github.com/dgryski/go-linebreak - repo: github.com/dgryski/go-linlog - repo: github.com/dgryski/go-maglev tags: appengine # for dchest/siphash skipwasi: true - repo: github.com/dgryski/go-marvin32 - repo: github.com/dgryski/go-md5crypt - repo: github.com/dgryski/go-metro tags: noasm - repo: github.com/dgryski/go-misty1 - repo: github.com/dgryski/go-mph tags: noasm - repo: github.com/dgryski/go-mpchash tags: appengine # for dchest/siphash skipwasi: true - repo: github.com/dgryski/go-neeva - repo: github.com/dgryski/go-nibz - repo: github.com/dgryski/go-nibblesort - repo: github.com/dgryski/go-pcgr - repo: github.com/dgryski/go-present - repo: github.com/dgryski/go-quicklz skipwasi: true # not 32-bit compliant - repo: github.com/dgryski/go-radixsort - repo: github.com/dgryski/go-rbo - repo: github.com/dgryski/go-rc5 - repo: github.com/dgryski/go-rc6 - repo: github.com/dgryski/go-s4lru - repo: github.com/dgryski/go-sequitur - repo: github.com/dgryski/go-sip13 tags: noasm - repo: github.com/dgryski/go-skinny - repo: github.com/dgryski/go-skip32 - repo: github.com/dgryski/go-skipjack - repo: github.com/dgryski/go-sparx - repo: github.com/dgryski/go-spooky - repo: github.com/dgryski/go-spritz - repo: github.com/dgryski/go-timewindow - repo: github.com/dgryski/go-tinylz - repo: github.com/dgryski/go-tinymap - repo: github.com/dgryski/go-trigram - repo: github.com/dgryski/go-twine - repo: github.com/dgryski/go-xoroshiro - repo: github.com/dgryski/go-xoshiro - repo: github.com/dgryski/go-zlatlong - repo: github.com/dgryski/go-postings tags: noasm - repo: golang.org/x/crypto tags: noasm subdirs: - pkg: argon2 - pkg: bcrypt - pkg: blake2b - pkg: blake2s - pkg: blowfish - pkg: bn256 - pkg: cast5 - pkg: chacha20 skipwasi: true # needs recover - pkg: chacha20poly1305 - pkg: curve25519 - pkg: ed25519 - pkg: hkdf - pkg: md4 - pkg: nacl/auth - pkg: nacl/box - pkg: nacl/secretbox - pkg: nacl/sign - pkg: openpgp/armor - pkg: openpgp/elgamal - pkg: openpgp/s2k - pkg: pbkdf2 - pkg: pkcs12/internal/rc2 - pkg: ripemd160 - pkg: salsa20 - pkg: scrypt - pkg: ssh/internal/bcrypt_pbkdf - pkg: tea - pkg: twofish - pkg: xtea #- pkg: cryptobyte # panic: unimplemented: reflect.OverflowInt() #- pkg: salsa20/salsa # panic: runtime error: index out of range #- pkg: sha3 # panic: unimplemented: (reflect.Type).NumMethod() - repo: github.com/google/shlex - repo: github.com/google/btree - repo: github.com/google/der-ascii subdirs: - pkg: cmd/ascii2der - pkg: cmd/der2ascii - pkg: internal - repo: github.com/google/hilbert - repo: github.com/google/go-intervals subdirs: - pkg: intervalset - pkg: timespanset skipwasi: true # needs timezone stuff - repo: github.com/google/okay - repo: github.com/google/go-patchutils skipwasi: true # needs os.readdir - repo: golang.org/x/text subdirs: - pkg: encoding - pkg: encoding/charmap - pkg: encoding/htmlindex - pkg: encoding/ianaindex - pkg: encoding/japanese - pkg: encoding/korean - pkg: encoding/simplifiedchinese - pkg: encoding/traditionalchinese - pkg: encoding/unicode - pkg: encoding/unicode/utf32 - pkg: internal/format - pkg: internal/ucd - pkg: internal/tag - pkg: search - pkg: unicode/rangetable - pkg: message/catalog #- pkg: collate/build # panic: (reflect.Value).Interface: unexported #- pkg: feature/plural # TestSelect, TestOrdinal, TestCardinal fail #- pkg: internal/catmsg # TestCodec fails #- pkg: internal/gen/bitfield # panic: unimplemented: (reflect.Type).Name() #- pkg: number # fails due to printf %T formatting - repo: golang.org/x/image tags: noasm subdirs: #- pkg: bmp # needs _time.startTimer - pkg: ccitt - pkg: colornames - pkg: draw - pkg: font - pkg: font/basicfont - pkg: font/opentype - pkg: font/plan9font - pkg: math/fixed - pkg: riff - pkg: webp - pkg: tiff - repo: github.com/golang/geo subdirs: - pkg: r1 - pkg: r2 - pkg: r3 - pkg: s1 #- pkg: s2 # reflect.DeepEqual() -> MapKeys - repo: github.com/golang/groupcache subdirs: - pkg: consistenthash - pkg: lru - repo: github.com/armon/go-radix - repo: github.com/armon/circbuf - repo: github.com/VividCortex/gohistogram - repo: github.com/cespare/xxhash tags: appengine - repo: gonum.org/v1/gonum - repo: gonum.org/v1/gonum tags: noasm appengine subdirs: - pkg: blas/blas32 - pkg: blas/blas64 skipwasi: true # needs recover - pkg: blas/cblas64 - pkg: blas/cblas128 - pkg: blas/gonum skipwasi: true # needs recover - pkg: cmplxs skipwasi: true # needs recover - pkg: cmplxs/cscalar - pkg: diff/fd skipwasi: true # needs recover - pkg: dsp/window - pkg: floats skipwasi: true # needs recover - pkg: floats/scalar - pkg: integrate - pkg: integrate/quad - pkg: internal/cmplx64 - pkg: internal/testrand - pkg: interp skipwasi: true # needs recover - pkg: lapack/gonum skipwasi: true # takes too long slow: true - pkg: mathext - pkg: mathext/prng - pkg: optimize/convex/lp skipwasi: true # takes too long - pkg: optimize/functions - pkg: spatial/r2 - pkg: spatial/r3 - pkg: stat/distmat - pkg: stat/mds - pkg: stat/spatial - pkg: stat/distmv skipwasi: true # takes too long slow: true - pkg: stat/samplemv skipwasi: true # takes too long #- pkg: graph # ld.lld-11: -- error: undefined symbol: reflect.mapiterkey (among other reflect errors) #- pkg: graph/topo # -- Reflect: Same as above #- pkg: internal/math32 # -- /usr/local/go/src/testing/quick/quick.go:273:11: fType.NumOut undefined (type reflect.Type has no field or method NumOut) #- pkg: mat # -- panic: mat: row index out of range #- pkg: num/dual # TestFormat unexpected result for fmt.Sprintf("%#v", T{Real:1.1, Emag:2.1}): got:"T{Real:1.1, Emag:2.1}", want:"dual.Number{Real:1.1, Emag:2.1}" unexpected result for fmt.Sprintf("%#v", T{Real:-1.1, Emag:-2.1}): got:"T{Real:-1.1, Emag:-2.1}", want:"dual.Number{Real:-1.1, Emag:-2.1}" #- pkg: num/dualcmplx # TestFormat (similar to above) #- pkg: num/dualquat # TestFormat (similar to above) #- pkg: num/hyperdual # TestFormat (similar to above) #- pkg: num/quat # TestFormat (similar to above) #- pkg: optimize', # ld.lld-11: error: undefined symbol: golang.org/x/tools/container/intsets.havePOPCNT error: failed to link ... #- pkg: spatial/barneshut # panic: unimplemented: (reflect.Value).MapKeys() #- pkg: spatial/kdtree # panic: unimplemented: (reflect.Value).MapKeys() #- pkg: spatial/vptree # panic: unimplemented: (reflect.Value).MapKeys() #- pkg: stat # panic: stat: slice length mismatch #- pkg: stat/card # /usr/local/go/src/encoding/gob/decode.go:562:21: MakeMapWithSize not declared by package reflect #- pkg: stat/distuv # panic: distuv: cannot compute Mode for Beta != 0\ #- pkg: stat/sampleuv # TestWeightedTimeSeeded requires t.Skip(), otherwise passes #- pkg: unit # All Format tests fail. Similar to `num` subpackages. - repo: github.com/cloudflare/bm - repo: github.com/cloudflare/bn256 tags: generic - repo: github.com/cloudflare/ahocorasick #- repo: github.com/google/open-location-code # unfortunately, Go discards the test files # version: master # skipwasi: true # needs file access # subdirs: # - pkg: go - repo: github.com/chewxy/math32 version: master - repo: github.com/russross/blackfriday version: v2 - repo: github.com/soypat/mu8 - repo: github.com/brandondube/pctl - repo: github.com/julienschmidt/httprouter - repo: github.com/openhistogram/circonusllhist skipwasi: true # math.MaxInt64 (untyped int constant 9223372036854775807) overflows int ================================================ FILE: testdata/embed/a/b/.hidden ================================================ ================================================ FILE: testdata/embed/a/b/bar.txt ================================================ bar ================================================ FILE: testdata/embed/a/b/foo.txt ================================================ foo ================================================ FILE: testdata/embed/embed.go ================================================ package main import ( "embed" "strings" ) //go:embed a hello.txt var files embed.FS var ( //go:embed "hello.*" helloString string //go:embed hello.txt helloBytes []byte ) // A test to check that hidden files are not included when matching a directory. //go:embed a/b/.hidden var hidden string var helloStringBytes = []byte(helloString) func main() { println("string:", strings.TrimSpace(helloString)) println("bytes:", strings.TrimSpace(string(helloBytes))) println("[]byte(string):", strings.TrimSpace(string(helloStringBytes))) println("files:") readFiles(".") } func readFiles(dir string) { entries, err := files.ReadDir(dir) if err != nil { println(err.Error()) return } for _, entry := range entries { entryPath := entry.Name() if dir != "." { entryPath = dir + "/" + entryPath } println("-", entryPath) if entry.IsDir() { readFiles(entryPath) } } } ================================================ FILE: testdata/embed/hello.txt ================================================ hello world! ================================================ FILE: testdata/embed/out.txt ================================================ string: hello world! bytes: hello world! []byte(string): hello world! files: - a - a/b - a/b/bar.txt - a/b/foo.txt - hello.txt ================================================ FILE: testdata/env.go ================================================ package main import ( "os" ) func main() { // Check for environment variables (set by the test runner). println("ENV1:", os.Getenv("ENV1")) v, ok := os.LookupEnv("ENV2") if !ok { println("ENV2 not found") } println("ENV2:", v) found := false expected := "ENV1=" + os.Getenv("ENV1") for _, envVar := range os.Environ() { if envVar == expected { found = true } } if !found { println("could not find " + expected + " in os.Environ()") } // Check for command line arguments. // Argument 0 is skipped because it is the program name, which varies by // test run. println() for _, arg := range os.Args[1:] { println("arg:", arg) } } ================================================ FILE: testdata/env.txt ================================================ ENV1: VALUE1 ENV2: VALUE2 arg: first arg: second ================================================ FILE: testdata/errors/cgo.go ================================================ package main // #error hello // ))) import "C" func main() { } // ERROR: # command-line-arguments // ERROR: cgo.go:3:5: error: hello // ERROR: cgo.go:4:4: error: expected identifier or '(' ================================================ FILE: testdata/errors/compiler.go ================================================ package main //go:wasmimport foo bar func foo() { } //go:align 7 var global int // Test for https://github.com/tinygo-org/tinygo/issues/4486 type genericType[T any] struct{} func (genericType[T]) methodWithoutBody() func callMethodWithoutBody() { msg := &genericType[int]{} msg.methodWithoutBody() } // ERROR: # command-line-arguments // ERROR: compiler.go:4:6: can only use //go:wasmimport on declarations // ERROR: compiler.go:8:5: global variable alignment must be a positive power of two // ERROR: compiler.go:13:23: missing function body ================================================ FILE: testdata/errors/importcycle/cycle.go ================================================ package importcycle import _ "github.com/tinygo-org/tinygo/testdata/errors/importcycle" ================================================ FILE: testdata/errors/interp.go ================================================ package main import _ "unsafe" func init() { foo() } func foo() { interp_test_error() } // This is a function that always causes an error in interp, for testing. // //go:linkname interp_test_error __tinygo_interp_raise_test_error func interp_test_error() func main() { } // ERROR: # main // ERROR: {{.*testdata[\\/]errors[\\/]interp\.go}}:10:19: test error // ERROR: call void @__tinygo_interp_raise_test_error{{.*}} // ERROR: {{}} // ERROR: traceback: // ERROR: {{.*testdata[\\/]errors[\\/]interp\.go}}:10:19: // ERROR: call void @__tinygo_interp_raise_test_error{{.*}} // ERROR: {{.*testdata[\\/]errors[\\/]interp\.go}}:6:5: // ERROR: call void @main.foo{{.*}} // ERROR: {{.*testdata[\\/]errors}}: // ERROR: call void @"main.init#1"{{.*}} ================================================ FILE: testdata/errors/invaliddep/invaliddep.go ================================================ ppackage // syntax error ================================================ FILE: testdata/errors/invalidmain.go ================================================ // some comment to move the first line package foobar // ERROR: # command-line-arguments // ERROR: invalidmain.go:3:9: expected main package to have name "main", not "foobar" ================================================ FILE: testdata/errors/invalidname/invalidname.go ================================================ // some comment to move the 'package' line package _ ================================================ FILE: testdata/errors/invalidname.go ================================================ package main import _ "github.com/tinygo-org/tinygo/testdata/errors/invalidname" // ERROR: # github.com/tinygo-org/tinygo/testdata/errors/invalidname // ERROR: invalidname{{[\\/]}}invalidname.go:3:9: invalid package name _ ================================================ FILE: testdata/errors/linker-flashoverflow.go ================================================ package main import "unsafe" const ( a = "0123456789abcdef" // 16 bytes b = a + a + a + a + a + a + a + a // 128 bytes c = b + b + b + b + b + b + b + b // 1024 bytes d = c + c + c + c + c + c + c + c // 8192 bytes e = d + d + d + d + d + d + d + d // 65536 bytes f = e + e + e + e + e + e + e + e // 524288 bytes ) var s = f func main() { println(unsafe.StringData(s)) } // ERROR: program too large for this chip (flash overflowed by {{[0-9]+}} bytes) // ERROR: optimization guide: https://tinygo.org/docs/guides/optimizing-binaries/ ================================================ FILE: testdata/errors/linker-ramoverflow.go ================================================ package main var b [64 << 10]byte // 64kB func main() { println("ptr:", &b[0]) } // ERROR: program uses too much static RAM on this chip (RAM overflowed by {{[0-9]+}} bytes) ================================================ FILE: testdata/errors/linker-undefined.go ================================================ package main func foo() func main() { foo() foo() } // ERROR: linker-undefined.go:6: linker could not find symbol {{_?}}main.foo // ERROR: linker-undefined.go:7: linker could not find symbol {{_?}}main.foo ================================================ FILE: testdata/errors/loader-importcycle.go ================================================ package main import _ "github.com/tinygo-org/tinygo/testdata/errors/importcycle" func main() { } // ERROR: package command-line-arguments // ERROR: imports github.com/tinygo-org/tinygo/testdata/errors/importcycle // ERROR: imports github.com/tinygo-org/tinygo/testdata/errors/importcycle: import cycle not allowed ================================================ FILE: testdata/errors/loader-invaliddep.go ================================================ package main import _ "github.com/tinygo-org/tinygo/testdata/errors/invaliddep" func main() { } // ERROR: invaliddep{{[\\/]}}invaliddep.go:1:1: expected 'package', found ppackage ================================================ FILE: testdata/errors/loader-invalidpackage.go ================================================ ppackage // syntax error // ERROR: loader-invalidpackage.go:1:1: expected 'package', found ppackage ================================================ FILE: testdata/errors/loader-nopackage.go ================================================ package main import ( _ "github.com/tinygo-org/tinygo/testdata/errors/non-existing-package" _ "github.com/tinygo-org/tinygo/testdata/errors/non-existing-package-2" ) func main() { } // ERROR: loader-nopackage.go:4:2: no required module provides package github.com/tinygo-org/tinygo/testdata/errors/non-existing-package; to add it: // ERROR: go get github.com/tinygo-org/tinygo/testdata/errors/non-existing-package // ERROR: loader-nopackage.go:5:2: no required module provides package github.com/tinygo-org/tinygo/testdata/errors/non-existing-package-2; to add it: // ERROR: go get github.com/tinygo-org/tinygo/testdata/errors/non-existing-package-2 ================================================ FILE: testdata/errors/optimizer.go ================================================ package main import "runtime/interrupt" var num = 5 func main() { // Error coming from LowerInterrupts. interrupt.New(num, func(interrupt.Interrupt) { }) // 2nd error interrupt.New(num, func(interrupt.Interrupt) { }) } // ERROR: # command-line-arguments // ERROR: optimizer.go:9:15: interrupt ID is not a constant // ERROR: optimizer.go:13:15: interrupt ID is not a constant ================================================ FILE: testdata/errors/syntax.go ================================================ package main func main(var) { // syntax error } // ERROR: # command-line-arguments // ERROR: syntax.go:3:11: expected ')', found 'var' ================================================ FILE: testdata/errors/types.go ================================================ package main func main() { var a int a = "foobar" nonexisting() } // ERROR: # command-line-arguments // ERROR: types.go:4:6: declared and not used: a // ERROR: types.go:5:6: cannot use "foobar" (untyped string constant) as int value in assignment // ERROR: types.go:6:2: undefined: nonexisting ================================================ FILE: testdata/filesystem.go ================================================ package main import ( "errors" "io" "io/fs" "os" ) func main() { _, err := os.Open("non-exist") if !errors.Is(err, fs.ErrNotExist) { panic("should be non exist error") } f, err := os.Open("testdata/filesystem.txt") if err != nil { panic(err) } defer func() { if err := f.Close(); err != nil { panic(err) } // read after close: error should be returned _, err := f.Read(make([]byte, 10)) if err == nil { panic("error expected for reading after closing files") } }() data, err := io.ReadAll(f) if err != nil { panic(err) } os.Stdout.Write(data) path, err := os.Getwd() if err != nil { panic(err) } if path == "" { panic("path is empty") } } ================================================ FILE: testdata/filesystem.txt ================================================ abcdefg 1 2 3 4 5 ================================================ FILE: testdata/float.go ================================================ package main func main() { // sanity println(3.14159265358979323846) // float64 f64 := float64(2) / float64(3) println(f64) println(f64 + 1.0) println(f64 - 1.0) println(f64 * 2.0) println(f64 / 2.0) // float32 f32 := float32(2) / float32(3) println(f32) println(f32 + 1.0) println(f32 - 1.0) println(f32 * 2.0) println(f32 / 2.0) // casting println(float32(f64)) println(float64(f32)) // float -> int var f1 float32 = 3.3 var f2 float32 = 5.7 var f3 float32 = -2.3 var f4 float32 = -11.8 println(int32(f1), int32(f2), int32(f3), int32(f4)) // float -> int saturating behavior var f5 float32 = -1 var f6 float32 = 256 var f7 float32 = -129 f8 := float32(^uint32(0)) f9 := float32(int32(-2147483648)) f10 := float32(int32(2147483647)) var inf float32 = 1 inf /= 0 var nan float32 = 0 nan /= 0 println(uint8(f5), uint8(f6), int8(f7), int8(f6), uint32(f8), int32(f9), int32(f10), uint8(inf), uint8(-inf), int8(inf), int8(-inf), uint8(nan), int64(nan)) // int -> float var i1 int32 = 53 var i2 int32 = -8 var i3 uint32 = 20 println(float32(i1), float32(i2), float32(i3)) // complex64 c64 := complex(f32, 1.2) println(c64) println(real(c64)) println(imag(c64)) // complex128 c128 := complex(f64, -2.0) println(c128) println(real(c128)) println(imag(c128)) // untyped complex println(2 + 1i) println(complex(2, -2)) // cast complex println(complex64(c128)) println(complex128(c64)) // binops and negate on complex numbers c64 = 5 + 2i println("complex64 add: ", c64+-3+8i) println("complex64 sub: ", c64 - -3 + 8i) println("complex64 mul: ", c64*-3+8i) println("complex64 div: ", c64/-3+8i) println("complex64 neg: ", -c64) c128 = -5 + 2i println("complex128 add:", c128+2+6i) println("complex128 sub:", c128-2+6i) println("complex128 mul:", c128*2+6i) println("complex128 div:", c128/2+6i) println("complex128 neg:", -c128) } ================================================ FILE: testdata/float.txt ================================================ +3.141593e+000 +6.666667e-001 +1.666667e+000 -3.333333e-001 +1.333333e+000 +3.333333e-001 +6.666667e-001 +1.666667e+000 -3.333333e-001 +1.333333e+000 +3.333333e-001 +6.666667e-001 +6.666667e-001 3 5 -2 -11 0 255 -128 127 4294967295 -2147483648 2147483647 255 0 127 -128 0 0 +5.300000e+001 -8.000000e+000 +2.000000e+001 (+6.666667e-001+1.200000e+000i) +6.666667e-001 +1.200000e+000 (+6.666667e-001-2.000000e+000i) +6.666667e-001 -2.000000e+000 (+2.000000e+000+1.000000e+000i) (+2.000000e+000-2.000000e+000i) (+6.666667e-001-2.000000e+000i) (+6.666667e-001+1.200000e+000i) complex64 add: (+2.000000e+000+1.000000e+001i) complex64 sub: (+8.000000e+000+1.000000e+001i) complex64 mul: (-1.500000e+001+2.000000e+000i) complex64 div: (-1.666667e+000+7.333333e+000i) complex64 neg: (-5.000000e+000-2.000000e+000i) complex128 add: (-3.000000e+000+8.000000e+000i) complex128 sub: (-7.000000e+000+8.000000e+000i) complex128 mul: (-1.000000e+001+1.000000e+001i) complex128 div: (-2.500000e+000+7.000000e+000i) complex128 neg: (+5.000000e+000-2.000000e+000i) ================================================ FILE: testdata/gc.go ================================================ package main import "runtime" var xorshift32State uint32 = 1 func xorshift32(x uint32) uint32 { // Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs" x ^= x << 13 x ^= x >> 17 x ^= x << 5 return x } func randuint32() uint32 { xorshift32State = xorshift32(xorshift32State) return xorshift32State } func main() { testNonPointerHeap() testKeepAlive() } var scalarSlices [4][]byte var randSeeds [4]uint32 func testNonPointerHeap() { maxSliceSize := uint32(1024) if ^uintptr(0) <= 0xffff { // 16-bit and lower devices, such as AVR. // Heap size is a real issue there, while it is still useful to run // these tests. Therefore, lower the max slice size. maxSliceSize = 64 } // Allocate roughly 0.5MB of memory. for i := 0; i < 1000; i++ { // Pick a random index that the optimizer can't predict. index := randuint32() % 4 // Check whether the contents of the previous allocation was correct. rand := randSeeds[index] for _, b := range scalarSlices[index] { rand = xorshift32(rand) if b != byte(rand) { panic("memory was overwritten!") } } // Allocate a randomly-sized slice, randomly sliced to be smaller. sliceLen := randuint32() % maxSliceSize slice := make([]byte, sliceLen) cutLen := randuint32() % maxSliceSize if cutLen < sliceLen { slice = slice[cutLen:] } scalarSlices[index] = slice // Fill the slice with a pattern that looks random but is easily // calculated and verified. rand = randuint32() + 1 randSeeds[index] = rand for i := 0; i < len(slice); i++ { rand = xorshift32(rand) slice[i] = byte(rand) } } println("ok") } func testKeepAlive() { // There isn't much we can test, but at least we can test that // runtime.KeepAlive compiles correctly. var x int runtime.KeepAlive(&x) } ================================================ FILE: testdata/gc.txt ================================================ ok ================================================ FILE: testdata/generics/testa/testa.go ================================================ package testa import ( "github.com/tinygo-org/tinygo/testdata/generics/value" ) func Test() { v := value.New(1) vm := value.Map(v, Plus100) vm.Get(callback, callback) } func callback(v int) { println("value:", v) } // Plus100 is a `Transform` that adds 100 to `value`. func Plus100(value int) int { return value + 100 } ================================================ FILE: testdata/generics/testb/testb.go ================================================ package testb import ( "github.com/tinygo-org/tinygo/testdata/generics/value" ) func Test() { v := value.New(1) vm := value.Map(v, Plus500) vm.Get(callback, callback) } func callback(v int) { println("value:", v) } // Plus500 is a `Transform` that adds 500 to `value`. func Plus500(value int) int { return value + 500 } ================================================ FILE: testdata/generics/value/value.go ================================================ package value type ( Value[T any] interface { Get(Callback[T], Callback[T]) } Callback[T any] func(T) Transform[S any, D any] func(S) D ) func New[T any](v T) Value[T] { return &value[T]{ v: v, } } type value[T any] struct { v T } func (v *value[T]) Get(fn1, fn2 Callback[T]) { // For example purposes. // Normally would be asynchronous callback. fn1(v.v) fn2(v.v) } func Map[S, D any](v Value[S], tx Transform[S, D]) Value[D] { return &mapper[S, D]{ v: v, tx: tx, } } type mapper[S, D any] struct { v Value[S] tx Transform[S, D] } func (m *mapper[S, D]) Get(fn1, fn2 Callback[D]) { // two callbacks are passed to generate more than // one anonymous function symbol name. m.v.Get(func(v S) { // anonymous function inside of anonymous function. func() { fn1(m.tx(v)) }() }, func(v S) { fn2(m.tx(v)) }) } ================================================ FILE: testdata/generics.go ================================================ package main import ( "github.com/tinygo-org/tinygo/testdata/generics/testa" "github.com/tinygo-org/tinygo/testdata/generics/testb" ) func main() { println("add:", Add(3, 5)) println("add:", Add(int8(3), 5)) var c C[int] c.F() // issue 2951 SliceOp([]int(nil)) // issue 3002 testa.Test() testb.Test() } type Integer interface { int | int8 | int16 | int32 | int64 } func Add[T Integer](a, b T) T { return a + b } // Test for https://github.com/tinygo-org/tinygo/issues/2951 type C[V any] struct{} func (c *C[V]) F() {} // Test for https://github.com/tinygo-org/tinygo/issues/3002 func SliceOp[S ~[]E, E any](s S) {} ================================================ FILE: testdata/generics.txt ================================================ add: 8 add: 8 value: 101 value: 101 value: 501 value: 501 ================================================ FILE: testdata/go1.21.go ================================================ package main func main() { // The new min/max builtins. ia := 1 ib := 5 ic := -3 fa := 1.0 fb := 5.0 fc := -3.0 println("min/max:", min(ia, ib, ic), max(ia, ib, ic)) println("min/max:", min(fa, fb, fc), max(fa, fb, fc)) // The clear builtin, for slices. s := []int{1, 2, 3, 4, 5} clear(s[:3]) println("cleared s[:3]:", s[0], s[1], s[2], s[3], s[4]) // The clear builtin, for maps. m := map[int]string{ 1: "one", 2: "two", 3: "three", } clear(m) println("cleared map:", m[1], m[2], m[3], len(m)) m[4] = "four" println("added to cleared map:", m[1], m[2], m[3], m[4], len(m)) } ================================================ FILE: testdata/go1.21.txt ================================================ min/max: -3 5 min/max: -3.000000e+000 +5.000000e+000 cleared s[:3]: 0 0 0 4 5 cleared map: 0 added to cleared map: four 1 ================================================ FILE: testdata/go1.22/go.mod ================================================ module github.com/tinygo-org/tinygo/testdata/go1.22 go 1.22 ================================================ FILE: testdata/go1.22/main.go ================================================ package main func main() { testIntegerRange() testLoopVar() } func testIntegerRange() { for i := range 10 { println(10 - i) } println("go1.22 has lift-off!") } func testLoopVar() { var f func() int for i := 0; i < 1; i++ { if i == 0 { f = func() int { return i } } } // Variable n is 1 in Go 1.21, or 0 in Go 1.22. n := f() if n == 0 { println("loops behave like Go 1.22") } else if n == 1 { println("loops behave like Go 1.21") } else { println("unknown loop behavior") } } ================================================ FILE: testdata/go1.22/out.txt ================================================ 10 9 8 7 6 5 4 3 2 1 go1.22 has lift-off! loops behave like Go 1.22 ================================================ FILE: testdata/go1.23/go.mod ================================================ module github.com/tinygo-org/tinygo/testdata/go1.23 go 1.23 ================================================ FILE: testdata/go1.23/main.go ================================================ package main import "iter" func main() { testFuncRange(counter) testIterPull(counter) println("go1.23 has lift-off!") } func testFuncRange(it iter.Seq[int]) { for i := range it { println(i) } } func testIterPull(it iter.Seq[int]) { next, stop := iter.Pull(it) defer stop() for { i, ok := next() if !ok { break } println(i) } } func counter(yield func(int) bool) { for i := 10; i >= 1; i-- { yield(i) } } ================================================ FILE: testdata/go1.23/out.txt ================================================ 10 9 8 7 6 5 4 3 2 1 10 9 8 7 6 5 4 3 2 1 go1.23 has lift-off! ================================================ FILE: testdata/goroutines.go ================================================ package main import ( "sync" "time" ) func init() { println("init") go println("goroutine in init") time.Sleep(1 * time.Millisecond) } func main() { println("main 1") go sub() time.Sleep(100 * time.Millisecond) println("main 2") time.Sleep(200 * time.Millisecond) println("main 3") // Await a blocking call. println("wait:") wait() println("end waiting") value := delayedValue() println("value produced after some time:", value) // Run a non-blocking call in a goroutine. This should be turned into a // regular call, so should be equivalent to calling nowait() without 'go' // prefix. go nowait() time.Sleep(time.Millisecond) println("done with non-blocking goroutine") var printer Printer printer = &myPrinter{} printer.Print() sleepFuncValue(func(x int) { time.Sleep(1 * time.Millisecond) println("slept inside func pointer", x) }) time.Sleep(1 * time.Millisecond) n := 20 sleepFuncValue(func(x int) { time.Sleep(1 * time.Millisecond) println("slept inside closure, with value:", n, x) }) time.Sleep(2 * time.Millisecond) var x int go func() { time.Sleep(2 * time.Millisecond) x = 1 }() time.Sleep(time.Second / 2) println("closure go call result:", x) time.Sleep(2 * time.Millisecond) var m sync.Mutex var wg sync.WaitGroup m.Lock() println("pre-acquired mutex") wg.Add(1) go acquire(&m, &wg) time.Sleep(2 * time.Millisecond) println("releasing mutex") m.Unlock() wg.Wait() time.Sleep(2 * time.Millisecond) m.Lock() println("re-acquired mutex") m.Unlock() println("done") startSimpleFunc(emptyFunc) time.Sleep(2 * time.Millisecond) testGoOnBuiltins() testGoOnInterface(Foo(0)) testIssue1790() done := make(chan int) go testPaddedParameters(paddedStruct{x: 5, y: 7}, done) <-done } func acquire(m *sync.Mutex, wg *sync.WaitGroup) { m.Lock() wg.Done() println("acquired mutex from goroutine") time.Sleep(2 * time.Millisecond) println("releasing mutex from goroutine") m.Unlock() } func sub() { println("sub 1") time.Sleep(200 * time.Millisecond) println("sub 2") } func wait() { println(" wait start") time.Sleep(time.Millisecond) println(" wait end") } func delayedValue() int { time.Sleep(time.Millisecond) return 42 } func sleepFuncValue(fn func(int)) { go fn(8) } func startSimpleFunc(fn simpleFunc) { // Test that named function types don't crash the compiler. go fn() } func nowait() { println("non-blocking goroutine") } type Printer interface { Print() } type myPrinter struct { } func (i *myPrinter) Print() { time.Sleep(time.Millisecond) println("async interface method call") } type simpleFunc func() func emptyFunc() { } func testGoOnBuiltins() { // Test copy builtin (there is no non-racy practical use of this). go copy(make([]int, 8), []int{2, 5, 8, 4}) // Test recover builtin (no-op). go recover() // Test close builtin. ch := make(chan int) go close(ch) n, ok := <-ch if n != 0 || ok != false { println("error: expected closed channel to return 0, false") } // Test delete builtin. m := map[string]int{"foo": 3} go delete(m, "foo") time.Sleep(time.Millisecond) v, ok := m["foo"] if v != 0 || ok != false { println("error: expected deleted map entry to be 0, false") } } var once sync.Once var waitChan = make(chan struct{}) func testGoOnInterface(f Itf) { go f.Nowait() time.Sleep(time.Millisecond) go f.Wait() <-waitChan time.Sleep(time.Millisecond * 2) println("done with 'go on interface'") } // This tests a fix for issue 1790: // https://github.com/tinygo-org/tinygo/issues/1790 func testIssue1790() *int { once.Do(func() {}) i := 0 return &i } type Itf interface { Nowait() Wait() } type Foo string func (f Foo) Nowait() { println("called: Foo.Nowait") } func (f Foo) Wait() { println("called: Foo.Wait") close(waitChan) time.Sleep(time.Microsecond) println(" ...waited") } type paddedStruct struct { x uint8 _ [0]int64 y uint8 } // Structs with interesting padding used to crash. func testPaddedParameters(s paddedStruct, done chan int) { println("paddedStruct:", s.x, s.y) close(done) } ================================================ FILE: testdata/goroutines.txt ================================================ init goroutine in init main 1 sub 1 main 2 sub 2 main 3 wait: wait start wait end end waiting value produced after some time: 42 non-blocking goroutine done with non-blocking goroutine async interface method call slept inside func pointer 8 slept inside closure, with value: 20 8 closure go call result: 1 pre-acquired mutex releasing mutex acquired mutex from goroutine releasing mutex from goroutine re-acquired mutex done called: Foo.Nowait called: Foo.Wait ...waited done with 'go on interface' paddedStruct: 5 7 ================================================ FILE: testdata/init.go ================================================ package main func init() { println("init") } func main() { println("main") println("v1:", v1) println("v2:", v2.x, v2.y) println("v3:", len(v3), cap(v3), v3[0], v3[3]) println("v4:", len(v4), v4 == nil) println("v5:", len(v5), v5 == nil) println("v6:", v6) println("v7:", cap(v7), string(v7)) println("v8:", v8) println("v9:", len(v9), v9[0], v9[1], v9[2]) println(uint8SliceSrc[0]) println(uint8SliceDst[0]) println(intSliceSrc[0]) println(intSliceDst[0]) } type ( t2 struct { x int y int } ) var ( v1 = 3 v2 = t2{2, 5} v3 = []int{2, 3, 5, 7} v4 map[string]int v5 = map[string]int{} v6 = float64(v1) < 2.6 v7 = []byte("foo") v8 string v9 []int uint8SliceSrc = []uint8{3, 100} uint8SliceDst []uint8 intSliceSrc = []int16{5, 123, 1024} intSliceDst []int16 someList *linkedList someBigList *bigLinkedList ) type linkedList struct { prev *linkedList next *linkedList v int // arbitrary value (don't care) } func init() { someList = &linkedList{ v: -1, } for i := 0; i < 3; i++ { prev := someList someList = &linkedList{ v: i, prev: prev, } prev.next = someList } } type bigLinkedList struct { prev *bigLinkedList next *bigLinkedList v int buf [100]*int } func init() { // Create a circular reference. someBigList = &bigLinkedList{ v: -1, } for i := 0; i < 3; i++ { prev := someBigList someBigList = &bigLinkedList{ v: i, prev: prev, } prev.next = someBigList } } func init() { uint8SliceDst = make([]uint8, len(uint8SliceSrc)) copy(uint8SliceDst, uint8SliceSrc) intSliceDst = make([]int16, len(intSliceSrc)) copy(intSliceDst, intSliceSrc) v8 = sliceString("foobarbaz", 3, 8) v9 = sliceSlice([]int{0, 1, 2, 3, 4, 5}, 2, 5) } func sliceString(s string, start, end int) string { return s[start:end] } func sliceSlice(s []int, start, end int) []int { return s[start:end] } type outside struct{} func init() { _, _ = any(0).(interface{ DoesNotExist() }) _, _ = any("").(interface{ DoesNotExist() }) _, _ = any(outside{}).(interface{ DoesNotExist() }) type inside struct{} _, _ = any(inside{}).(interface{ DoesNotExist() }) } ================================================ FILE: testdata/init.txt ================================================ init main v1: 3 v2: 2 5 v3: 4 4 2 7 v4: 0 true v5: 0 false v6: false v7: 3 foo v8: barba v9: 3 2 3 4 3 3 5 5 ================================================ FILE: testdata/init_multi.go ================================================ package main func init() { println("init1") } func init() { println("init2") } func main() { println("main") } ================================================ FILE: testdata/init_multi.txt ================================================ init1 init2 main ================================================ FILE: testdata/interface.go ================================================ package main import "time" func main() { thing := &Thing{"foo"} println("thing:", thing.String()) thing.Print() printItf(5) printItf(byte('x')) printItf("foo") printItf(Foo(18)) printItf(*thing) printItf(thing) printItf(Stringer(thing)) printItf(struct{ n int }{}) printItf(struct { n int `foo:"bar"` }{}) printItf(Number(3)) array := Array([4]uint32{1, 7, 11, 13}) printItf(array) printItf(ArrayStruct{3, array}) printItf(SmallPair{3, 5}) s := Stringer(thing) println("Stringer.String():", s.String()) var itf interface{} = s println("Stringer.(*Thing).String():", itf.(Stringer).String()) if s, ok := s.(interface{ String() string }); ok { println("s has String() method:", s.String()) } println("nested switch:", nestedSwitch('v', 3)) // Try putting a linked list in an interface: // https://github.com/tinygo-org/tinygo/issues/309 itf = linkedList{} // Test bugfix for assertion on named empty interface: // https://github.com/tinygo-org/tinygo/issues/453 _, _ = itf.(Empty) var v Byter = FooByte(3) println("Byte(): ", v.Byte()) var n int var f float32 var interfaceEqualTests = []struct { equal bool lhs interface{} rhs interface{} }{ {true, true, true}, {true, int(1), int(1)}, {true, int8(1), int8(1)}, {true, int16(1), int16(1)}, {true, int32(1), int32(1)}, {true, int64(1), int64(1)}, {true, uint(1), uint(1)}, {false, uint(1), uint(2)}, {true, uint8(1), uint8(1)}, {true, uint16(1), uint16(1)}, {true, uint32(1), uint32(1)}, {true, uint64(1), uint64(1)}, {true, uintptr(1), uintptr(1)}, {true, float32(1.1), float32(1.1)}, {true, float64(1.1), float64(1.1)}, {true, complex(100, 8), complex(100, 8)}, {false, complex(100, 8), complex(101, 8)}, {false, complex(100, 8), complex(100, 9)}, {true, complex64(8), complex64(8)}, {true, complex128(8), complex128(8)}, {true, "string", "string"}, {false, "string", "stringx"}, {true, [2]int16{-5, 201}, [2]int16{-5, 201}}, {false, [2]int16{-5, 201}, [2]int16{-5, 202}}, {false, [2]int16{-5, 201}, [2]int16{5, 201}}, {true, &n, &n}, {false, &n, new(int)}, {false, new(int), new(int)}, {false, &n, &f}, {true, struct { a int b int }{3, 5}, struct { a int b int }{3, 5}}, {false, struct { a int b int }{3, 5}, struct { a int b int }{3, 6}}, {true, named1(), named1()}, {true, named2(), named2()}, {false, named1(), named2()}, {false, named2(), named3()}, {true, namedptr1(), namedptr1()}, {false, namedptr1(), namedptr2()}, } for i, tc := range interfaceEqualTests { if (tc.lhs == tc.rhs) != tc.equal { println("test", i, "of interfaceEqualTests failed") } } // test interface blocking blockDynamic(NonBlocker{}) println("non-blocking call on sometimes-blocking interface") blockDynamic(SleepBlocker(time.Millisecond)) println("slept 1ms") blockStatic(SleepBlocker(time.Millisecond)) println("slept 1ms") // check that pointer-to-pointer type switches work ptrptrswitch() // check that type asserts to interfaces with no methods work emptyintfcrash() } func printItf(val interface{}) { switch val := val.(type) { case Unmatched: panic("matched the unmatchable") case Doubler: println("is Doubler:", val.Double()) case Tuple: println("is Tuple:", val.Nth(0), val.Nth(1), val.Nth(2), val.Nth(3)) val.Print() case int: println("is int:", val) case byte: println("is byte:", val) case string: println("is string:", val) case Thing: println("is Thing:", val.String()) case *Thing: println("is *Thing:", val.String()) case struct{ i int }: println("is struct{i int}") case struct{ n int }: println("is struct{n int}") case struct { n int `foo:"bar"` }: println("is struct{n int `foo:\"bar\"`}") case Foo: println("is Foo:", val) default: println("is ?") } } var ( // Test for type assert support in the interp package. globalThing interface{} = Foo(3) _ = globalThing.(Foo) ) func nestedSwitch(verb rune, arg interface{}) bool { switch verb { case 'v', 's': switch arg.(type) { case int: return true } } return false } func blockDynamic(blocker DynamicBlocker) { blocker.Block() } func blockStatic(blocker StaticBlocker) { blocker.Sleep() } type Thing struct { name string } func (t Thing) String() string { return t.name } func (t Thing) Print() { println("Thing.Print:", t.name) } type Stringer interface { String() string } type Foo int type Number int func (n Number) Double() int { return int(n) * 2 } type Doubler interface { Double() int } type Tuple interface { Nth(int) uint32 Print() } type Array [4]uint32 func (a Array) Nth(n int) uint32 { return a[n] } func (a Array) Print() { println("Array len:", len(a)) } type ArrayStruct struct { n int a Array } func (a ArrayStruct) Nth(n int) uint32 { return a.a[n] } func (a ArrayStruct) Print() { println("ArrayStruct.Print:", len(a.a), a.n) } type SmallPair struct { a byte b byte } func (p SmallPair) Nth(n int) uint32 { return uint32(int(p.a)*n + int(p.b)*n) } func (p SmallPair) Print() { println("SmallPair.Print:", p.a, p.b) } // There is no type that matches this method. type Unmatched interface { NeverImplementedMethod() } type linkedList struct { addr *linkedList } type DynamicBlocker interface { Block() } type NonBlocker struct{} func (b NonBlocker) Block() {} type SleepBlocker time.Duration func (s SleepBlocker) Block() { time.Sleep(time.Duration(s)) } func (s SleepBlocker) Sleep() { s.Block() } type StaticBlocker interface { Sleep() } type Empty interface{} type FooByte int func (f FooByte) Byte() byte { return byte(f) } type Byter interface { Byte() uint8 } // Make sure that named types inside functions do not alias with any other named // functions. type named int func named1() any { return named(0) } func named2() any { type named int return named(0) } func named3() any { type named int return named(0) } func namedptr1() interface{} { type Test int return (*Test)(nil) } func namedptr2() interface{} { type Test byte return (*Test)(nil) } func ptrptrswitch() { identify(0) identify(new(int)) identify(new(*int)) } func identify(itf any) { switch itf.(type) { case int: println("type is int") case *int: println("type is *int") case **int: println("type is **int") default: println("other type??") } } func emptyintfcrash() { if x, ok := any(5).(any); ok { println("x is", x.(int)) } } ================================================ FILE: testdata/interface.txt ================================================ thing: foo Thing.Print: foo is int: 5 is byte: 120 is string: foo is Foo: 18 is Thing: foo is *Thing: foo is *Thing: foo is struct{n int} is struct{n int `foo:"bar"`} is Doubler: 6 is Tuple: 1 7 11 13 Array len: 4 is Tuple: 1 7 11 13 ArrayStruct.Print: 4 3 is Tuple: 0 8 16 24 SmallPair.Print: 3 5 Stringer.String(): foo Stringer.(*Thing).String(): foo s has String() method: foo nested switch: true Byte(): 3 non-blocking call on sometimes-blocking interface slept 1ms slept 1ms type is int type is *int type is **int x is 5 ================================================ FILE: testdata/json.go ================================================ package main import ( "encoding/json" ) func main() { println("int:", encode(3)) println("float64:", encode(3.14)) println("string:", encode("foo")) println("slice of strings:", encode([]string{"foo", "bar"})) } func encode(itf interface{}) string { buf, err := json.Marshal(itf) if err != nil { panic("failed to JSON encode: " + err.Error()) } return string(buf) } ================================================ FILE: testdata/json.txt ================================================ int: 3 float64: 3.14 string: "foo" slice of strings: ["foo","bar"] ================================================ FILE: testdata/ldflags.go ================================================ package main // These globals can be changed using -ldflags="-X main.someGlobal=value". // At the moment, only globals without an initializer can be replaced this way. var someGlobal string func main() { println("someGlobal:", someGlobal) } ================================================ FILE: testdata/ldflags.txt ================================================ someGlobal: foobar ================================================ FILE: testdata/map.go ================================================ package main import ( "sort" "unsafe" ) var testmap1 = map[string]int{"data": 3} var testmap2 = map[string]int{ "one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 9, "ten": 10, "eleven": 11, "twelve": 12, } type ArrayKey [4]byte var testMapArrayKey = map[ArrayKey]int{ ArrayKey([4]byte{1, 2, 3, 4}): 1234, ArrayKey([4]byte{4, 3, 2, 1}): 4321, } var testmapIntInt = map[int]int{1: 1, 2: 4, 3: 9} type namedFloat struct { s string f float32 } func main() { m := map[string]int{"answer": 42, "foo": 3} readMap(m, "answer") readMap(testmap1, "data") readMap(testmap2, "three") readMap(testmap2, "ten") delete(testmap2, "six") readMap(testmap2, "seven") lookup(testmap2, "eight") lookup(testmap2, "nokey") // operations on nil maps var nilmap map[string]int println(m == nil, m != nil, len(m)) println(nilmap == nil, nilmap != nil, len(nilmap)) delete(nilmap, "foo") println("nilmap:", nilmap["bar"]) println(testmapIntInt[2]) testmapIntInt[2] = 42 println(testmapIntInt[2]) for k := range nilmap { println(k) // unreachable } var nilbinmap map[uint16]int delete(nilbinmap, 4) println("nilbinmap:", nilbinmap[5]) arrKey := ArrayKey([4]byte{4, 3, 2, 1}) println(testMapArrayKey[arrKey]) testMapArrayKey[arrKey] = 5555 println(testMapArrayKey[arrKey]) // test maps with interface keys itfMap := map[interface{}]int{ 3.14: 3, 8: 8, uint8(8): 80, "eight": 800, [2]int{5, 2}: 52, true: 1, } println("itfMap[3]:", itfMap[3]) // doesn't exist println("itfMap[3.14]:", itfMap[3.14]) println("itfMap[8]:", itfMap[8]) println("itfMap[uint8(8)]:", itfMap[uint8(8)]) println(`itfMap["eight"]:`, itfMap["eight"]) println(`itfMap[[2]int{5, 2}]:`, itfMap[[2]int{5, 2}]) println("itfMap[true]:", itfMap[true]) delete(itfMap, 8) println("itfMap[8]:", itfMap[8]) for key, value := range itfMap { if key == "eight" { println("itfMap: found key \"eight\":", value) } } // test map with float keys floatMap := map[float32]int{ 42: 84, 3.14: 6, } println("floatMap[42]:", floatMap[42]) println("floatMap[43]:", floatMap[43]) delete(floatMap, 42) println("floatMap[42]:", floatMap[42]) for k, v := range floatMap { println("floatMap key, value:", k, v) } // test maps with struct keys structMap := map[namedFloat]int{ namedFloat{"tau", 6.28}: 5, } println(`structMap[{"tau", 6.28}]:`, structMap[namedFloat{"tau", 6.28}]) println(`structMap[{"Tau", 6.28}]:`, structMap[namedFloat{"Tau", 6.28}]) println(`structMap[{"tau", 3.14}]:`, structMap[namedFloat{"tau", 3.14}]) delete(structMap, namedFloat{"tau", 6.28}) println(`structMap[{"tau", 6.28}]:`, structMap[namedFloat{"tau", 6.28}]) // test preallocated map squares := make(map[int]int, 200) testBigMap(squares, 100) println("tested preallocated map") // test growing maps squares = make(map[int]int, 0) testBigMap(squares, 10) squares = make(map[int]int, 20) testBigMap(squares, 40) println("tested growing of a map") floatcmplx() mapgrow() interfacerehash() } func floatcmplx() { var zero float64 var negz float64 = -zero // test that zero and negative zero hash to the same thing m := make(map[float64]int) m[zero]++ m[negz]++ println(m[negz]) cmap := make(map[complex128]int) var c complex128 c = complex(zero, zero) cmap[c]++ c = complex(negz, negz) cmap[c]++ c = complex(0, 0) println(cmap[c]) c = complex(1, negz) cmap[c]++ c = complex(1, zero) cmap[c]++ println(cmap[c]) c = complex(negz, 2) cmap[c]++ c = complex(zero, 2) cmap[c]++ println(cmap[c]) } func readMap(m map[string]int, key string) { println("map length:", len(m)) println("map read:", key, "=", m[key]) keys := make([]string, 0, len(m)) for k := range m { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { v := m[k] println(" ", k, "=", v) } } func lookup(m map[string]int, key string) { value, ok := m[key] println("lookup with comma-ok:", key, value, ok) } func testBigMap(squares map[int]int, n int) { for i := 0; i < n; i++ { if len(squares) != i { println("unexpected length:", len(squares), "at i =", i) } squares[i] = i * i for j := 0; j <= i; j++ { if v, ok := squares[j]; !ok || v != j*j { if !ok { println("key not found in squares map:", j) } else { println("unexpected value read back from squares map:", j, v) } return } } } } func mapgrow() { m := make(map[int]int) var Delete = 500 if unsafe.Sizeof(uintptr(0)) < 4 { // Reduce the number of iterations on low-memory devices like AVR. Delete = 20 } var N = Delete * 2 for i := 0; i < Delete; i++ { m[i] = i } var deleted bool for k, v := range m { if k == 0 { // grow map for i := Delete; i < N; i++ { m[i] = i } // delete some elements for i := 0; i < Delete; i++ { delete(m, i) } deleted = true continue } // make sure we never see a deleted element later in our iteration if deleted && v < Delete { println("saw deleted element", v) } } if len(m) != N-Delete { println("bad length post grow/delete", len(m)) } seen := make([]bool, Delete) var mcount int for k, v := range m { if k != v { println("element mismatch", k, v) } if k < Delete { println("saw deleted element post-grow", k) } seen[v-Delete] = true mcount++ } for _, v := range seen { if !v { println("missing key", v) } } if mcount != N-Delete { println("bad number of elements post-grow:", mcount) } println("done") } type Counter interface { count() int } type counter struct { i int } func (c *counter) count() int { return c.i } func interfacerehash() { m := make(map[Counter]int) for i := 0; i < 20; i++ { c := &counter{i} m[c] = i } var failures int for k, v := range m { if got := m[k]; got != v { println("lookup failure got", got, "want", v) failures++ } } if failures == 0 { println("no interface lookup failures") } } ================================================ FILE: testdata/map.txt ================================================ map length: 2 map read: answer = 42 answer = 42 foo = 3 map length: 1 map read: data = 3 data = 3 map length: 12 map read: three = 3 eight = 8 eleven = 11 five = 5 four = 4 nine = 9 one = 1 seven = 7 six = 6 ten = 10 three = 3 twelve = 12 two = 2 map length: 12 map read: ten = 10 eight = 8 eleven = 11 five = 5 four = 4 nine = 9 one = 1 seven = 7 six = 6 ten = 10 three = 3 twelve = 12 two = 2 map length: 11 map read: seven = 7 eight = 8 eleven = 11 five = 5 four = 4 nine = 9 one = 1 seven = 7 ten = 10 three = 3 twelve = 12 two = 2 lookup with comma-ok: eight 8 true lookup with comma-ok: nokey 0 false false true 2 true false 0 nilmap: 0 4 42 nilbinmap: 0 4321 5555 itfMap[3]: 0 itfMap[3.14]: 3 itfMap[8]: 8 itfMap[uint8(8)]: 80 itfMap["eight"]: 800 itfMap[[2]int{5, 2}]: 52 itfMap[true]: 1 itfMap[8]: 0 itfMap: found key "eight": 800 floatMap[42]: 84 floatMap[43]: 0 floatMap[42]: 0 floatMap key, value: +3.140000e+000 6 structMap[{"tau", 6.28}]: 5 structMap[{"Tau", 6.28}]: 0 structMap[{"tau", 3.14}]: 0 structMap[{"tau", 6.28}]: 0 tested preallocated map tested growing of a map 2 2 2 2 done no interface lookup failures ================================================ FILE: testdata/math.go ================================================ package main import "math" func main() { for _, n := range []float64{0.3, 1.5, 2.6, -1.1, -3.1, -3.8} { println("n:", n) println(" asin: ", math.Asin(n)) println(" asinh: ", math.Asinh(n)) println(" acos: ", math.Acos(n)) println(" acosh: ", math.Acosh(n)) println(" atan: ", math.Atan(n)) println(" atanh: ", math.Atanh(n)) println(" atan2: ", math.Atan2(n, 0.2)) println(" cbrt: ", math.Cbrt(n)) println(" ceil: ", math.Ceil(n)) println(" cos: ", math.Cos(n)) println(" cosh: ", math.Cosh(n)) println(" erf: ", math.Erf(n)) println(" erfc: ", math.Erfc(n)) println(" exp: ", math.Exp(n)) println(" expm1: ", math.Expm1(n)) println(" exp2: ", math.Exp2(n)) println(" floor: ", math.Floor(n)) f, e := math.Frexp(n) println(" frexp: ", f, e) println(" hypot: ", math.Hypot(n, n*2)) println(" ldexp: ", math.Ldexp(n, 2)) println(" log: ", math.Log(n)) println(" log1p: ", math.Log1p(n)) println(" log10: ", math.Log10(n)) println(" log2: ", math.Log2(n)) println(" max: ", math.Max(n, n+1)) println(" min: ", math.Min(n, n+1)) println(" mod: ", math.Mod(n, n+1)) i, f := math.Modf(n) println(" modf: ", i, f) println(" pow: ", math.Pow(n, n)) println(" remainder:", math.Remainder(n, n+0.2)) println(" sin: ", math.Sin(n)) println(" sinh: ", math.Sinh(n)) println(" sqrt: ", float32(math.Sqrt(float64(n)))) println(" tan: ", math.Tan(n)) println(" tanh: ", math.Tanh(n)) println(" trunc: ", math.Trunc(n)) } } ================================================ FILE: testdata/math.txt ================================================ n: +3.000000e-001 asin: +3.046927e-001 asinh: +2.956730e-001 acos: +1.266104e+000 acosh: NaN atan: +2.914568e-001 atanh: +3.095196e-001 atan2: +9.827937e-001 cbrt: +6.694330e-001 ceil: +1.000000e+000 cos: +9.553365e-001 cosh: +1.045339e+000 erf: +3.286268e-001 erfc: +6.713732e-001 exp: +1.349859e+000 expm1: +3.498588e-001 exp2: +1.231144e+000 floor: +0.000000e+000 frexp: +6.000000e-001 -1 hypot: +6.708204e-001 ldexp: +1.200000e+000 log: -1.203973e+000 log1p: +2.623643e-001 log10: -5.228787e-001 log2: -1.736966e+000 max: +1.300000e+000 min: +3.000000e-001 mod: +3.000000e-001 modf: +0.000000e+000 +3.000000e-001 pow: +6.968453e-001 remainder: -2.000000e-001 sin: +2.955202e-001 sinh: +3.045203e-001 sqrt: +5.477226e-001 tan: +3.093362e-001 tanh: +2.913126e-001 trunc: +0.000000e+000 n: +1.500000e+000 asin: NaN asinh: +1.194763e+000 acos: NaN acosh: +9.624237e-001 atan: +9.827937e-001 atanh: NaN atan2: +1.438245e+000 cbrt: +1.144714e+000 ceil: +2.000000e+000 cos: +7.073720e-002 cosh: +2.352410e+000 erf: +9.661051e-001 erfc: +3.389485e-002 exp: +4.481689e+000 expm1: +3.481689e+000 exp2: +2.828427e+000 floor: +1.000000e+000 frexp: +7.500000e-001 1 hypot: +3.354102e+000 ldexp: +6.000000e+000 log: +4.054651e-001 log1p: +9.162907e-001 log10: +1.760913e-001 log2: +5.849625e-001 max: +2.500000e+000 min: +1.500000e+000 mod: +1.500000e+000 modf: +1.000000e+000 +5.000000e-001 pow: +1.837117e+000 remainder: -2.000000e-001 sin: +9.974950e-001 sinh: +2.129279e+000 sqrt: +1.224745e+000 tan: +1.410142e+001 tanh: +9.051483e-001 trunc: +1.000000e+000 n: +2.600000e+000 asin: NaN asinh: +1.683743e+000 acos: NaN acosh: +1.609438e+000 atan: +1.203622e+000 atanh: NaN atan2: +1.494024e+000 cbrt: +1.375069e+000 ceil: +3.000000e+000 cos: -8.568888e-001 cosh: +6.769006e+000 erf: +9.997640e-001 erfc: +2.360344e-004 exp: +1.346374e+001 expm1: +1.246374e+001 exp2: +6.062866e+000 floor: +2.000000e+000 frexp: +6.500000e-001 2 hypot: +5.813777e+000 ldexp: +1.040000e+001 log: +9.555114e-001 log1p: +1.280934e+000 log10: +4.149733e-001 log2: +1.378512e+000 max: +3.600000e+000 min: +2.600000e+000 mod: +2.600000e+000 modf: +2.000000e+000 +6.000000e-001 pow: +1.199308e+001 remainder: -2.000000e-001 sin: +5.155014e-001 sinh: +6.694732e+000 sqrt: +1.612452e+000 tan: -6.015966e-001 tanh: +9.890274e-001 trunc: +2.000000e+000 n: -1.100000e+000 asin: NaN asinh: -9.503469e-001 acos: NaN acosh: NaN atan: -8.329813e-001 atanh: NaN atan2: -1.390943e+000 cbrt: -1.032280e+000 ceil: -1.000000e+000 cos: +4.535961e-001 cosh: +1.668519e+000 erf: -8.802051e-001 erfc: +1.880205e+000 exp: +3.328711e-001 expm1: -6.671289e-001 exp2: +4.665165e-001 floor: -2.000000e+000 frexp: -5.500000e-001 1 hypot: +2.459675e+000 ldexp: -4.400000e+000 log: NaN log1p: NaN log10: NaN log2: NaN max: -1.000000e-001 min: -1.100000e+000 mod: -1.000000e-001 modf: -1.000000e+000 -1.000000e-001 pow: NaN remainder: -2.000000e-001 sin: -8.912074e-001 sinh: -1.335647e+000 sqrt: NaN tan: -1.964760e+000 tanh: -8.004990e-001 trunc: -1.000000e+000 n: -3.100000e+000 asin: NaN asinh: -1.849604e+000 acos: NaN acosh: NaN atan: -1.258754e+000 atanh: NaN atan2: -1.506369e+000 cbrt: -1.458100e+000 ceil: -3.000000e+000 cos: -9.991352e-001 cosh: +1.112150e+001 erf: -9.999884e-001 erfc: +1.999988e+000 exp: +4.504920e-002 expm1: -9.549508e-001 exp2: +1.166291e-001 floor: -4.000000e+000 frexp: -7.750000e-001 2 hypot: +6.931811e+000 ldexp: -1.240000e+001 log: NaN log1p: NaN log10: NaN log2: NaN max: -2.100000e+000 min: -3.100000e+000 mod: -1.000000e+000 modf: -3.000000e+000 -1.000000e-001 pow: NaN remainder: -2.000000e-001 sin: -4.158066e-002 sinh: -1.107645e+001 sqrt: NaN tan: +4.161665e-002 tanh: -9.959494e-001 trunc: -3.000000e+000 n: -3.800000e+000 asin: NaN asinh: -2.045028e+000 acos: NaN acosh: NaN atan: -1.313473e+000 atanh: NaN atan2: -1.518213e+000 cbrt: -1.560491e+000 ceil: -3.000000e+000 cos: -7.909677e-001 cosh: +2.236178e+001 erf: -9.999999e-001 erfc: +2.000000e+000 exp: +2.237077e-002 expm1: -9.776292e-001 exp2: +7.179365e-002 floor: -4.000000e+000 frexp: -9.500000e-001 2 hypot: +8.497058e+000 ldexp: -1.520000e+001 log: NaN log1p: NaN log10: NaN log2: NaN max: -2.800000e+000 min: -3.800000e+000 mod: -1.000000e+000 modf: -3.000000e+000 -8.000000e-001 pow: NaN remainder: -2.000000e-001 sin: +6.118579e-001 sinh: -2.233941e+001 sqrt: NaN tan: -7.735561e-001 tanh: -9.989996e-001 trunc: -3.000000e+000 ================================================ FILE: testdata/oldgo/go.mod ================================================ module github.com/tinygo-org/tinygo/testdata/oldgo // Go version doesn't matter much, as long as it's old. go 1.15 ================================================ FILE: testdata/oldgo/main.go ================================================ package main // This package verifies that the Go language version is correctly picked up // from the go.mod file. func main() { testLoopVar() } func testLoopVar() { var f func() int for i := 0; i < 1; i++ { if i == 0 { f = func() int { return i } } } // Variable n is 1 in Go 1.21, or 0 in Go 1.22. n := f() if n == 0 { println("loops behave like Go 1.22") } else if n == 1 { println("loops behave like Go 1.21") } else { println("unknown loop behavior") } } ================================================ FILE: testdata/oldgo/out.txt ================================================ loops behave like Go 1.21 ================================================ FILE: testdata/print.go ================================================ package main func main() { // test basic printing println("hello world!") println(42) println(100000000) // check that this one doesn't print an extra space between args print("a", "b", "c") println() // ..but this one does println("a", "b", "c") // print integers println(uint8(123)) println(int8(123)) println(int8(-123)) println(uint16(12345)) println(int16(12345)) println(int16(-12345)) println(uint32(12345678)) println(int32(12345678)) println(int32(-12345678)) println(uint64(123456789012)) println(int64(123456789012)) println(int64(-123456789012)) // print float64 println(3.14) // print float32 println(float32(3.14)) // print complex128 println(5 + 1.2345i) // print interface println(interface{}(nil)) println(interface{}(true)) println(interface{}("foobar")) println(interface{}(int64(-3))) println(interface{}(uint64(3))) println(interface{}(int(-3))) println(interface{}(uint(3))) // print map println(map[string]int{"three": 3, "five": 5}) // TODO: print pointer // print bool println(true, false) // print slice println([]byte(nil)) println([]int(nil)) } ================================================ FILE: testdata/print.txt ================================================ hello world! 42 100000000 abc a b c 123 123 -123 12345 12345 -12345 12345678 12345678 -12345678 123456789012 123456789012 -123456789012 +3.140000e+000 +3.140000e+000 (+5.000000e+000+1.234500e+000i) (0:nil) true foobar -3 3 -3 3 map[2] true false [0/0]nil [0/0]nil ================================================ FILE: testdata/rand.go ================================================ package main import "crypto/rand" // TODO: make this a test in the crypto/rand package. func main() { buf := make([]byte, 500) n, err := rand.Read(buf) if n != len(buf) || err != nil { println("could not read random numbers:", err) } // Very simple test that random numbers are at least somewhat random. sum := 0 for _, b := range buf { sum += int(b) } if sum < 95*len(buf) || sum > 159*len(buf) { println("random numbers don't seem that random, the average byte is", sum/len(buf)) } else { println("random number check was successful") } } ================================================ FILE: testdata/rand.txt ================================================ random number check was successful ================================================ FILE: testdata/recover.go ================================================ package main import ( "runtime" "sync" ) var wg sync.WaitGroup func main() { println("# simple recover") recoverSimple() println("\n# recover with result") result := recoverWithResult() println("result:", result) println("\n# nested defer frame") nestedDefer() println("\n# nested panic: panic inside recover") nestedPanic() println("\n# panic inside defer") panicInsideDefer() println("\n# panic replace") panicReplace() println("\n# defer panic") deferPanic() println("\n# runtime.Goexit") runtimeGoexit() } func recoverSimple() { defer func() { println("recovering...") printitf("recovered:", recover()) }() println("running panic...") panic("panic") } func recoverWithResult() (result int) { defer func() { printitf("recovered:", recover()) }() result = 3 println("running panic...") panic("panic") } func nestedDefer() { defer func() { printitf("recovered:", recover()) }() func() { // The defer here doesn't catch the panic using recover(), so the outer // panic should do that. defer func() { println("deferred nested function") }() panic("panic") }() println("unreachable") } func nestedPanic() { defer func() { printitf("recovered 1:", recover()) defer func() { printitf("recovered 2:", recover()) }() panic("foo") }() panic("panic") } func panicInsideDefer() { defer func() { printitf("recovered:", recover()) }() defer func() { panic("panic") }() } func panicReplace() { defer func() { printitf("recovered:", recover()) }() defer func() { println("panic 2") panic("panic 2") }() println("panic 1") panic("panic 1") } func deferPanic() { defer func() { printitf("recovered from deferred call:", recover()) }() // This recover should not do anything. defer recover() defer panic("deferred panic") println("defer panic") } func runtimeGoexit() { wg.Add(1) go func() { defer func() { println("Goexit deferred function, recover is nil:", recover() == nil) wg.Done() }() runtime.Goexit() }() wg.Wait() } func printitf(msg string, itf interface{}) { switch itf := itf.(type) { case string: println(msg, itf) default: println(msg, itf) } } ================================================ FILE: testdata/recover.txt ================================================ # simple recover running panic... recovering... recovered: panic # recover with result running panic... recovered: panic result: 3 # nested defer frame deferred nested function recovered: panic # nested panic: panic inside recover recovered 1: panic recovered 2: foo # panic inside defer recovered: panic # panic replace panic 1 panic 2 recovered: panic 2 # defer panic defer panic recovered from deferred call: deferred panic # runtime.Goexit Goexit deferred function, recover is nil: true ================================================ FILE: testdata/reflect.go ================================================ package main import ( "errors" "reflect" "strconv" "unsafe" ) type ( myint int myslice []byte myslice2 []myint mychan chan int myptr *int point struct { X int16 Y int16 } mystruct struct { n int `foo:"bar"` some point "some\x00tag" zero struct{} buf []byte Buf []byte } linkedList struct { next *linkedList `description:"chain"` foo int } selfref struct { x *selfref } ) var ( errorValue = errors.New("test error") errorType = reflect.TypeOf((*error)(nil)).Elem() stringerType = reflect.TypeOf((*interface { String() string })(nil)).Elem() ) func main() { println("matching types") println(reflect.TypeOf(int(3)) == reflect.TypeOf(int(5))) println(reflect.TypeOf(int(3)) == reflect.TypeOf(uint(5))) println(reflect.TypeOf(myint(3)) == reflect.TypeOf(int(5))) println(reflect.TypeOf(myslice{}) == reflect.TypeOf([]byte{})) println(reflect.TypeOf(myslice2{}) == reflect.TypeOf([]myint{})) println(reflect.TypeOf(myslice2{}) == reflect.TypeOf([]int{})) println("\nvalues of interfaces") var zeroSlice []byte var zeroFunc func() // by embedding a 0-array func type in your struct, it is not comparable type doNotCompare [0]func() type notComparable struct { doNotCompare data *int32 } var zeroMap map[string]int var zeroChan chan int n := 42 for _, v := range []interface{}{ // basic types true, false, int(2000), int(-2000), uint(2000), int8(-3), int8(3), uint8(200), int16(-300), int16(300), uint16(50000), int32(7 << 20), int32(-7 << 20), uint32(7 << 20), int64(9 << 40), int64(-9 << 40), uint64(9 << 40), uintptr(12345), float32(3.14), float64(3.14), complex64(1.2 + 0.3i), complex128(1.3 + 0.4i), myint(32), "foo", unsafe.Pointer(new(int)), // channels zeroChan, mychan(zeroChan), // pointers new(int), new(error), &n, myptr(new(int)), // slices []byte{1, 2, 3}, make([]uint8, 2, 5), []rune{3, 5}, []string{"xyz", "Z"}, zeroSlice, []byte{}, []float32{1, 1.32}, []float64{1, 1.64}, []complex64{1, 1.64 + 0.3i}, []complex128{1, 1.128 + 0.4i}, myslice{5, 3, 11}, // array [3]int64{5, 8, 2}, [2]uint8{3, 5}, // functions zeroFunc, emptyFunc, // maps zeroMap, map[string]int{}, // structs struct{}{}, struct{ error }{}, struct { a uint8 b int16 c int8 }{42, 321, 123}, mystruct{5, point{-5, 3}, struct{}{}, []byte{'G', 'o'}, []byte{'X'}}, &linkedList{ foo: 42, }, struct{ A, B uintptr }{2, 3}, // interfaces []interface{}{3, "str", -4 + 2.5i}, } { showValue(reflect.ValueOf(v), "") } // Test reflect.New(). newInt8 := reflect.New(reflect.TypeOf(int8(0))) newInt8.Elem().SetInt(5) newInt16 := reflect.New(reflect.TypeOf(int16(0))) newInt16.Elem().SetInt(-800) newInt32 := reflect.New(reflect.TypeOf(int32(0))) newInt32.Elem().SetInt(1e8) newInt64 := reflect.New(reflect.TypeOf(int64(0))) newInt64.Elem().SetInt(-1e12) newComplex128 := reflect.New(reflect.TypeOf(0 + 0i)) newComplex128.Elem().SetComplex(-8 - 20e5i) for _, val := range []reflect.Value{newInt8, newInt16, newInt32, newInt64, newComplex128} { showValue(val, "") } // test sizes println("\nsizes:") for _, tc := range []struct { name string rt reflect.Type }{ {"int8", reflect.TypeOf(int8(0))}, {"int16", reflect.TypeOf(int16(0))}, {"int32", reflect.TypeOf(int32(0))}, {"int64", reflect.TypeOf(int64(0))}, {"uint8", reflect.TypeOf(uint8(0))}, {"uint16", reflect.TypeOf(uint16(0))}, {"uint32", reflect.TypeOf(uint32(0))}, {"uint64", reflect.TypeOf(uint64(0))}, {"float32", reflect.TypeOf(float32(0))}, {"float64", reflect.TypeOf(float64(0))}, {"complex64", reflect.TypeOf(complex64(0))}, {"complex128", reflect.TypeOf(complex128(0))}, } { println(tc.name, int(tc.rt.Size()), tc.rt.Bits()) } assertSize(reflect.TypeOf(uintptr(0)).Size() == unsafe.Sizeof(uintptr(0)), "uintptr") assertSize(reflect.TypeOf("").Size() == unsafe.Sizeof(""), "string") assertSize(reflect.TypeOf(new(int)).Size() == unsafe.Sizeof(new(int)), "*int") assertSize(reflect.TypeOf(zeroFunc).Size() == unsafe.Sizeof(zeroFunc), "func()") assertSize(reflect.TypeOf(zeroChan).Size() == unsafe.Sizeof(zeroChan), "chan int") assertSize(reflect.TypeOf(zeroMap).Size() == unsafe.Sizeof(zeroMap), "map[string]int") // make sure embedding a zero-sized "not comparable" struct does not add size to a struct assertSize(reflect.TypeOf(doNotCompare{}).Size() == unsafe.Sizeof(doNotCompare{}), "[0]func()") assertSize(unsafe.Sizeof(notComparable{}) == unsafe.Sizeof((*int32)(nil)), "struct{[0]func(); *int32}") // Test that offset is correctly calculated. // This doesn't just test reflect but also (indirectly) that unsafe.Alignof // works correctly. s := struct { small1 byte big1 int64 small2 byte big2 int64 }{} st := reflect.TypeOf(s) println("offset for int64 matches:", st.Field(1).Offset-st.Field(0).Offset == uintptr(unsafe.Pointer(&s.big1))-uintptr(unsafe.Pointer(&s.small1))) println("offset for complex128 matches:", st.Field(3).Offset-st.Field(2).Offset == uintptr(unsafe.Pointer(&s.big2))-uintptr(unsafe.Pointer(&s.small2))) // SetBool rv := reflect.ValueOf(new(bool)).Elem() rv.SetBool(true) if rv.Bool() != true { panic("could not set bool with SetBool()") } // SetInt for _, v := range []interface{}{ new(int), new(int8), new(int16), new(int32), new(int64), } { rv := reflect.ValueOf(v).Elem() rv.SetInt(99) if rv.Int() != 99 { panic("could not set integer with SetInt()") } } // SetUint for _, v := range []interface{}{ new(uint), new(uint8), new(uint16), new(uint32), new(uint64), new(uintptr), } { rv := reflect.ValueOf(v).Elem() rv.SetUint(99) if rv.Uint() != 99 { panic("could not set integer with SetUint()") } } // SetFloat for _, v := range []interface{}{ new(float32), new(float64), } { rv := reflect.ValueOf(v).Elem() rv.SetFloat(2.25) if rv.Float() != 2.25 { panic("could not set float with SetFloat()") } } // SetComplex for _, v := range []interface{}{ new(complex64), new(complex128), } { rv := reflect.ValueOf(v).Elem() rv.SetComplex(3 + 2i) if rv.Complex() != 3+2i { panic("could not set complex with SetComplex()") } } // SetString rv = reflect.ValueOf(new(string)).Elem() rv.SetString("foo") if rv.String() != "foo" { panic("could not set string with SetString()") } // Set int rv = reflect.ValueOf(new(int)).Elem() rv.SetInt(33) rv.Set(reflect.ValueOf(22)) if rv.Int() != 22 { panic("could not set int with Set()") } // Set uint8 rv = reflect.ValueOf(new(uint8)).Elem() rv.SetUint(33) rv.Set(reflect.ValueOf(uint8(22))) if rv.Uint() != 22 { panic("could not set uint8 with Set()") } // Set string rv = reflect.ValueOf(new(string)).Elem() rv.SetString("foo") rv.Set(reflect.ValueOf("bar")) if rv.String() != "bar" { panic("could not set string with Set()") } // Set complex128 rv = reflect.ValueOf(new(complex128)).Elem() rv.SetComplex(3 + 2i) rv.Set(reflect.ValueOf(4 + 8i)) if rv.Complex() != 4+8i { panic("could not set complex128 with Set()") } // Set to slice rv = reflect.ValueOf([]int{3, 5}) rv.Index(1).SetInt(7) if rv.Index(1).Int() != 7 { panic("could not set int in slice") } rv.Index(1).Set(reflect.ValueOf(8)) if rv.Index(1).Int() != 8 { panic("could not set int in slice") } if rv.Len() != 2 || rv.Index(0).Int() != 3 { panic("slice was changed while setting part of it") } testAppendSlice() // Test types that are created in reflect and never created elsewhere in a // value-to-interface conversion. v := reflect.ValueOf(new(unreferencedType)) switch v.Elem().Interface().(type) { case unreferencedType: println("type assertion succeeded for unreferenced type") default: println("type assertion failed (but should succeed)") } // Test type that is not referenced at all: not when creating the // reflect.Value (except through the field) and not with a type assert. // Previously this would result in a type assert failure because the Int() // method wasn't picked up. v = reflect.ValueOf(struct { X totallyUnreferencedType }{}) if v.Field(0).Interface().(interface { Int() int }).Int() != 42 { println("could not call method on totally unreferenced type") } if reflect.TypeOf(new(myint)) != reflect.PtrTo(reflect.TypeOf(myint(0))) { println("PtrTo failed for type myint") } if reflect.TypeOf(new(myslice)) != reflect.PtrTo(reflect.TypeOf(make(myslice, 0))) { println("PtrTo failed for type myslice") } if reflect.TypeOf(errorValue).Implements(errorType) != true { println("errorValue.Implements(errorType) was false, expected true") } if reflect.TypeOf(errorValue).Implements(stringerType) != false { println("errorValue.Implements(errorType) was true, expected false") } println("\nalignment / offset:") v2 := struct { noCompare [0]func() data byte }{} println("struct{[0]func(); byte}:", unsafe.Offsetof(v2.data) == uintptr(unsafe.Pointer(&v2.data))-uintptr(unsafe.Pointer(&v2))) println("\nstruct tags") TestStructTag() println("\nv.Interface() method") testInterfaceMethod() // Test reflect.DeepEqual. var selfref1, selfref2 selfref selfref1.x = &selfref1 selfref2.x = &selfref2 for i, tc := range []struct { v1, v2 interface{} equal bool }{ {int(5), int(5), true}, {int(3), int(5), false}, {int(5), uint(5), false}, {struct { a int b string }{3, "x"}, struct { a int b string }{3, "x"}, true}, {struct { a int b string }{3, "x"}, struct { a int b string }{3, "y"}, false}, {selfref1, selfref2, true}, } { result := reflect.DeepEqual(tc.v1, tc.v2) if result != tc.equal { if tc.equal { println("reflect.DeepEqual() test", i, "not equal while it should be") } else { println("reflect.DeepEqual() test", i, "equal while it should not be") } } } } func emptyFunc() { } func showValue(rv reflect.Value, indent string) { rt := rv.Type() if rt.Kind() != rv.Kind() { panic("type kind is different from value kind") } print(indent+"reflect type: ", rt.Kind().String()) if rv.CanSet() { print(" settable=true") } if rv.CanAddr() { print(" addrable=true") } if !rv.CanInterface() { print(" caninterface=false") } if !rt.Comparable() { print(" comparable=false") } if name := rt.Name(); name != "" { print(" name=", name) } println() switch rt.Kind() { case reflect.Bool: println(indent+" bool:", rv.Bool()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: println(indent+" int:", rv.Int()) case reflect.Uint, reflect.Uintptr, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: println(indent+" uint:", rv.Uint()) case reflect.Float32, reflect.Float64: println(indent+" float:", rv.Float()) case reflect.Complex64, reflect.Complex128: println(indent+" complex:", rv.Complex()) case reflect.String: println(indent+" string:", rv.String(), rv.Len()) for i := 0; i < rv.Len(); i++ { showValue(rv.Index(i), indent+" ") } case reflect.UnsafePointer: println(indent+" pointer:", rv.Pointer() != 0) case reflect.Array: println(indent+" array:", rt.Len(), rt.Elem().Kind().String(), int(rt.Size())) for i := 0; i < rv.Len(); i++ { showValue(rv.Index(i), indent+" ") } case reflect.Chan: println(indent+" chan:", rt.Elem().Kind().String()) println(indent+" nil:", rv.IsNil()) case reflect.Func: println(indent + " func") println(indent+" nil:", rv.IsNil()) case reflect.Interface: println(indent + " interface") println(indent+" nil:", rv.IsNil()) println(indent+" NumMethod:", rv.NumMethod()) if !rv.IsNil() { showValue(rv.Elem(), indent+" ") } case reflect.Map: println(indent + " map") println(indent+" nil:", rv.IsNil()) case reflect.Ptr: println(indent+" pointer:", rv.Pointer() != 0, rt.Elem().Kind().String()) println(indent+" nil:", rv.IsNil()) if !rv.IsNil() { showValue(rv.Elem(), indent+" ") } case reflect.Slice: println(indent+" slice:", rt.Elem().Kind().String(), rv.Len(), rv.Cap()) println(indent+" pointer:", rv.Pointer() != 0) println(indent+" nil:", rv.IsNil()) for i := 0; i < rv.Len(); i++ { println(indent+" indexing:", i) showValue(rv.Index(i), indent+" ") } case reflect.Struct: println(indent+" struct:", rt.NumField()) for i := 0; i < rv.NumField(); i++ { field := rt.Field(i) println(indent+" field:", i, field.Name) println(indent+" pkg:", field.PkgPath) println(indent+" tag:", strconv.Quote(string(field.Tag))) println(indent+" embedded:", field.Anonymous) println(indent+" exported:", field.IsExported()) showValue(rv.Field(i), indent+" ") } default: println(indent + " unknown type kind!") } } func assertSize(ok bool, typ string) { if !ok { panic("size mismatch for type " + typ) } } // Test whether appending to a slice is equivalent between reflect and native // slice append. func testAppendSlice() { for i := 0; i < 100; i++ { dst := makeRandomSlice(i) src := makeRandomSlice(i) result1 := append(dst, src...) result2 := reflect.AppendSlice(reflect.ValueOf(dst), reflect.ValueOf(src)).Interface().([]uint32) if !sliceEqual(result1, result2) { println("slice: mismatch after runtime.SliceAppend with", len(dst), cap(dst), len(src), cap(src)) } } } func makeRandomSlice(max int) []uint32 { cap := randuint32() % uint32(max+1) len := randuint32() % (cap + 1) s := make([]uint32, len, cap) for i := uint32(0); i < len; i++ { s[i] = randuint32() } return s } func sliceEqual(s1, s2 []uint32) bool { if len(s1) != len(s2) { return false } for i, val := range s1 { if s2[i] != val { return false } } // Note: can't compare cap because the Go implementation has a different // behavior between the built-in append function and // reflect.AppendSlice. return true } type unreferencedType int type totallyUnreferencedType int func (totallyUnreferencedType) Int() int { return 42 } func TestStructTag() { type S struct { F string `species:"gopher" color:"blue"` } s := S{} st := reflect.TypeOf(s) field := st.Field(0) println(field.Tag.Get("color"), field.Tag.Get("species")) } // Test Interface() call: it should never return an interface itself. func testInterfaceMethod() { v := reflect.ValueOf(struct{ X interface{} }{X: 5}) println("kind:", v.Field(0).Kind().String()) itf := v.Field(0).Interface() switch n := itf.(type) { case int: println("int", n) // correct default: println("something else") // incorrect } } var xorshift32State uint32 = 1 func xorshift32(x uint32) uint32 { // Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs" x ^= x << 13 x ^= x >> 17 x ^= x << 5 return x } func randuint32() uint32 { xorshift32State = xorshift32(xorshift32State) return xorshift32State } ================================================ FILE: testdata/reflect.txt ================================================ matching types true false false false false false values of interfaces reflect type: bool name=bool bool: true reflect type: bool name=bool bool: false reflect type: int name=int int: 2000 reflect type: int name=int int: -2000 reflect type: uint name=uint uint: 2000 reflect type: int8 name=int8 int: -3 reflect type: int8 name=int8 int: 3 reflect type: uint8 name=uint8 uint: 200 reflect type: int16 name=int16 int: -300 reflect type: int16 name=int16 int: 300 reflect type: uint16 name=uint16 uint: 50000 reflect type: int32 name=int32 int: 7340032 reflect type: int32 name=int32 int: -7340032 reflect type: uint32 name=uint32 uint: 7340032 reflect type: int64 name=int64 int: 9895604649984 reflect type: int64 name=int64 int: -9895604649984 reflect type: uint64 name=uint64 uint: 9895604649984 reflect type: uintptr name=uintptr uint: 12345 reflect type: float32 name=float32 float: +3.140000e+000 reflect type: float64 name=float64 float: +3.140000e+000 reflect type: complex64 name=complex64 complex: (+1.200000e+000+3.000000e-001i) reflect type: complex128 name=complex128 complex: (+1.300000e+000+4.000000e-001i) reflect type: int name=myint int: 32 reflect type: string name=string string: foo 3 reflect type: uint8 name=uint8 uint: 102 reflect type: uint8 name=uint8 uint: 111 reflect type: uint8 name=uint8 uint: 111 reflect type: unsafe.Pointer name=Pointer pointer: true reflect type: chan chan: int nil: true reflect type: chan name=mychan chan: int nil: true reflect type: ptr pointer: true int nil: false reflect type: int settable=true addrable=true name=int int: 0 reflect type: ptr pointer: true interface nil: false reflect type: interface settable=true addrable=true name=error interface nil: true NumMethod: 1 reflect type: ptr pointer: true int nil: false reflect type: int settable=true addrable=true name=int int: 42 reflect type: ptr name=myptr pointer: true int nil: false reflect type: int settable=true addrable=true name=int int: 0 reflect type: slice comparable=false slice: uint8 3 3 pointer: true nil: false indexing: 0 reflect type: uint8 settable=true addrable=true name=uint8 uint: 1 indexing: 1 reflect type: uint8 settable=true addrable=true name=uint8 uint: 2 indexing: 2 reflect type: uint8 settable=true addrable=true name=uint8 uint: 3 reflect type: slice comparable=false slice: uint8 2 5 pointer: true nil: false indexing: 0 reflect type: uint8 settable=true addrable=true name=uint8 uint: 0 indexing: 1 reflect type: uint8 settable=true addrable=true name=uint8 uint: 0 reflect type: slice comparable=false slice: int32 2 2 pointer: true nil: false indexing: 0 reflect type: int32 settable=true addrable=true name=int32 int: 3 indexing: 1 reflect type: int32 settable=true addrable=true name=int32 int: 5 reflect type: slice comparable=false slice: string 2 2 pointer: true nil: false indexing: 0 reflect type: string settable=true addrable=true name=string string: xyz 3 reflect type: uint8 name=uint8 uint: 120 reflect type: uint8 name=uint8 uint: 121 reflect type: uint8 name=uint8 uint: 122 indexing: 1 reflect type: string settable=true addrable=true name=string string: Z 1 reflect type: uint8 name=uint8 uint: 90 reflect type: slice comparable=false slice: uint8 0 0 pointer: false nil: true reflect type: slice comparable=false slice: uint8 0 0 pointer: true nil: false reflect type: slice comparable=false slice: float32 2 2 pointer: true nil: false indexing: 0 reflect type: float32 settable=true addrable=true name=float32 float: +1.000000e+000 indexing: 1 reflect type: float32 settable=true addrable=true name=float32 float: +1.320000e+000 reflect type: slice comparable=false slice: float64 2 2 pointer: true nil: false indexing: 0 reflect type: float64 settable=true addrable=true name=float64 float: +1.000000e+000 indexing: 1 reflect type: float64 settable=true addrable=true name=float64 float: +1.640000e+000 reflect type: slice comparable=false slice: complex64 2 2 pointer: true nil: false indexing: 0 reflect type: complex64 settable=true addrable=true name=complex64 complex: (+1.000000e+000+0.000000e+000i) indexing: 1 reflect type: complex64 settable=true addrable=true name=complex64 complex: (+1.640000e+000+3.000000e-001i) reflect type: slice comparable=false slice: complex128 2 2 pointer: true nil: false indexing: 0 reflect type: complex128 settable=true addrable=true name=complex128 complex: (+1.000000e+000+0.000000e+000i) indexing: 1 reflect type: complex128 settable=true addrable=true name=complex128 complex: (+1.128000e+000+4.000000e-001i) reflect type: slice comparable=false name=myslice slice: uint8 3 3 pointer: true nil: false indexing: 0 reflect type: uint8 settable=true addrable=true name=uint8 uint: 5 indexing: 1 reflect type: uint8 settable=true addrable=true name=uint8 uint: 3 indexing: 2 reflect type: uint8 settable=true addrable=true name=uint8 uint: 11 reflect type: array array: 3 int64 24 reflect type: int64 name=int64 int: 5 reflect type: int64 name=int64 int: 8 reflect type: int64 name=int64 int: 2 reflect type: array array: 2 uint8 2 reflect type: uint8 name=uint8 uint: 3 reflect type: uint8 name=uint8 uint: 5 reflect type: func comparable=false func nil: true reflect type: func comparable=false func nil: false reflect type: map comparable=false map nil: true reflect type: map comparable=false map nil: false reflect type: struct struct: 0 reflect type: struct struct: 1 field: 0 error pkg: main tag: "" embedded: true exported: false reflect type: interface caninterface=false name=error interface nil: true NumMethod: 1 reflect type: struct struct: 3 field: 0 a pkg: main tag: "" embedded: false exported: false reflect type: uint8 caninterface=false name=uint8 uint: 42 field: 1 b pkg: main tag: "" embedded: false exported: false reflect type: int16 caninterface=false name=int16 int: 321 field: 2 c pkg: main tag: "" embedded: false exported: false reflect type: int8 caninterface=false name=int8 int: 123 reflect type: struct comparable=false name=mystruct struct: 5 field: 0 n pkg: main tag: "foo:\"bar\"" embedded: false exported: false reflect type: int caninterface=false name=int int: 5 field: 1 some pkg: main tag: "some\x00tag" embedded: false exported: false reflect type: struct caninterface=false name=point struct: 2 field: 0 X pkg: tag: "" embedded: false exported: true reflect type: int16 caninterface=false name=int16 int: -5 field: 1 Y pkg: tag: "" embedded: false exported: true reflect type: int16 caninterface=false name=int16 int: 3 field: 2 zero pkg: main tag: "" embedded: false exported: false reflect type: struct caninterface=false struct: 0 field: 3 buf pkg: main tag: "" embedded: false exported: false reflect type: slice caninterface=false comparable=false slice: uint8 2 2 pointer: true nil: false indexing: 0 reflect type: uint8 addrable=true caninterface=false name=uint8 uint: 71 indexing: 1 reflect type: uint8 addrable=true caninterface=false name=uint8 uint: 111 field: 4 Buf pkg: tag: "" embedded: false exported: true reflect type: slice comparable=false slice: uint8 1 1 pointer: true nil: false indexing: 0 reflect type: uint8 settable=true addrable=true name=uint8 uint: 88 reflect type: ptr pointer: true struct nil: false reflect type: struct settable=true addrable=true name=linkedList struct: 2 field: 0 next pkg: main tag: "description:\"chain\"" embedded: false exported: false reflect type: ptr addrable=true caninterface=false pointer: false struct nil: true field: 1 foo pkg: main tag: "" embedded: false exported: false reflect type: int addrable=true caninterface=false name=int int: 42 reflect type: struct struct: 2 field: 0 A pkg: tag: "" embedded: false exported: true reflect type: uintptr name=uintptr uint: 2 field: 1 B pkg: tag: "" embedded: false exported: true reflect type: uintptr name=uintptr uint: 3 reflect type: slice comparable=false slice: interface 3 3 pointer: true nil: false indexing: 0 reflect type: interface settable=true addrable=true interface nil: false NumMethod: 0 reflect type: int name=int int: 3 indexing: 1 reflect type: interface settable=true addrable=true interface nil: false NumMethod: 0 reflect type: string name=string string: str 3 reflect type: uint8 name=uint8 uint: 115 reflect type: uint8 name=uint8 uint: 116 reflect type: uint8 name=uint8 uint: 114 indexing: 2 reflect type: interface settable=true addrable=true interface nil: false NumMethod: 0 reflect type: complex128 name=complex128 complex: (-4.000000e+000+2.500000e+000i) reflect type: ptr pointer: true int8 nil: false reflect type: int8 settable=true addrable=true name=int8 int: 5 reflect type: ptr pointer: true int16 nil: false reflect type: int16 settable=true addrable=true name=int16 int: -800 reflect type: ptr pointer: true int32 nil: false reflect type: int32 settable=true addrable=true name=int32 int: 100000000 reflect type: ptr pointer: true int64 nil: false reflect type: int64 settable=true addrable=true name=int64 int: -1000000000000 reflect type: ptr pointer: true complex128 nil: false reflect type: complex128 settable=true addrable=true name=complex128 complex: (-8.000000e+000-2.000000e+006i) sizes: int8 1 8 int16 2 16 int32 4 32 int64 8 64 uint8 1 8 uint16 2 16 uint32 4 32 uint64 8 64 float32 4 32 float64 8 64 complex64 8 64 complex128 16 128 offset for int64 matches: true offset for complex128 matches: true type assertion succeeded for unreferenced type alignment / offset: struct{[0]func(); byte}: true struct tags blue gopher v.Interface() method kind: interface int 5 ================================================ FILE: testdata/signal.go ================================================ package main // Test POSIX signals. // TODO: run `tinygo test os/signal` instead, once CGo errno return values are // supported. import ( "os" "os/signal" "syscall" "time" ) func main() { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGUSR1) // Wait for signals to arrive. go func() { for sig := range c { if sig == syscall.SIGUSR1 { println("got expected signal") } else { println("got signal:", sig.String()) } } }() // Send the signal. syscall.Kill(syscall.Getpid(), syscall.SIGUSR1) time.Sleep(time.Millisecond * 100) // Stop notifying. // (This is just a smoke test, it's difficult to test the default behavior // in a unit test). signal.Ignore(syscall.SIGUSR1) signal.Stop(c) println("exiting signal program") } ================================================ FILE: testdata/signal.txt ================================================ got expected signal exiting signal program ================================================ FILE: testdata/slice.go ================================================ package main import "unsafe" type MySlice [32]byte type myUint8 uint8 type RecursiveSlice []RecursiveSlice // Indexing into slice with named type (regression test). var array = [4]int{ myUint8(2): 3, } func main() { l := 5 foo := []int{1, 2, 4, 5} bar := make([]int, l-2, l) println("foo is nil?", foo == nil, nil == foo) printslice("foo", foo) printslice("bar", bar) printslice("foo[1:2]", foo[1:2]) println("sum foo:", sum(foo)) // creating a slice of uncommon base type assert(len(make([]struct{}, makeInt(4))) == 4) // creating a slice with uncommon len, cap types assert(len(make([]int, makeInt(2), makeInt(3))) == 2) assert(len(make([]int, makeInt8(2), makeInt8(3))) == 2) assert(len(make([]int, makeInt16(2), makeInt16(3))) == 2) assert(len(make([]int, makeInt32(2), makeInt32(3))) == 2) assert(len(make([]int, makeInt64(2), makeInt64(3))) == 2) assert(len(make([]int, makeUint(2), makeUint(3))) == 2) assert(len(make([]int, makeUint8(2), makeUint8(3))) == 2) assert(len(make([]int, makeUint16(2), makeUint16(3))) == 2) assert(len(make([]int, makeUint32(2), makeUint32(3))) == 2) assert(len(make([]int, makeUint64(2), makeUint64(3))) == 2) assert(len(make([]int, makeUintptr(2), makeUintptr(3))) == 2) assert(len(make([]int, makeMyUint8(2), makeMyUint8(3))) == 2) // indexing into a slice with uncommon index types assert(foo[int(2)] == 4) assert(foo[int8(2)] == 4) assert(foo[int16(2)] == 4) assert(foo[int32(2)] == 4) assert(foo[int64(2)] == 4) assert(foo[uint(2)] == 4) assert(foo[uint8(2)] == 4) assert(foo[uint16(2)] == 4) assert(foo[uint32(2)] == 4) assert(foo[uint64(2)] == 4) assert(foo[uintptr(2)] == 4) // slicing with uncommon low, high types assert(len(foo[int(1):int(3)]) == 2) assert(len(foo[int8(1):int8(3)]) == 2) assert(len(foo[int16(1):int16(3)]) == 2) assert(len(foo[int32(1):int32(3)]) == 2) assert(len(foo[int64(1):int64(3)]) == 2) assert(len(foo[uint(1):uint(3)]) == 2) assert(len(foo[uint8(1):uint8(3)]) == 2) assert(len(foo[uint16(1):uint16(3)]) == 2) assert(len(foo[uint32(1):uint32(3)]) == 2) assert(len(foo[uint64(1):uint64(3)]) == 2) assert(len(foo[uintptr(1):uintptr(3)]) == 2) // slicing an array with uncommon low, high types arr := [4]int{1, 2, 4, 5} assert(len(arr[int(1):int(3)]) == 2) assert(len(arr[int8(1):int8(3)]) == 2) assert(len(arr[int16(1):int16(3)]) == 2) assert(len(arr[int32(1):int32(3)]) == 2) assert(len(arr[int64(1):int64(3)]) == 2) assert(len(arr[uint(1):uint(3)]) == 2) assert(len(arr[uint8(1):uint8(3)]) == 2) assert(len(arr[uint16(1):uint16(3)]) == 2) assert(len(arr[uint32(1):uint32(3)]) == 2) assert(len(arr[uint64(1):uint64(3)]) == 2) assert(len(arr[uintptr(1):uintptr(3)]) == 2) // slicing with max parameter (added in Go 1.2) longfoo := []int{1, 2, 4, 5, 10, 11} assert(cap(longfoo[int(1):int(3):int(5)]) == 4) assert(cap(longfoo[int8(1):int8(3):int8(5)]) == 4) assert(cap(longfoo[int16(1):int16(3):int16(5)]) == 4) assert(cap(longfoo[int32(1):int32(3):int32(5)]) == 4) assert(cap(longfoo[int64(1):int64(3):int64(5)]) == 4) assert(cap(longfoo[uint(1):uint(3):uint(5)]) == 4) assert(cap(longfoo[uint8(1):uint8(3):uint8(5)]) == 4) assert(cap(longfoo[uint16(1):uint16(3):uint16(5)]) == 4) assert(cap(longfoo[uint32(1):uint32(3):uint32(5)]) == 4) assert(cap(longfoo[uint64(1):uint64(3):uint64(5)]) == 4) assert(cap(longfoo[uintptr(1):uintptr(3):uintptr(5)]) == 4) // slicing an array with max parameter (added in Go 1.2) assert(cap(arr[int(1):int(2):int(4)]) == 3) assert(cap(arr[int8(1):int8(2):int8(4)]) == 3) assert(cap(arr[int16(1):int16(2):int16(4)]) == 3) assert(cap(arr[int32(1):int32(2):int32(4)]) == 3) assert(cap(arr[int64(1):int64(2):int64(4)]) == 3) assert(cap(arr[uint(1):uint(2):uint(4)]) == 3) assert(cap(arr[uint8(1):uint8(2):uint8(4)]) == 3) assert(cap(arr[uint16(1):uint16(2):uint16(4)]) == 3) assert(cap(arr[uint32(1):uint32(2):uint32(4)]) == 3) assert(cap(arr[uint64(1):uint64(2):uint64(4)]) == 3) assert(cap(arr[uintptr(1):uintptr(2):uintptr(4)]) == 3) // copy println("copy foo -> bar:", copy(bar, foo)) printslice("bar", bar) // append var grow []int println("slice is nil?", grow == nil, nil == grow) printslice("grow", grow) grow = append(grow, 42) printslice("grow", grow) grow = append(grow, -1, -2) printslice("grow", grow) grow = append(grow, foo...) printslice("grow", grow) grow = append(grow) printslice("grow", grow) grow = append(grow, grow...) printslice("grow", grow) // append string to []bytes bytes := append([]byte{1, 2, 3}, "foo"...) print("bytes: len=", len(bytes), " cap=", cap(bytes), " data:") for _, n := range bytes { print(" ", n) } println() // Test conversion from array to slice. slice1 := []int{1, 2, 3, 4} arr1 := (*[4]int)(slice1) arr1[1] = -2 arr1[2] = 20 println("slice to array pointer:", arr1[0], arr1[1], arr1[2], arr1[3]) // Test unsafe.Add. arr2 := [...]int{1, 2, 3, 4} *(*int)(unsafe.Add(unsafe.Pointer(&arr2[0]), unsafe.Sizeof(int(1))*1)) = 5 *addInt(&arr2[0], 2) = 8 println("unsafe.Add array:", arr2[0], arr2[1], arr2[2], arr2[3]) // Test unsafe.Slice. arr3 := [...]int{1, 2, 3, 4} slice3 := unsafe.Slice(&arr3[1], 3) slice3[0] = 9 slice3[1] = 15 println("unsafe.Slice array:", len(slice3), cap(slice3), slice3[0], slice3[1], slice3[2]) // Verify the fix in https://github.com/tinygo-org/tinygo/pull/119 var unnamed [32]byte var named MySlice assert(len(unnamed[:]) == 32) assert(len(named[:]) == 32) for _, c := range named { assert(c == 0) } // Test recursive slices. rs := []RecursiveSlice(nil) println("len:", len(rs)) } func printslice(name string, s []int) { print(name, ": len=", len(s), " cap=", cap(s), " data:") for _, n := range s { print(" ", n) } println() } func sum(l []int) int { sum := 0 for _, n := range l { sum += n } return sum } func assert(ok bool) { if !ok { panic("assert failed") } } // Helper functions used to hide const values from the compiler during IR // construction. func makeInt(x int) int { return x } func makeInt8(x int8) int8 { return x } func makeInt16(x int16) int16 { return x } func makeInt32(x int32) int32 { return x } func makeInt64(x int64) int64 { return x } func makeUint(x uint) uint { return x } func makeUint8(x uint8) uint8 { return x } func makeUint16(x uint16) uint16 { return x } func makeUint32(x uint32) uint32 { return x } func makeUint64(x uint64) uint64 { return x } func makeUintptr(x uintptr) uintptr { return x } func makeMyUint8(x myUint8) myUint8 { return x } func addInt(ptr *int, index uintptr) *int { return (*int)(unsafe.Add(unsafe.Pointer(ptr), unsafe.Sizeof(int(1))*index)) } ================================================ FILE: testdata/slice.txt ================================================ foo is nil? false false foo: len=4 cap=4 data: 1 2 4 5 bar: len=3 cap=5 data: 0 0 0 foo[1:2]: len=1 cap=3 data: 2 sum foo: 12 copy foo -> bar: 3 bar: len=3 cap=5 data: 1 2 4 slice is nil? true true grow: len=0 cap=0 data: grow: len=1 cap=2 data: 42 grow: len=3 cap=4 data: 42 -1 -2 grow: len=7 cap=8 data: 42 -1 -2 1 2 4 5 grow: len=7 cap=8 data: 42 -1 -2 1 2 4 5 grow: len=14 cap=16 data: 42 -1 -2 1 2 4 5 42 -1 -2 1 2 4 5 bytes: len=6 cap=8 data: 1 2 3 102 111 111 slice to array pointer: 1 -2 20 4 unsafe.Add array: 1 5 8 4 unsafe.Slice array: 3 3 9 15 4 len: 0 ================================================ FILE: testdata/sort.go ================================================ package main import "sort" // sort.Slice implicitly uses reflect.Swapper func strings() { data := []string{"aaaa", "cccc", "bbb", "fff", "ggg"} sort.Slice(data, func(i, j int) bool { return data[i] > data[j] }) println("strings") for _, d := range data { println(d) } } func int64s() { sd := []int64{1, 6, 3, 2, 1923, 123, -123, -29, 3, 0, 1} sort.Slice(sd, func(i, j int) bool { return sd[i] > sd[j] }) println("int64s") for _, d := range sd { println(d) } ud := []uint64{1, 6, 3, 2, 1923, 123, 29, 3, 0, 1} sort.Slice(ud, func(i, j int) bool { return ud[i] > ud[j] }) println("uint64s") for _, d := range ud { println(d) } } func int32s() { sd := []int32{1, 6, 3, 2, 1923, 123, -123, -29, 3, 0, 1} sort.Slice(sd, func(i, j int) bool { return sd[i] > sd[j] }) println("int32s") for _, d := range sd { println(d) } ud := []uint32{1, 6, 3, 2, 1923, 123, 29, 3, 0, 1} sort.Slice(ud, func(i, j int) bool { return ud[i] > ud[j] }) println("uint32s") for _, d := range ud { println(d) } } func int16s() { sd := []int16{1, 6, 3, 2, 1923, 123, -123, -29, 3, 0, 1} sort.Slice(sd, func(i, j int) bool { return sd[i] > sd[j] }) println("int16s") for _, d := range sd { println(d) } ud := []uint16{1, 6, 3, 2, 1923, 123, 29, 3, 0, 1} sort.Slice(ud, func(i, j int) bool { return ud[i] > ud[j] }) println("uint16s") for _, d := range ud { println(d) } } func int8s() { sd := []int8{1, 6, 3, 2, 123, -123, -29, 3, 0, 1} sort.Slice(sd, func(i, j int) bool { return sd[i] > sd[j] }) println("int8s") for _, d := range sd { println(d) } ud := []uint8{1, 6, 3, 2, 123, 29, 3, 0, 1} sort.Slice(ud, func(i, j int) bool { return ud[i] > ud[j] }) println("uint8s") for _, d := range ud { println(d) } } func ints() { sd := []int{1, 6, 3, 2, 123, -123, -29, 3, 0, 1} sort.Slice(sd, func(i, j int) bool { return sd[i] > sd[j] }) println("ints") for _, d := range sd { println(d) } ud := []uint{1, 6, 3, 2, 123, 29, 3, 0, 1} sort.Slice(ud, func(i, j int) bool { return ud[i] > ud[j] }) println("uints") for _, d := range ud { println(d) } } func structs() { type s struct { name string a uint64 b uint32 c uint16 d int e *struct { aa uint16 bb int } } data := []s{ { name: "struct 1", d: 100, e: &struct { aa uint16 bb int }{aa: 123, bb: -10}, }, { name: "struct 2", d: 1, e: &struct { aa uint16 bb int }{aa: 15, bb: 10}, }, { name: "struct 3", d: 10, e: &struct { aa uint16 bb int }{aa: 31, bb: -1030}, }, { name: "struct 4", e: &struct { aa uint16 bb int }{}, }, } sort.Slice(data, func(i, j int) bool { di := data[i] dj := data[j] return di.d*di.e.bb > dj.d*dj.e.bb }) println("structs") for _, d := range data { println(d.name) } } func main() { strings() int64s() int32s() int16s() int8s() ints() structs() } ================================================ FILE: testdata/sort.txt ================================================ strings ggg fff cccc bbb aaaa int64s 1923 123 6 3 3 2 1 1 0 -29 -123 uint64s 1923 123 29 6 3 3 2 1 1 0 int32s 1923 123 6 3 3 2 1 1 0 -29 -123 uint32s 1923 123 29 6 3 3 2 1 1 0 int16s 1923 123 6 3 3 2 1 1 0 -29 -123 uint16s 1923 123 29 6 3 3 2 1 1 0 int8s 123 6 3 3 2 1 1 0 -29 -123 uint8s 123 29 6 3 3 2 1 1 0 ints 123 6 3 3 2 1 1 0 -29 -123 uints 123 29 6 3 3 2 1 1 0 structs struct 2 struct 4 struct 1 struct 3 ================================================ FILE: testdata/stdlib.go ================================================ package main import ( "fmt" "math/rand" "os" "strings" "syscall" "time" ) func main() { // package os, fmt fmt.Println("stdin: ", os.Stdin.Name()) fmt.Println("stdout:", os.Stdout.Name()) fmt.Println("stderr:", os.Stderr.Name()) // Package syscall, this mostly checks whether the calls don't trigger an error. syscall.Getuid() syscall.Geteuid() syscall.Getgid() syscall.Getegid() syscall.Getpid() syscall.Getppid() // package math/rand fmt.Println("pseudorandom number:", rand.New(rand.NewSource(1)).Int31()) // package strings fmt.Println("strings.IndexByte:", strings.IndexByte("asdf", 'd')) fmt.Println("strings.Replace:", strings.Replace("An example string", " ", "-", -1)) // package time time.Sleep(time.Millisecond) time.Sleep(-1) // negative sleep should return immediately // Exit the program normally. os.Exit(0) } ================================================ FILE: testdata/stdlib.txt ================================================ stdin: /dev/stdin stdout: /dev/stdout stderr: /dev/stderr pseudorandom number: 1298498081 strings.IndexByte: 2 strings.Replace: An-example-string ================================================ FILE: testdata/string.go ================================================ package main func testRangeString() { for i, c := range "abcü¢€𐍈°x" { println(i, c) } } func testStringToRunes() { var s = "abcü¢€𐍈°x" for i, c := range []rune(s) { println(i, c) } } func testRunesToString(r []rune) { println("string from runes:", string(r)) } type myString string func main() { testRangeString() testStringToRunes() testRunesToString([]rune{97, 98, 99, 252, 162, 8364, 66376, 176, 120}) var _ = len([]byte(myString("foobar"))) // issue 1246 } ================================================ FILE: testdata/string.txt ================================================ 0 97 1 98 2 99 3 252 5 162 7 8364 10 66376 14 176 16 120 0 97 1 98 2 99 3 252 4 162 5 8364 6 66376 7 176 8 120 string from runes: abcü¢€𐍈°x ================================================ FILE: testdata/structs.go ================================================ package main // TODO: add .ll test files to check the output type s0 struct { } type s1 struct { a byte } type s2 struct { a byte b byte } type s3 struct { a byte b byte c byte } // should not be expanded type s4 struct { a byte b byte c byte d byte } // same struct, different type type s4b struct { a byte b byte c byte d byte } type s5 struct { a struct { aa byte ab byte } b byte } type s6 struct { a string b byte } type s7 struct { a interface{} b byte } // should not be expanded type s8 struct { a []byte // 3 elements b byte // 1 element } // linked list (recursive type) type s9 struct { n int next *s9 s []*s9 } func test0(s s0) { println("test0") } func test1(s s1) { println("test1", s.a) } func test2(s s2) { println("test2", s.a, s.b) } func test3(s s3) { println("test3", s.a, s.b, s.c) } func test4(s s4) { println("test4", s.a, s.b, s.c, s.d) test4b(s4b(s)) test4bp((*s4b)(&s)) } func test4b(s s4b) { println("test4b", s.a, s.b, s.c, s.d) } func test4bp(s *s4b) { println("test4bp", s.a, s.b, s.c, s.d) } func test5(s s5) { println("test5", s.a.aa, s.a.ab, s.b) } func test6(s s6) { println("test6", s.a, len(s.a), s.b) } func test7(s s7) { println("test7", s.a, s.b) } func test8(s s8) { println("test8", len(s.a), cap(s.a), s.a[0], s.a[1], s.b) } func test9(s s9) { println("test9", s.next.next) } func main() { test0(s0{}) test1(s1{1}) test2(s2{1, 2}) test3(s3{1, 2, 3}) test4(s4{1, 2, 3, 4}) test5(s5{a: struct { aa byte ab byte }{1, 2}, b: 3}) test6(s6{"foo", 5}) test7(s7{a: nil, b: 8}) test8(s8{[]byte{12, 13, 14}[:2], 6}) test9(s9{next: &s9{}}) } ================================================ FILE: testdata/structs.txt ================================================ test0 test1 1 test2 1 2 test3 1 2 3 test4 1 2 3 4 test4b 1 2 3 4 test4bp 1 2 3 4 test5 1 2 3 test6 foo 3 5 test7 (0:nil) 8 test8 2 3 12 13 6 test9 nil ================================================ FILE: testdata/testing.go ================================================ package main // TODO: also test the verbose version. import ( "errors" "flag" "io" "strings" "testing" ) func TestFoo(t *testing.T) { t.Log("log Foo.a") t.Log("log Foo.b") } func TestBar(t *testing.T) { t.Log("log Bar") t.Log("log g\nh\ni\n") t.Run("Bar1", func(t *testing.T) {}) t.Run("Bar2", func(t *testing.T) { t.Log("log Bar2\na\nb\nc") t.Error("failed") t.Log("after failed") }) t.Run("Bar3", func(t *testing.T) {}) t.Log("log Bar end") } func TestAllLowercase(t *testing.T) { names := []string { "alpha", "BETA", "gamma", "BELTA", } for _, name := range names { t.Run(name, func(t *testing.T) { if 'a' <= name[0] && name[0] <= 'a' { t.Logf("expected lowercase name, and got one, so I'm happy") } else { t.Errorf("expected lowercase name, got %s", name) } }) } } var tests = []testing.InternalTest{ {"TestFoo", TestFoo}, {"TestBar", TestBar}, {"TestAllLowercase", TestAllLowercase}, } var benchmarks = []testing.InternalBenchmark{} var fuzzes = []testing.InternalFuzzTarget{} var examples = []testing.InternalExample{} // A fake regexp matcher. // Inflexible, but saves 50KB of flash and 50KB of RAM per -size full, // and lets tests pass on cortex-m. // Must match the one in src/testing/match.go that is substituted on bare-metal platforms, // or "make test" will fail there. func fakeMatchString(pat, str string) (bool, error) { if pat == ".*" { return true, nil } matched := strings.Contains(str, pat) return matched, nil } func main() { if testing.Testing() { println("not running a test at the moment, testing.Testing() should return false") } testing.Init() flag.Set("test.run", ".*/B") m := testing.MainStart(matchStringOnly(fakeMatchString /*regexp.MatchString*/), tests, benchmarks, fuzzes, examples) exitcode := m.Run() if exitcode != 0 { println("exitcode:", exitcode) } } var errMain = errors.New("testing: unexpected use of func Main") // matchStringOnly is part of upstream, and is used below to provide a dummy deps to pass to MainStart // so it can be run with go (tested with go 1.16) to provide a baseline for the regression test. // See c56cc9b3b57276. Unfortunately, testdeps is internal, so we can't just use &testdeps.TestDeps{}. type matchStringOnly func(pat, str string) (bool, error) func (f matchStringOnly) MatchString(pat, str string) (bool, error) { return f(pat, str) } func (f matchStringOnly) StartCPUProfile(w io.Writer) error { return errMain } func (f matchStringOnly) StopCPUProfile() {} func (f matchStringOnly) WriteProfileTo(string, io.Writer, int) error { return errMain } func (f matchStringOnly) ImportPath() string { return "" } func (f matchStringOnly) StartTestLog(io.Writer) {} func (f matchStringOnly) StopTestLog() error { return errMain } func (f matchStringOnly) SetPanicOnExit0(bool) {} ================================================ FILE: testdata/testing.txt ================================================ --- FAIL: TestBar (0.00s) log Bar log g h i --- FAIL: TestBar/Bar2 (0.00s) log Bar2 a b c failed after failed log Bar end --- FAIL: TestAllLowercase (0.00s) --- FAIL: TestAllLowercase/BETA (0.00s) expected lowercase name, got BETA --- FAIL: TestAllLowercase/BELTA (0.00s) expected lowercase name, got BELTA FAIL exitcode: 1 ================================================ FILE: testdata/timers.go ================================================ package main import "time" var timer = time.NewTimer(time.Millisecond) func main() { // Test ticker. ticker := time.NewTicker(time.Millisecond * 500) println("waiting on ticker") go func() { time.Sleep(time.Millisecond * 150) println(" - after 150ms") time.Sleep(time.Millisecond * 200) println(" - after 200ms") time.Sleep(time.Millisecond * 300) println(" - after 300ms") }() <-ticker.C println("waited on ticker at 500ms") <-ticker.C println("waited on ticker at 1000ms") ticker.Stop() time.Sleep(time.Millisecond * 750) select { case <-ticker.C: println("fail: ticker should have stopped!") default: println("ticker was stopped (didn't send anything after 750ms)") } timer := time.NewTimer(time.Millisecond * 750) println("waiting on timer") go func() { time.Sleep(time.Millisecond * 200) println(" - after 200ms") time.Sleep(time.Millisecond * 400) println(" - after 400ms") }() <-timer.C println("waited on timer at 750ms") time.Sleep(time.Millisecond * 500) reset := timer.Reset(time.Millisecond * 750) println("timer reset:", reset) println("waiting on timer") go func() { time.Sleep(time.Millisecond * 200) println(" - after 200ms") time.Sleep(time.Millisecond * 400) println(" - after 400ms") }() <-timer.C println("waited on timer at 750ms") time.Sleep(time.Millisecond * 500) } ================================================ FILE: testdata/timers.txt ================================================ waiting on ticker - after 150ms - after 200ms waited on ticker at 500ms - after 300ms waited on ticker at 1000ms ticker was stopped (didn't send anything after 750ms) waiting on timer - after 200ms - after 400ms waited on timer at 750ms timer reset: false waiting on timer - after 200ms - after 400ms waited on timer at 750ms ================================================ FILE: testdata/trivialpanic.go ================================================ package main var n *int func main() { println(*n) // this will panic } ================================================ FILE: testdata/wasmexit.go ================================================ package main import ( "os" "time" ) func main() { println("wasmexit test:", os.Args[1]) switch os.Args[1] { case "normal": return case "exit-0": os.Exit(0) case "exit-0-sleep": time.Sleep(time.Millisecond) println("slept") os.Exit(0) case "exit-1": os.Exit(1) case "exit-1-sleep": time.Sleep(time.Millisecond) println("slept") os.Exit(1) } println("unknown wasmexit test") } ================================================ FILE: testdata/wasmexit.js ================================================ require('../targets/wasm_exec.js'); function runTests() { let testCall = (name, params, expected) => { let result = go._inst.exports[name].apply(null, params); if (result !== expected) { console.error(`${name}(...${params}): expected result ${expected}, got ${result}`); } } // These are the same tests as in TestWasmExport. testCall('hello', [], undefined); testCall('add', [3, 5], 8); testCall('add', [7, 9], 16); testCall('add', [6, 1], 7); testCall('reentrantCall', [2, 3], 5); testCall('reentrantCall', [1, 8], 9); } let go = new Go(); go.importObject.tester = { callOutside: (a, b) => { return go._inst.exports.add(a, b); }, callTestMain: () => { runTests(); }, }; WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then(async (result) => { let value = await go.run(result.instance); console.log('exit code:', value); }).catch((err) => { console.error(err); process.exit(1); }); ================================================ FILE: testdata/wasmexport-noscheduler.go ================================================ package main import "time" func init() { println("called init") } //go:wasmimport tester callTestMain func callTestMain() func main() { // Check that exported functions can still be called after calling // time.Sleep. time.Sleep(time.Millisecond) // main.main is not used when using -buildmode=c-shared. callTestMain() } //go:wasmexport hello func hello() { println("hello!") } //go:wasmexport add func add(a, b int32) int32 { println("called add:", a, b) return a + b } //go:wasmimport tester callOutside func callOutside(a, b int32) int32 //go:wasmexport reentrantCall func reentrantCall(a, b int32) int32 { println("reentrantCall:", a, b) result := callOutside(a, b) println("reentrantCall result:", result) return result } //go:wasmexport goroutineExit func goroutineExit() { // Dummy, not a real test (since we have no scheduler). println("goroutineExit: exit") } ================================================ FILE: testdata/wasmexport.go ================================================ package main import ( "runtime" "time" ) func init() { println("called init") go adder() } //go:wasmimport tester callTestMain func callTestMain() func main() { // main.main is not used when using -buildmode=c-shared. callTestMain() } //go:wasmexport hello func hello() { println("hello!") } //go:wasmexport add func add(a, b int32) int32 { println("called add:", a, b) addInputs <- a addInputs <- b return <-addOutput } var addInputs = make(chan int32) var addOutput = make(chan int32) func adder() { for { a := <-addInputs b := <-addInputs time.Sleep(time.Millisecond) addOutput <- a + b } } //go:wasmimport tester callOutside func callOutside(a, b int32) int32 //go:wasmexport reentrantCall func reentrantCall(a, b int32) int32 { println("reentrantCall:", a, b) result := callOutside(a, b) println("reentrantCall result:", result) return result } // Test for bug: https://github.com/tinygo-org/tinygo/issues/4874 // //go:wasmexport goroutineExit func goroutineExit() { go func() { time.Sleep(time.Second * 10) println("goroutineExit: exiting goroutine") }() runtime.Gosched() println("goroutineExit: exit") } ================================================ FILE: testdata/wasmexport.js ================================================ require('../targets/wasm_exec.js'); function runTests() { let testCall = (name, params, expected) => { let result = go._inst.exports[name].apply(null, params); if (result !== expected) { console.error(`${name}(...${params}): expected result ${expected}, got ${result}`); } } // These are the same tests as in TestWasmExport. testCall('hello', [], undefined); testCall('add', [3, 5], 8); testCall('add', [7, 9], 16); testCall('add', [6, 1], 7); testCall('reentrantCall', [2, 3], 5); testCall('reentrantCall', [1, 8], 9); testCall('goroutineExit', [], undefined); } let go = new Go(); go.importObject.tester = { callOutside: (a, b) => { return go._inst.exports.add(a, b); }, callTestMain: () => { runTests(); }, }; WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { let buildMode = process.argv[3]; if (buildMode === 'default') { go.run(result.instance); } else if (buildMode === 'c-shared') { go.run(result.instance); runTests(); } }).catch((err) => { console.error(err); process.exit(1); }); ================================================ FILE: testdata/wasmexport.txt ================================================ called init hello! called add: 3 5 called add: 7 9 called add: 6 1 reentrantCall: 2 3 called add: 2 3 reentrantCall result: 5 reentrantCall: 1 8 called add: 1 8 reentrantCall result: 9 goroutineExit: exit ================================================ FILE: testdata/wasmfunc.go ================================================ package main import "syscall/js" func main() { js.Global().Call("setCallback", js.FuncOf(func(this js.Value, args []js.Value) any { println("inside callback! parameters:") sum := 0 for _, value := range args { n := value.Int() println(" parameter:", n) sum += n } return sum })) js.Global().Call("callCallback") } ================================================ FILE: testdata/wasmfunc.js ================================================ require('../targets/wasm_exec.js'); var callback; global.setCallback = (cb) => { callback = cb; }; global.callCallback = () => { console.log('calling callback!'); let result = callback(1, 2, 3, 4); console.log('result from callback:', result); }; let go = new Go(); WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { go.run(result.instance); }).catch((err) => { console.error(err); process.exit(1); }); ================================================ FILE: testdata/wasmfunc.txt ================================================ calling callback! inside callback! parameters: parameter: 1 parameter: 2 parameter: 3 parameter: 4 result from callback: 10 ================================================ FILE: testdata/zeroalloc.go ================================================ package main func main() { p := []byte{} for len(p) >= 1 { p = p[1:] } } ================================================ FILE: testdata/zeroalloc.txt ================================================ ================================================ FILE: tests/os/smoke/smoke_test.go ================================================ package os_smoke_test // Simple smoke tests for the os package or things that depend on it. // Intended to catch build tag mistakes affecting bare metal targets. import ( "crypto/rand" "crypto/rsa" "fmt" "path/filepath" "testing" ) // Regression test for https://github.com/tinygo-org/tinygo/issues/2563 func TestFilepath(t *testing.T) { if filepath.Base("foo/bar") != "bar" { t.Errorf("filepath.Base is very confused") } } // Regression test for https://github.com/tinygo-org/tinygo/issues/2530 func TestFmt(t *testing.T) { n, err := fmt.Printf("Hello, world!\n") if err != nil { t.Errorf("printf returned error %s", err) } else if n != 14 { t.Errorf("printf returned %d, expected 14", n) } } // Regression test for https://github.com/tinygo-org/tinygo/issues/4921 func TestRand(t *testing.T) { if _, err := rsa.GenerateKey(rand.Reader, 2048); err != nil { t.Error(err) } } ================================================ FILE: tests/runtime/memhash_test.go ================================================ package main import ( "hash/maphash" "strconv" "testing" ) var buf [8192]byte func BenchmarkMaphash(b *testing.B) { var h maphash.Hash benchmarkHash(b, "maphash", h) } func benchmarkHash(b *testing.B, str string, h maphash.Hash) { var sizes = []int{1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 1024, 8192} for _, n := range sizes { b.Run(strconv.Itoa(n), func(b *testing.B) { benchmarkHashn(b, int64(n), h) }) } } var total uint64 func benchmarkHashn(b *testing.B, size int64, h maphash.Hash) { b.SetBytes(size) sum := make([]byte, 4) for i := 0; i < b.N; i++ { h.Reset() h.Write(buf[:size]) sum = h.Sum(sum[:0]) total += uint64(sum[0]) } } ================================================ FILE: tests/runtime_wasi/malloc_test.go ================================================ //go:build tinygo.wasm package runtime_wasi import ( "reflect" "runtime" "strconv" "testing" "unsafe" ) //export malloc func libc_malloc(size uintptr) unsafe.Pointer //export free func libc_free(ptr unsafe.Pointer) //export calloc func libc_calloc(nmemb, size uintptr) unsafe.Pointer //export realloc func libc_realloc(ptr unsafe.Pointer, size uintptr) unsafe.Pointer func getFilledBuffer_malloc() uintptr { ptr := libc_malloc(5) fillPanda(ptr) return uintptr(ptr) } func getFilledBuffer_calloc() uintptr { ptr := libc_calloc(2, 5) fillPanda(ptr) *(*byte)(unsafe.Add(ptr, 5)) = 'b' *(*byte)(unsafe.Add(ptr, 6)) = 'e' *(*byte)(unsafe.Add(ptr, 7)) = 'a' *(*byte)(unsafe.Add(ptr, 8)) = 'r' *(*byte)(unsafe.Add(ptr, 9)) = 's' return uintptr(ptr) } func getFilledBuffer_realloc() uintptr { origPtr := getFilledBuffer_malloc() ptr := libc_realloc(unsafe.Pointer(origPtr), 9) *(*byte)(unsafe.Add(ptr, 5)) = 'b' *(*byte)(unsafe.Add(ptr, 6)) = 'e' *(*byte)(unsafe.Add(ptr, 7)) = 'a' *(*byte)(unsafe.Add(ptr, 8)) = 'r' return uintptr(ptr) } func getFilledBuffer_reallocNil() uintptr { ptr := libc_realloc(nil, 5) fillPanda(ptr) return uintptr(ptr) } func fillPanda(ptr unsafe.Pointer) { *(*byte)(unsafe.Add(ptr, 0)) = 'p' *(*byte)(unsafe.Add(ptr, 1)) = 'a' *(*byte)(unsafe.Add(ptr, 2)) = 'n' *(*byte)(unsafe.Add(ptr, 3)) = 'd' *(*byte)(unsafe.Add(ptr, 4)) = 'a' } func checkFilledBuffer(t *testing.T, ptr uintptr, content string) { t.Helper() buf := *(*string)(unsafe.Pointer(&reflect.StringHeader{ Data: ptr, Len: len(content), })) if buf != content { t.Errorf("expected %q, got %q", content, buf) } } func TestMallocFree(t *testing.T) { tests := []struct { name string getBuffer func() uintptr content string }{ { name: "malloc", getBuffer: getFilledBuffer_malloc, content: "panda", }, { name: "calloc", getBuffer: getFilledBuffer_calloc, content: "pandabears", }, { name: "realloc", getBuffer: getFilledBuffer_realloc, content: "pandabear", }, { name: "realloc nil", getBuffer: getFilledBuffer_reallocNil, content: "panda", }, } for _, tc := range tests { tt := tc t.Run(tt.name, func(t *testing.T) { bufPtr := tt.getBuffer() // Don't use defer to free the buffer as it seems to cause the GC to track it. // Churn GC, the pointer should still be valid until free is called. for i := 0; i < 1000; i++ { a := "hello" + strconv.Itoa(i) // Some conditional logic to ensure optimization doesn't remove the loop completely. if len(a) < 0 { break } runtime.GC() } checkFilledBuffer(t, bufPtr, tt.content) libc_free(unsafe.Pointer(bufPtr)) }) } } ================================================ FILE: tests/testing/builderr/builderr.go ================================================ package builderr import _ "unsafe" //go:linkname x notARealFunction func x() func Thing() { x() } ================================================ FILE: tests/testing/builderr/builderr_test.go ================================================ package builderr_test import ( "testing" "github.com/tinygo-org/tinygo/tests/testing/builderr" ) func TestThing(t *testing.T) { builderr.Thing() } ================================================ FILE: tests/testing/chdir/chdir.go ================================================ package main import ( "log" "os" "path/filepath" "runtime" ) /* Test that this program is 'run' in expected directory. 'run' with expected working-directory in 'EXPECT_DIR' environment variable' with{,out} a -C argument. */ func main() { expectDir := os.Getenv("EXPECT_DIR") cwd, err := os.Getwd() if err != nil { log.Fatal(err) } if runtime.GOOS == "windows" { cwd = filepath.ToSlash(cwd) } if cwd != expectDir { log.Fatalf("expected:\"%v\" != os.Getwd():\"%v\"", expectDir, cwd) } } ================================================ FILE: tests/testing/fail/fail_test.go ================================================ package fail_test import "testing" func TestFail(t *testing.T) { t.Error("fail") } ================================================ FILE: tests/testing/nothing/nothing.go ================================================ package nothing // This package has no tests. ================================================ FILE: tests/testing/pass/pass_test.go ================================================ package pass_test import "testing" func TestPass(t *testing.T) { // This test passes. } ================================================ FILE: tests/testing/recurse/subdir/subdir_test.go ================================================ package subdir import "testing" func TestSubdir(t *testing.T) { } ================================================ FILE: tests/testing/recurse/top_test.go ================================================ package top import "testing" func TestTop(t *testing.T) { } ================================================ FILE: tests/text/template/smoke/empty.go ================================================ package template_smoke func Function() { } ================================================ FILE: tests/text/template/smoke/smoke_test.go ================================================ // Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package template_smoke_test import ( "os" "strings" "testing" "text/template" ) func TestExampleTemplate(tt *testing.T) { // Define a template. const letter = ` Dear {{.Name}}, {{if .Attended}} It was a pleasure to see you at the wedding. {{- else}} It is a shame you couldn't make it to the wedding. {{- end}} {{with .Gift -}} Thank you for the lovely {{.}}. {{end}} Best wishes, Josie ` // Prepare some data to insert into the template. type Recipient struct { Name, Gift string Attended bool } var recipients = []Recipient{ {"Aunt Mildred", "bone china tea set", true}, {"Uncle John", "moleskin pants", false}, {"Cousin Rodney", "", false}, } // Create a new template and parse the letter into it. t := template.Must(template.New("letter").Parse(letter)) // Execute the template for each recipient. for _, r := range recipients { err := t.Execute(os.Stdout, r) if err != nil { tt.Log("executing template:", err) } } // Output: // Dear Aunt Mildred, // // It was a pleasure to see you at the wedding. // Thank you for the lovely bone china tea set. // // Best wishes, // Josie // // Dear Uncle John, // // It is a shame you couldn't make it to the wedding. // Thank you for the lovely moleskin pants. // // Best wishes, // Josie // // Dear Cousin Rodney, // // It is a shame you couldn't make it to the wedding. // // Best wishes, // Josie } // The following example is duplicated in html/template; keep them in sync. func TestExampleTemplate_block(tt *testing.T) { const ( master = `Names:{{block "list" .}}{{"\n"}}{{range .}}{{println "-" .}}{{end}}{{end}}` overlay = `{{define "list"}} {{join . ", "}}{{end}} ` ) var ( funcs = template.FuncMap{"join": strings.Join} guardians = []string{"Gamora", "Groot", "Nebula", "Rocket", "Star-Lord"} ) masterTmpl, err := template.New("master").Funcs(funcs).Parse(master) if err != nil { tt.Fatal(err) } overlayTmpl, err := template.Must(masterTmpl.Clone()).Parse(overlay) if err != nil { tt.Fatal(err) } if err := masterTmpl.Execute(os.Stdout, guardians); err != nil { tt.Fatal(err) } if err := overlayTmpl.Execute(os.Stdout, guardians); err != nil { tt.Fatal(err) } // Output: // Names: // - Gamora // - Groot // - Nebula // - Rocket // - Star-Lord // Names: Gamora, Groot, Nebula, Rocket, Star-Lord } ================================================ FILE: tests/tinygotest/main.go ================================================ package main import ( "fmt" ) func main() { Thing() fmt.Println("normal main") } func Thing() { fmt.Println("THING") } ================================================ FILE: tests/tinygotest/main_test.go ================================================ package main import ( "testing" // This is the tinygo testing package ) func TestFail1(t *testing.T) { t.Error("TestFail1 failed because of stuff and things") } func TestFail2(t *testing.T) { t.Fatalf("TestFail2 failed for %v ", "reasons") } func TestFail3(t *testing.T) { t.Fail() t.Logf("TestFail3 failed for %v ", "reasons") } func TestPass(t *testing.T) { t.Log("TestPass passed") } func BenchmarkNotImplemented(b *testing.B) { } ================================================ FILE: tests/wasm/chan_test.go ================================================ package wasm import ( "testing" "github.com/chromedp/chromedp" ) func TestChan(t *testing.T) { wasmTmpDir, server := startServer(t) err := run(t, "tinygo build -o "+wasmTmpDir+"/chan.wasm -target wasm testdata/chan.go") if err != nil { t.Fatal(err) } ctx := chromectx(t) err = chromedp.Run(ctx, chromedp.Navigate(server.URL+"/run?file=chan.wasm"), waitLog(`1 4 2 3 true`), ) if err != nil { t.Fatal(err) } } ================================================ FILE: tests/wasm/event_test.go ================================================ package wasm import ( "testing" "github.com/chromedp/chromedp" ) func TestEvent(t *testing.T) { wasmTmpDir, server := startServer(t) err := run(t, "tinygo build -o "+wasmTmpDir+"/event.wasm -target wasm testdata/event.go") if err != nil { t.Fatal(err) } ctx := chromectx(t) var log1, log2 string err = chromedp.Run(ctx, chromedp.Navigate(server.URL+"/run?file=event.wasm"), chromedp.WaitVisible("#log"), chromedp.InnerHTML("#log", &log1), waitLog(`1 4`), chromedp.Click("#testbtn"), chromedp.InnerHTML("#log", &log2), waitLog(`1 4 2 3 true`), ) t.Logf("log1: %s", log1) t.Logf("log2: %s", log2) if err != nil { t.Fatal(err) } } ================================================ FILE: tests/wasm/fmt_test.go ================================================ package wasm import ( "testing" "github.com/chromedp/chromedp" ) func TestFmt(t *testing.T) { wasmTmpDir, server := startServer(t) err := run(t, "tinygo build -o "+wasmTmpDir+"/fmt.wasm -target wasm testdata/fmt.go") if err != nil { t.Fatal(err) } ctx := chromectx(t) var log1 string err = chromedp.Run(ctx, chromedp.Navigate(server.URL+"/run?file=fmt.wasm"), chromedp.InnerHTML("#log", &log1), waitLog(`did not panic`), ) t.Logf("log1: %s", log1) if err != nil { t.Fatal(err) } } ================================================ FILE: tests/wasm/fmtprint_test.go ================================================ package wasm import ( "testing" "github.com/chromedp/chromedp" ) func TestFmtprint(t *testing.T) { wasmTmpDir, server := startServer(t) err := run(t, "tinygo build -o "+wasmTmpDir+"/fmtprint.wasm -target wasm testdata/fmtprint.go") if err != nil { t.Fatal(err) } ctx := chromectx(t) var log1 string err = chromedp.Run(ctx, chromedp.Navigate(server.URL+"/run?file=fmtprint.wasm"), chromedp.InnerHTML("#log", &log1), waitLog(`test from fmtprint 1 test from fmtprint 2 test from fmtprint 3 test from fmtprint 4`), ) t.Logf("log1: %s", log1) if err != nil { t.Fatal(err) } } ================================================ FILE: tests/wasm/go.mod ================================================ module github.com/tinygo-org/tinygo/tests/wasm go 1.24 require ( github.com/chromedp/cdproto v0.0.0-20250803210736-d308e07a266d github.com/chromedp/chromedp v0.14.1 ) require ( github.com/chromedp/sysutil v1.1.0 // indirect github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/ws v1.4.0 // indirect golang.org/x/sys v0.34.0 // indirect ) ================================================ FILE: tests/wasm/go.sum ================================================ github.com/chromedp/cdproto v0.0.0-20250803210736-d308e07a266d h1:ZtA1sedVbEW7EW80Iz2GR3Ye6PwbJAJXjv7D74xG6HU= github.com/chromedp/cdproto v0.0.0-20250803210736-d308e07a266d/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= github.com/chromedp/chromedp v0.14.1 h1:0uAbnxewy/Q+Bg7oafVePE/6EXEho9hnaC38f+TTENg= github.com/chromedp/chromedp v0.14.1/go.mod h1:rHzAv60xDE7VNy/MYtTUrYreSc0ujt2O1/C3bzctYBo= github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8= github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 h1:iizUGZ9pEquQS5jTGkh4AqeeHCMbfbjeb0zMt0aEFzs= github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs= github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= ================================================ FILE: tests/wasm/log_test.go ================================================ package wasm import ( "testing" "github.com/chromedp/chromedp" ) func TestLog(t *testing.T) { wasmTmpDir, server := startServer(t) err := run(t, "tinygo build -o "+wasmTmpDir+"/log.wasm -target wasm testdata/log.go") if err != nil { t.Fatal(err) } ctx := chromectx(t) var log1 string err = chromedp.Run(ctx, chromedp.Navigate(server.URL+"/run?file=log.wasm"), chromedp.InnerHTML("#log", &log1), waitLogRe(`^..../../.. ..:..:.. log 1 ..../../.. ..:..:.. log 2 ..../../.. ..:..:.. log 3 println 4 fmt.Println 5 ..../../.. ..:..:.. log 6 in func 1 ..../../.. ..:..:.. in func 2 $`), ) t.Logf("log1: %s", log1) if err != nil { t.Fatal(err) } } ================================================ FILE: tests/wasm/setup_test.go ================================================ package wasm import ( "context" "errors" "fmt" "net/http" "net/http/httptest" "os/exec" "regexp" "strings" "testing" "time" "github.com/chromedp/cdproto/cdp" "github.com/chromedp/cdproto/runtime" "github.com/chromedp/chromedp" ) func run(t *testing.T, cmdline string) error { args := strings.Fields(cmdline) return runargs(t, args...) } func runargs(t *testing.T, args ...string) error { cmd := exec.Command(args[0], args[1:]...) b, err := cmd.CombinedOutput() t.Logf("Command: %s; err=%v; full output:\n%s", strings.Join(args, " "), err, b) if err != nil { return err } return nil } func chromectx(t *testing.T) context.Context { // see https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md opts := append(chromedp.DefaultExecAllocatorOptions[:], chromedp.NoSandbox, chromedp.Flag("disable-web-security", true), chromedp.Flag("safebrowsing-disable-auto-update", true), chromedp.IgnoreCertErrors, chromedp.Flag("disable-sync", true), chromedp.Flag("disable-default-apps", true), chromedp.NoFirstRun, chromedp.Headless, chromedp.WSURLReadTimeout(45*time.Second), ) allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...) t.Cleanup(cancel) // looks for locally installed Chrome ctx, ccancel := chromedp.NewContext(allocCtx, chromedp.WithErrorf(t.Errorf), chromedp.WithDebugf(t.Logf), chromedp.WithLogf(t.Logf)) t.Cleanup(ccancel) // Wait for browser to be ready. err := chromedp.Run(ctx) if err != nil { t.Fatalf("failed to start browser: %s", err.Error()) } ctx, tcancel := context.WithTimeout(ctx, 30*time.Second) t.Cleanup(tcancel) return ctx } func startServer(t *testing.T) (string, *httptest.Server) { tmpDir := t.TempDir() fsh := http.FileServer(http.Dir(tmpDir)) h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/wasm_exec.js" { http.ServeFile(w, r, "../../targets/wasm_exec.js") return } if r.URL.Path == "/run" { fmt.Fprintf(w, ` Test





`, r.FormValue("file"))
			return
		}

		fsh.ServeHTTP(w, r)
	})

	server := httptest.NewServer(h)
	t.Logf("Started server at %q for dir: %s", server.URL, tmpDir)
	t.Cleanup(server.Close)

	return tmpDir, server
}

// waitLog blocks until the log output equals the text provided (ignoring whitespace before and after)
func waitLog(logText string) chromedp.QueryAction {
	return waitInnerTextTrimEq("#log", strings.TrimSpace(logText))
}

// waitLogRe blocks until the log output matches this regular expression
func waitLogRe(restr string) chromedp.QueryAction {
	return waitInnerTextMatch("#log", regexp.MustCompile(restr))
}

// waitInnerTextTrimEq will wait for the innerText of the specified element to match a specific text pattern (ignoring whitespace before and after)
func waitInnerTextTrimEq(sel string, innerText string) chromedp.QueryAction {
	return waitInnerTextMatch(sel, regexp.MustCompile(`^\s*`+regexp.QuoteMeta(innerText)+`\s*$`))
}

// waitInnerTextMatch will wait for the innerText of the specified element to match a specific regexp pattern
func waitInnerTextMatch(sel string, re *regexp.Regexp) chromedp.QueryAction {

	return chromedp.Query(sel, func(s *chromedp.Selector) {

		chromedp.WaitFunc(func(ctx context.Context, cur *cdp.Frame, execCtx runtime.ExecutionContextID, ids ...cdp.NodeID) ([]*cdp.Node, error) {

			nodes := make([]*cdp.Node, len(ids))
			cur.RLock()
			for i, id := range ids {
				nodes[i] = cur.Nodes[id]
				if nodes[i] == nil {
					cur.RUnlock()
					// not yet ready
					return nil, nil
				}
			}
			cur.RUnlock()

			var ret string
			err := chromedp.EvaluateAsDevTools("document.querySelector('"+sel+"').innerText", &ret).Do(ctx)
			if err != nil {
				return nodes, err
			}
			if !re.MatchString(ret) {
				// log.Printf("found text: %s", ret)
				return nodes, errors.New("unexpected value: " + ret)
			}

			// log.Printf("NodeValue: %#v", nodes[0])

			// return nil, errors.New("not ready yet")
			return nodes, nil
		})(s)

	})

}


================================================
FILE: tests/wasm/testdata/chan.go
================================================
package main

func main() {

	ch := make(chan bool, 1)
	println("1")
	go func() {
		println("2")
		ch <- true
		println("3")
	}()
	println("4")
	v := <-ch
	println(v)

}


================================================
FILE: tests/wasm/testdata/event.go
================================================
package main

import "syscall/js"

func main() {

	ch := make(chan bool, 1)

	println("1")

	js.Global().
		Get("document").
		Call("querySelector", "#main").
		Set("innerHTML", ``)

	js.Global().
		Get("document").
		Call("querySelector", "#testbtn").
		Call("addEventListener", "click",
			js.FuncOf(func(this js.Value, args []js.Value) interface{} {
				println("2")
				ch <- true
				println("3")
				return nil
			}))

	println("4")
	v := <-ch
	println(v)

}


================================================
FILE: tests/wasm/testdata/fmt.go
================================================
package main

import "fmt"

func main() {
	var _ fmt.Stringer
	println("did not panic")
}


================================================
FILE: tests/wasm/testdata/fmtprint.go
================================================
package main

import "fmt"

func main() {
	fmt.Println("test from fmtprint 1")
	fmt.Print("test from fmtprint 2\n")
	fmt.Print("test from fmtp")
	fmt.Print("rint 3\n")
	fmt.Printf("test from fmtprint %d\n", 4)
}


================================================
FILE: tests/wasm/testdata/log.go
================================================
package main

import (
	"fmt"
	"log"
	"syscall/js"
)

func main() {

	// try various log and other output directly
	log.Println("log 1")
	log.Print("log 2")
	log.Printf("log %d\n", 3)
	println("println 4")
	fmt.Println("fmt.Println 5")
	log.Printf("log %s", "6")

	// now set up some log output in a button click callback
	js.Global().
		Get("document").
		Call("querySelector", "#main").
		Set("innerHTML", ``)

	js.Global().
		Get("document").
		Call("querySelector", "#testbtn").
		Call("addEventListener", "click",
			js.FuncOf(func(this js.Value, args []js.Value) interface{} {
				println("in func 1")
				log.Printf("in func 2")
				return nil
			}))

	// click the button
	js.Global().
		Get("document").
		Call("querySelector", "#testbtn").
		Call("click")

}


================================================
FILE: tools/gen-critical-atomics/gen-critical-atomics.go
================================================
package main

import (
	"bytes"
	"flag"
	"os"
	"os/exec"
	"strings"
	"text/template"
)

var tmpl = template.Must(template.New("go").Funcs(template.FuncMap{
	"mul": func(x, y int) int {
		return x * y
	},
	"tuple": func(v ...interface{}) []interface{} {
		return v
	},
	"title": strings.Title,
}).Parse(`//go:build baremetal && !tinygo.wasm

// Automatically generated file. DO NOT EDIT.
// This file implements standins for non-native atomics using critical sections.

package runtime

import (
	_ "unsafe"
)

// Documentation:
// * https://llvm.org/docs/Atomics.html
// * https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html
//
// Some atomic operations are emitted inline while others are emitted as libcalls.
// How many are emitted as libcalls depends on the MCU arch and core variant.

{{- define "load"}}{{$bits := mul . 8 -}}
//export __atomic_load_{{.}}
func __atomic_load_{{.}}(ptr *uint{{$bits}}, ordering uintptr) uint{{$bits}} {
	// The LLVM docs for this say that there is a val argument after the pointer.
	// That is a typo, and the GCC docs omit it.
	mask := lockAtomics()
	val := *ptr
	unlockAtomics(mask)
	return val
}
{{end}}
{{- define "store"}}{{$bits := mul . 8 -}}
//export __atomic_store_{{.}}
func __atomic_store_{{.}}(ptr *uint{{$bits}}, val uint{{$bits}}, ordering uintptr) {
	mask := lockAtomics()
	*ptr = val
	unlockAtomics(mask)
}
{{end}}
{{- define "cas"}}{{$bits := mul . 8 -}}
//go:inline
func doAtomicCAS{{$bits}}(ptr *uint{{$bits}}, expected, desired uint{{$bits}}) uint{{$bits}} {
	mask := lockAtomics()
	old := *ptr
	if old == expected {
		*ptr = desired
	}
	unlockAtomics(mask)
	return old
}

//export __sync_val_compare_and_swap_{{.}}
func __sync_val_compare_and_swap_{{.}}(ptr *uint{{$bits}}, expected, desired uint{{$bits}}) uint{{$bits}} {
	return doAtomicCAS{{$bits}}(ptr, expected, desired)
}

//export __atomic_compare_exchange_{{.}}
func __atomic_compare_exchange_{{.}}(ptr, expected *uint{{$bits}}, desired uint{{$bits}}, successOrder, failureOrder uintptr) bool {
	exp := *expected
	old := doAtomicCAS{{$bits}}(ptr, exp, desired)
	return old == exp
}
{{end}}
{{- define "swap"}}{{$bits := mul . 8 -}}
//go:inline
func doAtomicSwap{{$bits}}(ptr *uint{{$bits}}, new uint{{$bits}}) uint{{$bits}} {
	mask := lockAtomics()
	old := *ptr
	*ptr = new
	unlockAtomics(mask)
	return old
}

//export __sync_lock_test_and_set_{{.}}
func __sync_lock_test_and_set_{{.}}(ptr *uint{{$bits}}, new uint{{$bits}}) uint{{$bits}} {
	return doAtomicSwap{{$bits}}(ptr, new)
}

//export __atomic_exchange_{{.}}
func __atomic_exchange_{{.}}(ptr *uint{{$bits}}, new uint{{$bits}}, ordering uintptr) uint{{$bits}} {
	return doAtomicSwap{{$bits}}(ptr, new)
}
{{end}}
{{- define "rmw"}}
	{{- $opname := index . 0}}
	{{- $bytes := index . 1}}{{$bits := mul $bytes 8}}
	{{- $signed := index . 2}}
	{{- $opdef := index . 3}}

{{- $type := printf "int%d" $bits}}
{{- if not $signed}}{{$type = printf "u%s" $type}}{{end -}}
{{- $opfn := printf "doAtomic%s%d" (title $opname) $bits}}

//go:inline
func {{$opfn}}(ptr *{{$type}}, value {{$type}}) (old, new {{$type}}) {
	mask := lockAtomics()
	old = *ptr
	{{$opdef}}
	*ptr = new
	unlockAtomics(mask)
	return old, new
}

//export __atomic_fetch_{{$opname}}_{{$bytes}}
func __atomic_fetch_{{$opname}}_{{$bytes}}(ptr *{{$type}}, value {{$type}}, ordering uintptr) {{$type}} {
	old, _ := {{$opfn}}(ptr, value)
	return old
}

//export __sync_fetch_and_{{$opname}}_{{$bytes}}
func __sync_fetch_and_{{$opname}}_{{$bytes}}(ptr *{{$type}}, value {{$type}}) {{$type}} {
	old, _ := {{$opfn}}(ptr, value)
	return old
}

//export __atomic_{{$opname}}_fetch_{{$bytes}}
func __atomic_{{$opname}}_fetch_{{$bytes}}(ptr *{{$type}}, value {{$type}}, ordering uintptr) {{$type}} {
	_, new := {{$opfn}}(ptr, value)
	return new
}
{{end}}
{{- define "atomics"}}
// {{mul . 8}}-bit atomics.

{{/* These atomics are accessible directly from sync/atomic. */ -}}
{{template "load" .}}
{{template "store" .}}
{{template "cas" .}}
{{template "swap" .}}
{{template "rmw" (tuple "add" . false "new = old + value")}}

{{- end}}
{{template "atomics" 2 -}}
{{template "atomics" 4 -}}
{{template "atomics" 8}}
`))

func main() {
	var out string
	flag.StringVar(&out, "out", "-", "output path")
	flag.Parse()
	f := os.Stdout
	if out != "-" {
		var err error
		f, err = os.Create(out)
		if err != nil {
			panic(err)
		}
		defer f.Close()
	}
	var buf bytes.Buffer
	err := tmpl.Execute(&buf, nil)
	if err != nil {
		panic(err)
	}
	cmd := exec.Command("gofmt")
	cmd.Stdin = &buf
	cmd.Stdout = f
	cmd.Stderr = os.Stderr
	err = cmd.Run()
	if err != nil {
		panic(err)
	}
}


================================================
FILE: tools/gen-device-avr/gen-device-avr.go
================================================
package main

import (
	"bufio"
	"encoding/xml"
	"fmt"
	"html/template"
	"math/bits"
	"os"
	"path/filepath"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"sync"
)

type AVRToolsDeviceFile struct {
	XMLName xml.Name `xml:"avr-tools-device-file"`
	Devices []struct {
		Name          string `xml:"name,attr"`
		Architecture  string `xml:"architecture,attr"`
		Family        string `xml:"family,attr"`
		AddressSpaces []struct {
			Name           string `xml:"name,attr"`
			Size           string `xml:"size,attr"`
			MemorySegments []struct {
				Name  string `xml:"name,attr"`
				Start string `xml:"start,attr"`
				Size  string `xml:"size,attr"`
			} `xml:"memory-segment"`
		} `xml:"address-spaces>address-space"`
		PeripheralInstances []struct {
			Name          string `xml:"name,attr"`
			Caption       string `xml:"caption,attr"`
			RegisterGroup struct {
				NameInModule string `xml:"name-in-module,attr"`
				Offset       string `xml:"offset,attr"`
			} `xml:"register-group"`
		} `xml:"peripherals>module>instance"`
		Interrupts []*XMLInterrupt `xml:"interrupts>interrupt"`
	} `xml:"devices>device"`
	PeripheralRegisterGroups []struct {
		Name      string `xml:"name,attr"`
		Caption   string `xml:"caption,attr"`
		Registers []struct {
			Name      string `xml:"name,attr"`
			Caption   string `xml:"caption,attr"`
			Offset    string `xml:"offset,attr"`
			Size      uint64 `xml:"size,attr"`
			Bitfields []struct {
				Name    string `xml:"name,attr"`
				Caption string `xml:"caption,attr"`
				Mask    string `xml:"mask,attr"`
			} `xml:"bitfield"`
		} `xml:"register"`
	} `xml:"modules>module>register-group"`
}

type XMLInterrupt struct {
	Index    int    `xml:"index,attr"`
	Name     string `xml:"name,attr"`
	Instance string `xml:"module-instance,attr"`
	Caption  string `xml:"caption,attr"`
}

type Device struct {
	metadata   map[string]interface{}
	interrupts []Interrupt
	types      []*PeripheralType
	instances  []*PeripheralInstance
	oldStyle   bool
}

// AddressSpace is the Go version of an XML element like the following:
//
//	
//
// It describes one address space in an AVR microcontroller. One address space
// may have multiple memory segments.
type AddressSpace struct {
	Size     string
	Segments map[string]MemorySegment
}

// MemorySegment is the Go version of an XML element like the following:
//
//	
//
// It describes a single contiguous area of memory in a particular address space
// (see AddressSpace).
type MemorySegment struct {
	start int64
	size  int64
}

type Interrupt struct {
	Index   int
	Name    string
	Caption string
}

// Peripheral instance, for example PORTB
type PeripheralInstance struct {
	Name    string
	Caption string
	Address uint64
	Type    *PeripheralType
}

// Peripheral type, for example PORT (if it's shared between different
// instances, which is the case for new-style ATDF files).
type PeripheralType struct {
	Name      string
	Caption   string
	Registers []*Register
	Instances []*PeripheralInstance
}

// Single register or struct field in a peripheral type.
type Register struct {
	Caption   string
	Name      string
	Type      string
	Offset    uint64 // offset, only for old-style ATDF files
	Bitfields []Bitfield
}

type Bitfield struct {
	Name    string
	Caption string
	Mask    uint
}

func readATDF(path string) (*Device, error) {
	// Read Atmel device descriptor files.
	// See: http://packs.download.atmel.com

	// Open the XML file.
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	decoder := xml.NewDecoder(f)
	xml := &AVRToolsDeviceFile{}
	err = decoder.Decode(xml)
	if err != nil {
		return nil, err
	}

	device := xml.Devices[0]

	memorySizes := make(map[string]*AddressSpace, len(device.AddressSpaces))
	for _, el := range device.AddressSpaces {
		memorySizes[el.Name] = &AddressSpace{
			Size:     el.Size,
			Segments: make(map[string]MemorySegment),
		}
		for _, segmentEl := range el.MemorySegments {
			start, err := strconv.ParseInt(segmentEl.Start, 0, 32)
			if err != nil {
				return nil, err
			}
			size, err := strconv.ParseInt(segmentEl.Size, 0, 32)
			if err != nil {
				return nil, err
			}
			memorySizes[el.Name].Segments[segmentEl.Name] = MemorySegment{
				start: start,
				size:  size,
			}
		}
	}

	// There appear to be two kinds of devices and ATDF files: those before
	// ~2017 and those introduced as part of the tinyAVR (1 and 2 series).
	// The newer devices are structured slightly differently, with peripherals
	// laid out more like Cortex-M chips and one or more instances per chip.
	// Older designs basically just have a bunch of registers with little
	// structure in them.
	// The code generated for these chips is quite different:
	//   * For old-style chips we'll generate a bunch of registers without
	//     peripherals (e.g. PORTB, DDRB, etc).
	//   * For new-style chips we'll generate proper peripheral structs like we
	//     do for Cortex-M chips.
	oldStyle := true
	for _, instanceEl := range device.PeripheralInstances {
		if instanceEl.RegisterGroup.NameInModule == "" {
			continue
		}
		offset, err := strconv.ParseUint(instanceEl.RegisterGroup.Offset, 0, 16)
		if err != nil {
			return nil, fmt.Errorf("failed to parse offset %#v of peripheral %s: %v", instanceEl.RegisterGroup.Offset, instanceEl.Name, err)
		}
		if offset != 0 {
			oldStyle = false
		}
	}

	// Read all peripheral types.
	var types []*PeripheralType
	typeMap := make(map[string]*PeripheralType)
	allRegisters := map[string]*Register{}
	for _, registerGroupEl := range xml.PeripheralRegisterGroups {
		var regs []*Register
		regEls := registerGroupEl.Registers
		if !oldStyle {
			// We only need to sort registers when we're generating peripheral
			// structs.
			sort.SliceStable(regEls, func(i, j int) bool {
				return regEls[i].Offset < regEls[j].Offset
			})
		}
		addReg := func(reg *Register) {
			if oldStyle {
				// Check for duplicate registers (they happen).
				if reg2 := allRegisters[reg.Name]; reg2 != nil {
					return
				}
				allRegisters[reg.Name] = reg
			}
			regs = append(regs, reg)
		}
		offset := uint64(0)
		for _, regEl := range regEls {
			regOffset, err := strconv.ParseUint(regEl.Offset, 0, 64)
			if err != nil {
				return nil, fmt.Errorf("failed to parse offset %#v of register %s: %v", regEl.Offset, regEl.Name, err)
			}
			if !oldStyle {
				// Add some padding to the gap in the struct, if needed.
				if offset < regOffset {
					regs = append(regs, &Register{
						Name: "_",
						Type: fmt.Sprintf("[%d]volatile.Register8", regOffset-offset),
					})
					offset = regOffset
				}

				// Check for overlapping registers.
				if offset > regOffset {
					return nil, fmt.Errorf("register %s in peripheral %s overlaps with another register", regEl.Name, registerGroupEl.Name)
				}
			}

			var bitfields []Bitfield
			for _, bitfieldEl := range regEl.Bitfields {
				maskString := bitfieldEl.Mask
				if len(maskString) == 2 {
					// Two devices (ATtiny102 and ATtiny104) appear to have an
					// error in the bitfields, leaving out the '0x' prefix.
					maskString = "0x" + maskString
				}
				mask, err := strconv.ParseUint(maskString, 0, 32)
				if err != nil {
					return nil, fmt.Errorf("failed to parse mask %#v of bitfield %s: %v", maskString, bitfieldEl.Name, err)
				}
				name := regEl.Name + "_" + bitfieldEl.Name
				if !oldStyle {
					name = registerGroupEl.Name + "_" + name
				}
				bitfields = append(bitfields, Bitfield{
					Name:    name,
					Caption: bitfieldEl.Caption,
					Mask:    uint(mask),
				})
			}

			switch regEl.Size {
			case 1:
				addReg(&Register{
					Name:      regEl.Name,
					Type:      "volatile.Register8",
					Caption:   regEl.Caption,
					Offset:    regOffset,
					Bitfields: bitfields,
				})
			case 2:
				addReg(&Register{
					Name:    regEl.Name + "L",
					Type:    "volatile.Register8",
					Caption: regEl.Caption + " (lower bits)",
					Offset:  regOffset + 0,
				})
				addReg(&Register{
					Name:    regEl.Name + "H",
					Type:    "volatile.Register8",
					Caption: regEl.Caption + " (upper bits)",
					Offset:  regOffset + 1,
				})
			default:
				panic("todo: unknown size")
			}
			offset += regEl.Size
		}
		periphType := &PeripheralType{
			Name:      registerGroupEl.Name,
			Caption:   registerGroupEl.Caption,
			Registers: regs,
		}
		types = append(types, periphType)
		typeMap[periphType.Name] = periphType
	}

	// Read all peripheral instances.
	var instances []*PeripheralInstance
	for _, instanceEl := range device.PeripheralInstances {
		if instanceEl.RegisterGroup.NameInModule == "" {
			continue
		}
		offset, err := strconv.ParseUint(instanceEl.RegisterGroup.Offset, 0, 16)
		if err != nil {
			return nil, fmt.Errorf("failed to parse offset %#v of peripheral %s: %v", instanceEl.RegisterGroup.Offset, instanceEl.Name, err)
		}
		periphType := typeMap[instanceEl.RegisterGroup.NameInModule]
		instance := &PeripheralInstance{
			Name:    instanceEl.Name,
			Caption: instanceEl.Caption,
			Address: offset,
			Type:    periphType,
		}
		instances = append(instances, instance)
		periphType.Instances = append(periphType.Instances, instance)
	}

	ramStart := int64(0)
	ramSize := int64(0) // for devices with no RAM
	for _, ramSegmentName := range []string{"IRAM", "INTERNAL_SRAM", "SRAM"} {
		if segment, ok := memorySizes["data"].Segments[ramSegmentName]; ok {
			ramStart = segment.start
			ramSize = segment.size
		}
	}

	// Flash that is mapped into the data address space (attiny10, attiny1616,
	// etc).
	mappedFlashStart := int64(0)
	for _, name := range []string{"MAPPED_PROGMEM", "MAPPED_FLASH"} {
		if segment, ok := memorySizes["data"].Segments[name]; ok {
			mappedFlashStart = segment.start
		}
	}

	flashSize, err := strconv.ParseInt(memorySizes["prog"].Size, 0, 32)
	if err != nil {
		return nil, err
	}

	// Process the interrupts to clean up inconsistencies between ATDF files.
	var interrupts []Interrupt
	hasResetInterrupt := false
	for _, intr := range device.Interrupts {
		name := intr.Name
		if intr.Instance != "" {
			// ATDF files for newer chips also have an instance name, which must
			// be specified to make the interrupt name unique.
			name = intr.Instance + "_" + name
		}
		if name == "RESET" {
			hasResetInterrupt = true
		}
		interrupts = append(interrupts, Interrupt{
			Index:   intr.Index,
			Name:    name,
			Caption: intr.Caption,
		})
	}
	if !hasResetInterrupt {
		interrupts = append(interrupts, Interrupt{
			Index: 0,
			Name:  "RESET",
		})
	}
	sort.SliceStable(interrupts, func(i, j int) bool {
		return interrupts[i].Index < interrupts[j].Index
	})

	return &Device{
		metadata: map[string]interface{}{
			"file":             filepath.Base(path),
			"descriptorSource": "http://packs.download.atmel.com/",
			"name":             device.Name,
			"nameLower":        strings.ToLower(device.Name),
			"description":      fmt.Sprintf("Device information for the %s.", device.Name),
			"arch":             device.Architecture,
			"family":           device.Family,
			"flashSize":        int(flashSize),
			"ramStart":         ramStart,
			"ramSize":          ramSize,
			"mappedFlashStart": mappedFlashStart,
			"numInterrupts":    len(device.Interrupts),
		},
		interrupts: interrupts,
		types:      types,
		instances:  instances,
		oldStyle:   oldStyle,
	}, nil
}

func writeGo(outdir string, device *Device) error {
	// The Go module for this device.
	outf, err := os.Create(outdir + "/" + device.metadata["nameLower"].(string) + ".go")
	if err != nil {
		return err
	}
	defer outf.Close()
	w := bufio.NewWriter(outf)

	maxInterruptNum := 0
	for _, intr := range device.interrupts {
		if intr.Index > maxInterruptNum {
			maxInterruptNum = intr.Index
		}
	}

	t := template.Must(template.New("go").Parse(`// Automatically generated file. DO NOT EDIT.
// Generated by gen-device-avr.go from {{.metadata.file}}, see {{.metadata.descriptorSource}}

//go:build {{.pkgName}} && {{.metadata.nameLower}}

// {{.metadata.description}}
package {{.pkgName}}

import (
	"runtime/volatile"
	"unsafe"
)

// Some information about this device.
const (
	DEVICE = "{{.metadata.name}}"
	ARCH   = "{{.metadata.arch}}"
	FAMILY = "{{.metadata.family}}"
)

// Interrupts
const ({{range .interrupts}}
	IRQ_{{.Name}} = {{.Index}} // {{.Caption}}{{end}}
	IRQ_max = {{.interruptMax}} // Highest interrupt number on this device.
)

// Pseudo function call that is replaced by the compiler with the actual
// functions registered through interrupt.New.
//go:linkname callHandlers runtime/interrupt.callHandlers
func callHandlers(num int)

{{- range .interrupts}}
//export __vector_{{.Name}}
//go:interrupt
func interrupt{{.Name}}() {
	callHandlers(IRQ_{{.Name}})
}
{{- end}}

{{if .oldStyle -}}
// Peripherals.
var (
{{- range .instances}}
	// {{.Caption}}
	{{range .Type.Registers -}}
	{{if ne .Name "_" -}}
	{{.Name}} = (*{{.Type}})(unsafe.Pointer(uintptr(0x{{printf "%x" .Offset}})))
	{{end -}}
	{{end -}}
{{end}})
{{else}}
// Peripherals instances.
var (
{{- range .instances -}}
	{{.Name}} = (*{{.Type.Name}}_Type)(unsafe.Pointer(uintptr(0x{{printf "%x" .Address}})))
	{{- if .Caption}}// {{.Caption}}{{end}}
{{end -}}
)

// Peripheral type definitions.

{{range .types}}
type {{.Name}}_Type struct {
{{range .Registers -}}
	{{.Name}} {{.Type}} {{if .Caption}} // {{.Caption}} {{end}}
{{end -}}
}
{{end}}
{{end}}
`))
	err = t.Execute(w, map[string]interface{}{
		"metadata":     device.metadata,
		"pkgName":      filepath.Base(strings.TrimRight(outdir, "/")),
		"interrupts":   device.interrupts,
		"interruptMax": maxInterruptNum,
		"instances":    device.instances,
		"types":        device.types,
		"oldStyle":     device.oldStyle,
	})
	if err != nil {
		return err
	}

	// Write bitfields.
	for _, peripheral := range device.types {
		// Only write bitfields when there are any.
		numFields := 0
		for _, r := range peripheral.Registers {
			numFields += len(r.Bitfields)
		}
		if numFields == 0 {
			continue
		}

		fmt.Fprintf(w, "\n// Bitfields for %s: %s\nconst(", peripheral.Name, peripheral.Caption)
		for _, register := range peripheral.Registers {
			if len(register.Bitfields) == 0 {
				continue
			}
			fmt.Fprintf(w, "\n\t// %s", register.Name)
			if register.Caption != "" {
				fmt.Fprintf(w, ": %s", register.Caption)
			}
			fmt.Fprintf(w, "\n")
			allBits := map[string]interface{}{}
			for _, bitfield := range register.Bitfields {
				if bits.OnesCount(bitfield.Mask) == 1 {
					fmt.Fprintf(w, "\t%s = 0x%x", bitfield.Name, bitfield.Mask)
					if len(bitfield.Caption) != 0 {
						fmt.Fprintf(w, " // %s", bitfield.Caption)
					}
					fmt.Fprintf(w, "\n")
					fmt.Fprintf(w, "\t%s_Msk = 0x%x", bitfield.Name, bitfield.Mask)
					if len(bitfield.Caption) != 0 {
						fmt.Fprintf(w, " // %s", bitfield.Caption)
					}
					fmt.Fprintf(w, "\n")
					allBits[bitfield.Name] = nil
				} else {
					n := 0
					for i := uint(0); i < 8; i++ {
						if (bitfield.Mask>>i)&1 == 0 {
							continue
						}
						name := fmt.Sprintf("%s%d", bitfield.Name, n)
						if _, ok := allBits[name]; !ok {
							fmt.Fprintf(w, "\t%s = 0x%x", name, 1< num {
			fmt.Fprintf(out, "    %s __vector_default\n", jmp)
			num++
		}
		num++
		fmt.Fprintf(out, "    %s __vector_%s\n", jmp, intr.Name)
	}

	fmt.Fprint(out, `
    ; Define default implementations for interrupts, redirecting to
    ; __vector_default when not implemented.
`)
	for _, intr := range device.interrupts {
		fmt.Fprintf(out, "    IRQ __vector_%s\n", intr.Name)
	}
	return nil
}

func writeLD(outdir string, device *Device) error {
	// Variables for the linker script.
	out, err := os.Create(outdir + "/" + device.metadata["nameLower"].(string) + ".ld")
	if err != nil {
		return err
	}
	defer out.Close()
	t := template.Must(template.New("ld").Parse(`/* Automatically generated file. DO NOT EDIT. */
/* Generated by gen-device-avr.go from {{.file}}, see {{.descriptorSource}} */

__flash_size = 0x{{printf "%x" .flashSize}};
{{if .mappedFlashStart -}}
__mapped_flash_start = 0x{{printf "%x" .mappedFlashStart}};
{{end -}}
__ram_start = 0x{{printf "%x" .ramStart}};
__ram_size   = 0x{{printf "%x" .ramSize}};
__num_isrs   = {{.numInterrupts}};
`))
	return t.Execute(out, device.metadata)
}

func processFile(filepath, outdir string) error {
	device, err := readATDF(filepath)
	if err != nil {
		return err
	}
	err = writeGo(outdir, device)
	if err != nil {
		return err
	}
	err = writeAsm(outdir, device)
	if err != nil {
		return err
	}
	return writeLD(outdir, device)
}

func generate(indir, outdir string) error {
	// Read list of ATDF files to process.
	matches, err := filepath.Glob(indir + "/*.atdf")
	if err != nil {
		return err
	}

	// Start worker goroutines.
	var wg sync.WaitGroup
	workChan := make(chan string)
	errChan := make(chan error, 1)
	for i := 0; i < runtime.NumCPU(); i++ {
		go func() {
			for filepath := range workChan {
				err := processFile(filepath, outdir)
				wg.Done()
				if err != nil {
					// Store error to errChan if no error was stored before.
					select {
					case errChan <- err:
					default:
					}
				}
			}
		}()
	}

	// Submit all jobs to the goroutines.
	wg.Add(len(matches))
	for _, filepath := range matches {
		fmt.Println(filepath)
		workChan <- filepath
	}
	close(workChan)

	// Wait until all workers have finished.
	wg.Wait()

	// Check for an error.
	select {
	case err := <-errChan:
		return err
	default:
		return nil
	}
}

func main() {
	indir := os.Args[1]  // directory with register descriptor files (*.atdf)
	outdir := os.Args[2] // output directory
	err := generate(indir, outdir)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}


================================================
FILE: tools/gen-device-svd/gen-device-svd.go
================================================
package main

import (
	"bufio"
	"encoding/xml"
	"errors"
	"flag"
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"text/template"
	"unicode"
)

var validName = regexp.MustCompile("^[a-zA-Z0-9_]+$")
var enumBitSpecifier = regexp.MustCompile("^#x*[01]+[01x]*$")

type SVDFile struct {
	XMLName     xml.Name `xml:"device"`
	Name        string   `xml:"name"`
	Description string   `xml:"description"`
	LicenseText string   `xml:"licenseText"`
	CPU         *struct {
		Name         string `xml:"name"`
		FPUPresent   bool   `xml:"fpuPresent"`
		NVICPrioBits int    `xml:"nvicPrioBits"`
	} `xml:"cpu"`
	Peripherals []SVDPeripheral `xml:"peripherals>peripheral"`
}

type SVDPeripheral struct {
	Name        string `xml:"name"`
	Description string `xml:"description"`
	BaseAddress string `xml:"baseAddress"`
	GroupName   string `xml:"groupName"`
	DerivedFrom string `xml:"derivedFrom,attr"`
	Interrupts  []struct {
		Name  string `xml:"name"`
		Index int    `xml:"value"`
	} `xml:"interrupt"`
	Registers []*SVDRegister `xml:"registers>register"`
	Clusters  []*SVDCluster  `xml:"registers>cluster"`
}

type SVDRegister struct {
	Name          string      `xml:"name"`
	Description   string      `xml:"description"`
	Dim           *string     `xml:"dim"`
	DimIndex      *string     `xml:"dimIndex"`
	DimIncrement  string      `xml:"dimIncrement"`
	Size          *string     `xml:"size"`
	Fields        []*SVDField `xml:"fields>field"`
	Offset        *string     `xml:"offset"`
	AddressOffset *string     `xml:"addressOffset"`
}

type SVDField struct {
	Name             string  `xml:"name"`
	Description      string  `xml:"description"`
	Lsb              *uint32 `xml:"lsb"`
	Msb              *uint32 `xml:"msb"`
	BitOffset        *uint32 `xml:"bitOffset"`
	BitWidth         *uint32 `xml:"bitWidth"`
	BitRange         *string `xml:"bitRange"`
	EnumeratedValues struct {
		DerivedFrom     string `xml:"derivedFrom,attr"`
		Name            string `xml:"name"`
		EnumeratedValue []struct {
			Name        string `xml:"name"`
			Description string `xml:"description"`
			Value       string `xml:"value"`
		} `xml:"enumeratedValue"`
	} `xml:"enumeratedValues"`
}

type SVDCluster struct {
	Dim           *int           `xml:"dim"`
	DimIncrement  string         `xml:"dimIncrement"`
	DimIndex      *string        `xml:"dimIndex"`
	Name          string         `xml:"name"`
	Description   string         `xml:"description"`
	Registers     []*SVDRegister `xml:"register"`
	Clusters      []*SVDCluster  `xml:"cluster"`
	AddressOffset string         `xml:"addressOffset"`
}

type Device struct {
	Metadata       *Metadata
	Interrupts     []*Interrupt
	Peripherals    []*Peripheral
	PeripheralDict map[string]*Peripheral
}

type Metadata struct {
	File             string
	DescriptorSource string
	Name             string
	NameLower        string
	Description      string
	LicenseBlock     string

	HasCPUInfo   bool // set if the following fields are populated
	CPUName      string
	FPUPresent   bool
	NVICPrioBits int
}

type Interrupt struct {
	Name            string
	HandlerName     string
	PeripheralIndex int
	Value           int // interrupt number
	Description     string
}

type Peripheral struct {
	Name        string
	GroupName   string
	BaseAddress uint64
	Description string
	ClusterName string
	Registers   []*PeripheralField
	Subtypes    []*Peripheral
}

// A PeripheralField is a single field in a peripheral type. It may be a full
// peripheral or a cluster within a peripheral.
type PeripheralField struct {
	Name         string
	Address      uint64
	Description  string
	Registers    []*PeripheralField // contains fields if this is a cluster
	Array        int
	ElementSize  int
	Constants    []Constant
	ShortName    string     // name stripped of "spaced array" suffix
	Bitfields    []Bitfield // set of bit-fields provided by this
	HasBitfields bool       // set true when Bitfields was set for a first PeripheralField of "spaced array".
}

type Constant struct {
	Name        string
	Description string
	Value       uint64
}

type Bitfield struct {
	Name   string
	Offset uint32
	Mask   uint32
}

func formatText(text string) string {
	text = regexp.MustCompile(`[ \t\n]+`).ReplaceAllString(text, " ") // Collapse whitespace (like in HTML)
	text = strings.ReplaceAll(text, "\\n ", "\n")
	text = strings.TrimSpace(text)
	return text
}

func isMultiline(s string) bool {
	return strings.Index(s, "\n") >= 0
}

func splitLine(s string) []string {
	return strings.Split(s, "\n")
}

// Replace characters that are not allowed in a symbol name with a '_'. This is
// useful to be able to process SVD files with errors.
func cleanName(text string) string {
	if !validName.MatchString(text) {
		result := make([]rune, 0, len(text))
		for _, c := range text {
			if validName.MatchString(string(c)) {
				result = append(result, c)
			} else {
				result = append(result, '_')
			}
		}
		text = string(result)
	}
	if len(text) != 0 && (text[0] >= '0' && text[0] <= '9') {
		// Identifiers may not start with a number.
		// Add an underscore instead.
		text = "_" + text
	}
	return text
}

func processSubCluster(p *Peripheral, cluster *SVDCluster, clusterOffset uint64, clusterName string, peripheralDict map[string]*Peripheral) []*Peripheral {
	var peripheralsList []*Peripheral
	clusterPrefix := clusterName + "_"
	cpRegisters := []*PeripheralField{}

	for _, regEl := range cluster.Registers {
		cpRegisters = append(cpRegisters, parseRegister(p.GroupName, regEl, p.BaseAddress+clusterOffset, clusterPrefix)...)
	}
	// handle sub-clusters of registers
	for _, subClusterEl := range cluster.Clusters {
		subclusterName := strings.ReplaceAll(subClusterEl.Name, "[%s]", "")
		subclusterPrefix := subclusterName + "_"
		subclusterOffset, err := strconv.ParseUint(subClusterEl.AddressOffset, 0, 32)
		if err != nil {
			panic(err)
		}
		subdim := *subClusterEl.Dim
		subdimIncrement, err := strconv.ParseInt(subClusterEl.DimIncrement, 0, 32)
		if err != nil {
			panic(err)
		}

		if subdim > 1 {
			subcpRegisters := []*PeripheralField{}
			for _, regEl := range subClusterEl.Registers {
				subcpRegisters = append(subcpRegisters, parseRegister(p.GroupName, regEl, p.BaseAddress+clusterOffset+subclusterOffset, subclusterPrefix)...)
			}

			cpRegisters = append(cpRegisters, &PeripheralField{
				Name:        subclusterName,
				Address:     p.BaseAddress + clusterOffset + subclusterOffset,
				Description: subClusterEl.Description,
				Registers:   subcpRegisters,
				Array:       subdim,
				ElementSize: int(subdimIncrement),
				ShortName:   clusterPrefix + subclusterName,
			})
		} else {
			for _, regEl := range subClusterEl.Registers {
				cpRegisters = append(cpRegisters, parseRegister(regEl.Name, regEl, p.BaseAddress+clusterOffset+subclusterOffset, subclusterPrefix)...)
			}
		}
	}

	sort.SliceStable(cpRegisters, func(i, j int) bool {
		return cpRegisters[i].Address < cpRegisters[j].Address
	})
	clusterPeripheral := &Peripheral{
		Name:        p.Name + "_" + clusterName,
		GroupName:   p.GroupName + "_" + clusterName,
		Description: p.Description + " - " + clusterName,
		ClusterName: clusterName,
		BaseAddress: p.BaseAddress + clusterOffset,
		Registers:   cpRegisters,
	}
	peripheralsList = append(peripheralsList, clusterPeripheral)
	peripheralDict[clusterPeripheral.Name] = clusterPeripheral
	p.Subtypes = append(p.Subtypes, clusterPeripheral)

	return peripheralsList
}

func processCluster(p *Peripheral, clusters []*SVDCluster, peripheralDict map[string]*Peripheral) []*Peripheral {
	var peripheralsList []*Peripheral
	for _, cluster := range clusters {
		clusterName := strings.ReplaceAll(cluster.Name, "[%s]", "")
		if cluster.DimIndex != nil {
			clusterName = strings.ReplaceAll(clusterName, "%s", "")
		}
		clusterPrefix := clusterName + "_"
		clusterOffset, err := strconv.ParseUint(cluster.AddressOffset, 0, 32)
		if err != nil {
			panic(err)
		}
		var dim, dimIncrement int
		if cluster.Dim == nil {
			// Nordic SVD have sub-clusters with another sub-clusters.
			if clusterOffset == 0 || len(cluster.Clusters) > 0 {
				peripheralsList = append(peripheralsList, processSubCluster(p, cluster, clusterOffset, clusterName, peripheralDict)...)
				continue
			}
			dim = -1
			dimIncrement = -1
		} else {
			dim = *cluster.Dim
			if dim == 1 {
				dimIncrement = -1
			} else {
				inc, err := strconv.ParseUint(cluster.DimIncrement, 0, 32)
				if err != nil {
					panic(err)
				}
				dimIncrement = int(inc)
			}
		}
		clusterRegisters := []*PeripheralField{}
		for _, regEl := range cluster.Registers {
			regName := p.GroupName
			if regName == "" {
				regName = p.Name
			}
			clusterRegisters = append(clusterRegisters, parseRegister(regName, regEl, p.BaseAddress+clusterOffset, clusterPrefix)...)
		}
		sort.SliceStable(clusterRegisters, func(i, j int) bool {
			return clusterRegisters[i].Address < clusterRegisters[j].Address
		})
		if dimIncrement == -1 && len(clusterRegisters) > 0 {
			lastReg := clusterRegisters[len(clusterRegisters)-1]
			lastAddress := lastReg.Address
			if lastReg.Array != -1 {
				lastAddress = lastReg.Address + uint64(lastReg.Array*lastReg.ElementSize)
			}
			firstAddress := clusterRegisters[0].Address
			dimIncrement = int(lastAddress - firstAddress)
		}

		if !unicode.IsUpper(rune(clusterName[0])) && !unicode.IsDigit(rune(clusterName[0])) {
			clusterName = strings.ToUpper(clusterName)
		}

		p.Registers = append(p.Registers, &PeripheralField{
			Name:        clusterName,
			Address:     p.BaseAddress + clusterOffset,
			Description: cluster.Description,
			Registers:   clusterRegisters,
			Array:       dim,
			ElementSize: dimIncrement,
			ShortName:   clusterName,
		})
	}
	sort.SliceStable(p.Registers, func(i, j int) bool {
		return p.Registers[i].Address < p.Registers[j].Address
	})
	return peripheralsList
}

// Read ARM SVD files.
func readSVD(path, sourceURL string) (*Device, error) {
	// Open the XML file.
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	decoder := xml.NewDecoder(f)
	device := &SVDFile{}
	err = decoder.Decode(device)
	if err != nil {
		return nil, err
	}

	peripheralDict := map[string]*Peripheral{}
	groups := map[string]*Peripheral{}

	interrupts := make(map[string]*Interrupt)
	var peripheralsList []*Peripheral

	// Some SVD files have peripheral elements derived from a peripheral that
	// comes later in the file. To make sure this works, sort the peripherals if
	// needed.
	orderedPeripherals := orderPeripherals(device.Peripherals)

	for _, periphEl := range orderedPeripherals {
		description := formatText(periphEl.Description)
		baseAddress, err := strconv.ParseUint(periphEl.BaseAddress, 0, 64)
		if err != nil {
			return nil, fmt.Errorf("invalid base address: %w", err)
		}
		// Some group names (for example the STM32H7A3x) have an invalid
		// group name. Replace invalid characters with "_".
		groupName := cleanName(periphEl.GroupName)
		if groupName == "" {
			groupName = cleanName(periphEl.Name)
		}

		for _, interrupt := range periphEl.Interrupts {
			addInterrupt(interrupts, interrupt.Name, interrupt.Name, interrupt.Index, description)
			// As a convenience, also use the peripheral name as the interrupt
			// name. Only do that for the nrf for now, as the stm32 .svd files
			// don't always put interrupts in the correct peripheral...
			if len(periphEl.Interrupts) == 1 && strings.HasPrefix(device.Name, "nrf") {
				addInterrupt(interrupts, periphEl.Name, interrupt.Name, interrupt.Index, description)
			}
		}

		if _, ok := groups[groupName]; ok || periphEl.DerivedFrom != "" {
			var derivedFrom *Peripheral
			if periphEl.DerivedFrom != "" {
				derivedFrom = peripheralDict[periphEl.DerivedFrom]
			} else {
				derivedFrom = groups[groupName]
			}
			p := &Peripheral{
				Name:        periphEl.Name,
				GroupName:   derivedFrom.GroupName,
				Description: description,
				BaseAddress: baseAddress,
			}
			if p.Description == "" {
				p.Description = derivedFrom.Description
			}
			peripheralsList = append(peripheralsList, p)
			peripheralDict[p.Name] = p
			for _, subtype := range derivedFrom.Subtypes {
				peripheralsList = append(peripheralsList, &Peripheral{
					Name:        periphEl.Name + "_" + subtype.ClusterName,
					GroupName:   subtype.GroupName,
					Description: subtype.Description,
					BaseAddress: baseAddress,
				})
			}
			continue
		}

		p := &Peripheral{
			Name:        periphEl.Name,
			GroupName:   groupName,
			Description: description,
			BaseAddress: baseAddress,
			Registers:   []*PeripheralField{},
		}
		if p.GroupName == "" {
			p.GroupName = periphEl.Name
		}
		peripheralsList = append(peripheralsList, p)
		peripheralDict[periphEl.Name] = p

		if _, ok := groups[groupName]; !ok && groupName != "" {
			groups[groupName] = p
		}

		for _, register := range periphEl.Registers {
			regName := groupName // preferably use the group name
			if regName == "" {
				regName = periphEl.Name // fall back to peripheral name
			}
			p.Registers = append(p.Registers, parseRegister(regName, register, baseAddress, "")...)
		}
		peripheralsList = append(peripheralsList, processCluster(p, periphEl.Clusters, peripheralDict)...)
	}

	// Make a sorted list of interrupts.
	interruptList := make([]*Interrupt, 0, len(interrupts))
	for _, intr := range interrupts {
		interruptList = append(interruptList, intr)
	}
	sort.SliceStable(interruptList, func(i, j int) bool {
		if interruptList[i].Value != interruptList[j].Value {
			return interruptList[i].Value < interruptList[j].Value
		}
		return interruptList[i].PeripheralIndex < interruptList[j].PeripheralIndex
	})

	// Properly format the description, with comments.
	description := ""
	if text := device.Description; text != "" {
		description = "// " + strings.ReplaceAll(text, "\n", "\n// ")
		description = regexp.MustCompile(`\s+\n`).ReplaceAllString(description, "\n")
	}

	// Properly format the license block, with comments.
	licenseBlock := ""
	if text := formatText(device.LicenseText); text != "" {
		licenseBlock = "//     " + strings.ReplaceAll(text, "\n", "\n//     ")
		licenseBlock = regexp.MustCompile(`\s+\n`).ReplaceAllString(licenseBlock, "\n")
	}

	// Remove "-" characters from the device name because such characters cannot
	// be used in build tags. Necessary for the ESP32-C3 for example.
	nameLower := strings.ReplaceAll(strings.ToLower(device.Name), "-", "")
	metadata := &Metadata{
		File:             filepath.Base(path),
		DescriptorSource: sourceURL,
		Name:             device.Name,
		NameLower:        nameLower,
		Description:      description,
		LicenseBlock:     licenseBlock,
	}
	if device.CPU != nil {
		metadata.HasCPUInfo = true
		metadata.CPUName = device.CPU.Name
		metadata.FPUPresent = device.CPU.FPUPresent
		metadata.NVICPrioBits = device.CPU.NVICPrioBits
	}
	return &Device{
		Metadata:       metadata,
		Interrupts:     interruptList,
		Peripherals:    peripheralsList,
		PeripheralDict: peripheralDict,
	}, nil
}

// orderPeripherals sorts the peripherals so that derived peripherals come after
// base peripherals. This is necessary for some SVD files.
func orderPeripherals(input []SVDPeripheral) []*SVDPeripheral {
	var sortedPeripherals []*SVDPeripheral
	var missingBasePeripherals []*SVDPeripheral
	knownBasePeripherals := map[string]struct{}{}
	for i := range input {
		p := &input[i]
		groupName := p.GroupName
		if groupName == "" {
			groupName = p.Name
		}
		knownBasePeripherals[groupName] = struct{}{}
		if p.DerivedFrom != "" {
			if _, ok := knownBasePeripherals[p.DerivedFrom]; !ok {
				missingBasePeripherals = append(missingBasePeripherals, p)
				continue
			}
		}
		sortedPeripherals = append(sortedPeripherals, p)
	}

	// Let's hope all base peripherals are now included.
	sortedPeripherals = append(sortedPeripherals, missingBasePeripherals...)

	return sortedPeripherals
}

func addInterrupt(interrupts map[string]*Interrupt, name, interruptName string, index int, description string) {
	if _, ok := interrupts[name]; ok {
		if interrupts[name].Value != index {
			// Note: some SVD files like the one for STM32H7x7 contain mistakes.
			// Instead of throwing an error, simply log it.
			fmt.Fprintf(os.Stderr, "interrupt with the same name has different indexes: %s (%d vs %d)\n",
				name, interrupts[name].Value, index)
		}
		parts := strings.Split(interrupts[name].Description, " // ")
		hasDescription := false
		for _, part := range parts {
			if part == description {
				hasDescription = true
			}
		}
		if !hasDescription {
			interrupts[name].Description += " // " + description
		}
	} else {
		interrupts[name] = &Interrupt{
			Name:            name,
			HandlerName:     interruptName + "_IRQHandler",
			PeripheralIndex: len(interrupts),
			Value:           index,
			Description:     description,
		}
	}
}

func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPrefix string) ([]Constant, []Bitfield) {
	var fields []Constant
	var bitfields []Bitfield
	enumSeen := map[string]int64{}
	for _, fieldEl := range fieldEls {
		// Some bitfields (like the STM32H7x7) contain invalid bitfield
		// names like "CNT[31]". Replace invalid characters with "_" when
		// needed.
		fieldName := cleanName(fieldEl.Name)
		if !unicode.IsUpper(rune(fieldName[0])) && !unicode.IsDigit(rune(fieldName[0])) {
			fieldName = strings.ToUpper(fieldName)
		}

		// Find the lsb/msb that is encoded in various ways.
		// Standards are great, that's why there are so many to choose from!
		var lsb, msb uint32
		if fieldEl.Lsb != nil && fieldEl.Msb != nil {
			// try to use lsb/msb tags
			lsb = *fieldEl.Lsb
			msb = *fieldEl.Msb
		} else if fieldEl.BitOffset != nil && fieldEl.BitWidth != nil {
			// try to use bitOffset/bitWidth tags
			lsb = *fieldEl.BitOffset
			msb = *fieldEl.BitWidth + lsb - 1
		} else if fieldEl.BitRange != nil {
			// try use bitRange
			// example string: "[20:16]"
			parts := strings.Split(strings.Trim(*fieldEl.BitRange, "[]"), ":")
			l, err := strconv.ParseUint(parts[1], 0, 32)
			if err != nil {
				panic(err)
			}
			lsb = uint32(l)
			m, err := strconv.ParseUint(parts[0], 0, 32)
			if err != nil {
				panic(err)
			}
			msb = uint32(m)
		} else {
			// this is an error. what to do?
			fmt.Fprintln(os.Stderr, "unable to find lsb/msb in field:", fieldName)
			continue
		}

		// The enumerated values can be the same as another field, so to avoid
		// duplication SVD files can simply refer to another set of enumerated
		// values in the same register.
		// See: https://www.keil.com/pack/doc/CMSIS/SVD/html/elem_registers.html#elem_enumeratedValues
		enumeratedValues := fieldEl.EnumeratedValues
		if enumeratedValues.DerivedFrom != "" {
			parts := strings.Split(enumeratedValues.DerivedFrom, ".")
			if len(parts) == 1 {
				found := false
				for _, otherFieldEl := range fieldEls {
					if otherFieldEl.EnumeratedValues.Name == parts[0] {
						found = true
						enumeratedValues = otherFieldEl.EnumeratedValues
					}
				}
				if !found {
					fmt.Fprintf(os.Stderr, "Warning: could not find enumeratedValue.derivedFrom of %s for register field %s\n", enumeratedValues.DerivedFrom, fieldName)
				}
			} else {
				// The derivedFrom attribute may also point to enumerated values
				// in other registers and even peripherals, but this feature
				// isn't often used in SVD files.
				fmt.Fprintf(os.Stderr, "TODO: enumeratedValue.derivedFrom to a different register: %s\n", enumeratedValues.DerivedFrom)
			}
		}

		bitfields = append(bitfields, Bitfield{
			Name:   fieldName,
			Offset: lsb,
			Mask:   (0xffffffff >> (31 - (msb - lsb))) << lsb,
		})
		fields = append(fields, Constant{
			Name:        fmt.Sprintf("%s_%s%s_%s_Pos", groupName, bitfieldPrefix, regName, fieldName),
			Description: fmt.Sprintf("Position of %s field.", fieldName),
			Value:       uint64(lsb),
		})
		fields = append(fields, Constant{
			Name:        fmt.Sprintf("%s_%s%s_%s_Msk", groupName, bitfieldPrefix, regName, fieldName),
			Description: fmt.Sprintf("Bit mask of %s field.", fieldName),
			Value:       (0xffffffffffffffff >> (63 - (msb - lsb))) << lsb,
		})
		if lsb == msb { // single bit
			fields = append(fields, Constant{
				Name:        fmt.Sprintf("%s_%s%s_%s", groupName, bitfieldPrefix, regName, fieldName),
				Description: fmt.Sprintf("Bit %s.", fieldName),
				Value:       1 << lsb,
			})
		}
		for _, enumEl := range enumeratedValues.EnumeratedValue {
			enumName := enumEl.Name
			// Renesas has enum without actual values that we have to skip
			if enumEl.Value == "" {
				continue
			}

			if strings.EqualFold(enumName, "reserved") || !validName.MatchString(enumName) {
				continue
			}
			if !unicode.IsUpper(rune(enumName[0])) && !unicode.IsDigit(rune(enumName[0])) {
				enumName = strings.ToUpper(enumName)
			}
			enumDescription := formatText(enumEl.Description)
			var enumValue uint64
			var err error
			if strings.HasPrefix(enumEl.Value, "0b") {
				val := strings.TrimPrefix(enumEl.Value, "0b")
				enumValue, err = strconv.ParseUint(val, 2, 64)
			} else {
				enumValue, err = strconv.ParseUint(enumEl.Value, 0, 64)
			}
			if err != nil {
				if enumBitSpecifier.MatchString(enumEl.Value) {
					// NXP and Renesas SVDs use the form #xx1x, #x0xx, etc for values
					enumValue, err = strconv.ParseUint(strings.ReplaceAll(enumEl.Value[1:], "x", "0"), 2, 64)
					if err != nil {
						panic(err)
					}
				} else {
					panic(err)
				}
			}
			enumName = fmt.Sprintf("%s_%s%s_%s_%s", groupName, bitfieldPrefix, regName, fieldName, enumName)

			// Avoid duplicate values. Duplicate names with the same value are
			// allowed, but the same name with a different value is not. Instead
			// of trying to work around those cases, remove the value entirely
			// as there is probably not one correct answer in such a case.
			// For example, SVD files from NXP have enums limited to 20
			// characters, leading to lots of duplicates when these enum names
			// are long. Nothing here can really fix those cases.
			previousEnumValue, seenBefore := enumSeen[enumName]
			if seenBefore {
				if previousEnumValue < 0 {
					// There was a mismatch before, ignore all equally named fields.
					continue
				}
				if int64(enumValue) != previousEnumValue {
					// There is a mismatch. Mark it as such, and remove the
					// existing enum bitfield value.
					enumSeen[enumName] = -1
					for i, field := range fields {
						if field.Name == enumName {
							fields = append(fields[:i], fields[i+1:]...)
							break
						}
					}
				}
				continue
			}
			enumSeen[enumName] = int64(enumValue)

			fields = append(fields, Constant{
				Name:        enumName,
				Description: enumDescription,
				Value:       enumValue,
			})
		}
	}
	return fields, bitfields
}

type Register struct {
	element     *SVDRegister
	baseAddress uint64
}

func NewRegister(element *SVDRegister, baseAddress uint64) *Register {
	return &Register{
		element:     element,
		baseAddress: baseAddress,
	}
}

func (r *Register) name() string {
	return strings.ReplaceAll(r.element.Name, "[%s]", "")
}

func (r *Register) description() string {
	return formatText(r.element.Description)
}

func (r *Register) address() uint64 {
	offsetString := r.element.Offset
	if offsetString == nil {
		offsetString = r.element.AddressOffset
	}
	addr, err := strconv.ParseUint(*offsetString, 0, 32)
	if err != nil {
		panic(err)
	}
	return r.baseAddress + addr
}

func (r *Register) dim() int {
	if r.element.Dim == nil {
		return -1 // no dim elements
	}
	dim, err := strconv.ParseInt(*r.element.Dim, 0, 32)
	if err != nil {
		panic(err)
	}
	return int(dim)
}

func (r *Register) dimIndex() []string {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("register", r.name())
			panic(err)
		}
	}()

	dim := r.dim()
	if r.element.DimIndex == nil {
		if dim <= 0 {
			return nil
		}

		idx := make([]string, dim)
		for i := range idx {
			idx[i] = strconv.FormatInt(int64(i), 10)
		}
		return idx
	}

	t := strings.Split(*r.element.DimIndex, "-")
	if len(t) == 2 {
		// renesas uses hex letters e.g. A-B
		if strings.Contains("ABCDEFabcdef", t[0]) {
			t[0] = "0x" + t[0]
		}
		if strings.Contains("ABCDEFabcdef", t[1]) {
			t[1] = "0x" + t[1]
		}

		x, err := strconv.ParseInt(t[0], 0, 32)
		if err != nil {
			panic(err)
		}
		y, err := strconv.ParseInt(t[1], 0, 32)
		if err != nil {
			panic(err)
		}

		if x < 0 || y < x || y-x != int64(dim-1) {
			panic("invalid dimIndex")
		}

		idx := make([]string, dim)
		for i := x; i <= y; i++ {
			idx[i-x] = strconv.FormatInt(i, 10)
		}
		return idx
	} else if len(t) > 2 {
		panic("invalid dimIndex")
	}

	s := strings.Split(*r.element.DimIndex, ",")
	if len(s) != dim {
		panic("invalid dimIndex")
	}

	return s
}

func (r *Register) size() int {
	if r.element.Size != nil {
		size, err := strconv.ParseInt(*r.element.Size, 0, 32)
		if err != nil {
			panic(err)
		}
		return int(size) / 8
	}
	return 4
}

func parseRegister(groupName string, regEl *SVDRegister, baseAddress uint64, bitfieldPrefix string) []*PeripheralField {
	reg := NewRegister(regEl, baseAddress)

	if reg.dim() != -1 {
		dimIncrement, err := strconv.ParseUint(regEl.DimIncrement, 0, 32)
		if err != nil {
			panic(err)
		}
		if strings.Contains(reg.name(), "%s") {
			// a "spaced array" of registers, special processing required
			// we need to generate a separate register for each "element"
			var results []*PeripheralField
			shortName := strings.ToUpper(strings.ReplaceAll(strings.ReplaceAll(reg.name(), "_%s", ""), "%s", ""))
			for i, j := range reg.dimIndex() {
				regAddress := reg.address() + (uint64(i) * dimIncrement)
				results = append(results, &PeripheralField{
					Name:        strings.ToUpper(strings.ReplaceAll(reg.name(), "%s", j)),
					Address:     regAddress,
					Description: reg.description(),
					Array:       -1,
					ElementSize: reg.size(),
					ShortName:   shortName,
				})
			}
			// set first result bitfield
			results[0].Constants, results[0].Bitfields = parseBitfields(groupName, shortName, regEl.Fields, bitfieldPrefix)
			results[0].HasBitfields = len(results[0].Bitfields) > 0
			for i := 1; i < len(results); i++ {
				results[i].Bitfields = results[0].Bitfields
				results[i].HasBitfields = results[0].HasBitfields
			}
			return results
		}
	}
	regName := reg.name()
	if !unicode.IsUpper(rune(regName[0])) && !unicode.IsDigit(rune(regName[0])) {
		regName = strings.ToUpper(regName)
	}
	regName = cleanName(regName)

	constants, bitfields := parseBitfields(groupName, regName, regEl.Fields, bitfieldPrefix)
	return []*PeripheralField{&PeripheralField{
		Name:         regName,
		Address:      reg.address(),
		Description:  reg.description(),
		Constants:    constants,
		Array:        reg.dim(),
		ElementSize:  reg.size(),
		ShortName:    regName,
		Bitfields:    bitfields,
		HasBitfields: len(bitfields) > 0,
	}}
}

// The Go module for this device.
func writeGo(outdir string, device *Device, interruptSystem string) error {
	outf, err := os.Create(filepath.Join(outdir, device.Metadata.NameLower+".go"))
	if err != nil {
		return err
	}
	defer outf.Close()
	w := bufio.NewWriter(outf)

	maxInterruptValue := 0
	for _, intr := range device.Interrupts {
		if intr.Value > maxInterruptValue {
			maxInterruptValue = intr.Value
		}
	}

	interruptHandlerMap := make(map[string]*Interrupt)
	var interruptHandlers []*Interrupt
	for _, intr := range device.Interrupts {
		if _, ok := interruptHandlerMap[intr.HandlerName]; !ok {
			interruptHandlerMap[intr.HandlerName] = intr
			interruptHandlers = append(interruptHandlers, intr)
		}
	}

	t := template.Must(template.New("go").Funcs(template.FuncMap{
		"bytesNeeded": func(i, j uint64) uint64 { return j - i },
		"isMultiline": isMultiline,
		"splitLine":   splitLine,
	}).Parse(`// Automatically generated file. DO NOT EDIT.
// Generated by gen-device-svd.go from {{.device.Metadata.File}}, see {{.device.Metadata.DescriptorSource}}

//go:build {{.pkgName}} && {{.device.Metadata.NameLower}}

/*
{{.device.Metadata.Description}}
*/
{{.device.Metadata.LicenseBlock}}
package {{.pkgName}}

import (
	"runtime/volatile"
	"unsafe"
)

// Some information about this device.
const (
	Device       = "{{.device.Metadata.Name}}"
{{- if .device.Metadata.HasCPUInfo }}
	CPU          = "{{.device.Metadata.CPUName}}"
	FPUPresent   = {{.device.Metadata.FPUPresent}}
	NVICPrioBits = {{.device.Metadata.NVICPrioBits}}
{{- end }}
)

// Interrupt numbers.
const (
{{- range .device.Interrupts}}
	{{- if .Description}}
		{{- range .Description|splitLine}}
	// {{.}}
		{{- end}}
	{{- end}}
	IRQ_{{.Name}} = {{.Value}}
	{{- "\n"}}
{{- end}}
	// Highest interrupt number on this device.
	IRQ_max = {{.interruptMax}} 
)

// Pseudo function call that is replaced by the compiler with the actual
// functions registered through interrupt.New.
//go:linkname callHandlers runtime/interrupt.callHandlers
func callHandlers(num int)

{{- if eq .interruptSystem "hardware"}}
{{- range .interruptHandlers}}
//export {{.HandlerName}}
func interrupt{{.Name}}() {
	callHandlers(IRQ_{{.Name}})
}
{{- end}}
{{- end}}

{{- if eq .interruptSystem "software"}}
func HandleInterrupt(num int) {
	switch num {
	{{- range .interruptHandlers}}
	case IRQ_{{.Name}}:
		callHandlers(IRQ_{{.Name}})
	{{- end}}
	}
}
{{- end}}

// Peripherals.
var (
{{- range .device.Peripherals}}
	{{- if .Description}}
		{{- range .Description|splitLine}}
	// {{.}}
		{{- end}}
	{{- end}}
	{{.Name}} = (*{{.GroupName}}_Type)(unsafe.Pointer(uintptr(0x{{printf "%x" .BaseAddress}})))
	{{- "\n"}}
{{- end}}
)

`))
	err = t.Execute(w, map[string]interface{}{
		"device":            device,
		"pkgName":           filepath.Base(strings.TrimRight(outdir, "/")),
		"interruptMax":      maxInterruptValue,
		"interruptSystem":   interruptSystem,
		"interruptHandlers": interruptHandlers,
	})
	if err != nil {
		return err
	}

	// Define peripheral struct types.
	for _, peripheral := range device.Peripherals {
		if peripheral.Registers == nil {
			// This peripheral was derived from another peripheral. No new type
			// needs to be defined for it.
			continue
		}
		fmt.Fprintln(w)
		if peripheral.Description != "" {
			for _, l := range splitLine(peripheral.Description) {
				fmt.Fprintf(w, "// %s\n", l)
			}
		}
		fmt.Fprintf(w, "type %s_Type struct {\n", peripheral.GroupName)

		address := peripheral.BaseAddress
		type clusterInfo struct {
			name        string
			description string
			address     uint64
			size        uint64
			registers   []*PeripheralField
		}
		clusters := []clusterInfo{}
		for _, register := range peripheral.Registers {
			if register.Registers == nil && address > register.Address {
				// In Nordic SVD files, these registers are deprecated or
				// duplicates, so can be ignored.
				//fmt.Fprintf(os.Stderr, "skip: %s.%s 0x%x - 0x%x %d\n", peripheral.Name, register.name, address, register.address, register.elementSize)
				// remove bit fields from such register
				register.Bitfields = nil
				continue
			}

			var regType string
			switch register.ElementSize {
			case 8:
				regType = "volatile.Register64"
			case 4:
				regType = "volatile.Register32"
			case 2:
				regType = "volatile.Register16"
			case 1:
				regType = "volatile.Register8"
			default:
				regType = "volatile.Register32"
			}

			// insert padding, if needed
			if address < register.Address {
				bytesNeeded := register.Address - address
				if bytesNeeded == 1 {
					w.WriteString("\t_ byte\n")
				} else {
					fmt.Fprintf(w, "\t_ [%d]byte\n", bytesNeeded)
				}
				address = register.Address
			}

			lastCluster := false
			if register.Registers != nil {
				// This is a cluster, not a register. Create the cluster type.
				regType = peripheral.GroupName + "_" + register.Name
				clusters = append(clusters, clusterInfo{regType, register.Description, register.Address, uint64(register.ElementSize), register.Registers})
				regType = regType + "_Type"
				subaddress := register.Address
				for _, subregister := range register.Registers {

					if subaddress != subregister.Address {
						bytesNeeded := subregister.Address - subaddress
						subaddress += bytesNeeded
					}
					var subregSize uint64
					if subregister.Array != -1 {
						subregSize = uint64(subregister.Array * subregister.ElementSize)
					} else {
						subregSize = uint64(subregister.ElementSize)
					}
					subaddress += subregSize
				}
				if register.Array == -1 {
					lastCluster = true
				}
				address = subaddress
			}

			if register.Array != -1 {
				regType = fmt.Sprintf("[%d]%s", register.Array, regType)
			}
			fmt.Fprintf(w, "\t%s %s // 0x%X\n", register.Name, regType, register.Address-peripheral.BaseAddress)

			// next address
			if lastCluster {
				lastCluster = false
			} else if register.Array != -1 {
				address = register.Address + uint64(register.ElementSize*register.Array)
			} else {
				address = register.Address + uint64(register.ElementSize)
			}
		}
		w.WriteString("}\n")

		for _, register := range peripheral.Registers {
			regName := register.Name
			writeGoRegisterBitfieldType(w, register, peripheral.GroupName, regName)
		}

		// Define clusters
		for i := 0; i < len(clusters); i++ {
			cluster := clusters[i]
			if len(cluster.registers) == 0 {
				continue
			}

			if _, ok := device.PeripheralDict[cluster.name]; ok {
				continue
			}

			fmt.Fprintln(w)
			if cluster.description != "" {
				for _, l := range splitLine(cluster.description) {
					fmt.Fprintf(w, "// %s\n", l)
				}
			}
			fmt.Fprintf(w, "type %s_Type struct {\n", cluster.name)

			address := cluster.address

			for _, register := range cluster.registers {
				if register.Registers == nil && address > register.Address {
					// In Nordic SVD files, these registers are deprecated or
					// duplicates, so can be ignored.
					//fmt.Fprintf(os.Stderr, "skip: %s.%s 0x%x - 0x%x %d\n", peripheral.Name, register.name, address, register.address, register.elementSize)
					continue
				}
				var regType string
				switch register.ElementSize {
				case 8:
					regType = "volatile.Register64"
				case 4:
					regType = "volatile.Register32"
				case 2:
					regType = "volatile.Register16"
				case 1:
					regType = "volatile.Register8"
				default:
					regType = "volatile.Register32"
				}

				// insert padding, if needed
				if address < register.Address {
					bytesNeeded := register.Address - address
					if bytesNeeded == 1 {
						w.WriteString("\t_ byte\n")
					} else {
						fmt.Fprintf(w, "\t_ [%d]byte\n", bytesNeeded)
					}
					address = register.Address
				}

				lastCluster := false
				if register.Registers != nil {
					// This is a cluster, not a register. Create the cluster type.
					regType = peripheral.GroupName + "_" + register.Name
					clusters = append(clusters, clusterInfo{regType, register.Description, register.Address, uint64(register.ElementSize), register.Registers})
					regType = regType + "_Type"

					subaddress := register.Address
					for _, subregister := range register.Registers {
						if subaddress != subregister.Address {
							bytesNeeded := subregister.Address - subaddress
							subaddress += bytesNeeded
						}
						var subregSize uint64
						if subregister.Array != -1 {
							subregSize = uint64(subregister.Array * subregister.ElementSize)
						} else {
							subregSize = uint64(subregister.ElementSize)
						}
						subaddress += subregSize
					}
					if register.Array == -1 {
						lastCluster = true
					}
					address = subaddress
				}

				if register.Array != -1 {
					regType = fmt.Sprintf("[%d]%s", register.Array, regType)
				}
				fmt.Fprintf(w, "\t%s %s // 0x%X\n", register.Name, regType, register.Address-peripheral.BaseAddress)

				// next address
				if lastCluster {
					lastCluster = false
				} else if register.Array != -1 {
					address = register.Address + uint64(register.ElementSize*register.Array)
				} else {
					address = register.Address + uint64(register.ElementSize)
				}
			}
			// make sure the structure is full
			if cluster.size > (address - cluster.registers[0].Address) {
				bytesNeeded := cluster.size - (address - cluster.registers[0].Address)
				if bytesNeeded == 1 {
					w.WriteString("\t_ byte\n")
				} else {
					fmt.Fprintf(w, "\t_ [%d]byte\n", bytesNeeded)
				}
			} else if cluster.size != (address - cluster.registers[0].Address) {
				println("peripheral:", peripheral.Name, "cluster:", cluster.name, "size:", cluster.size, "struct size:", (address - cluster.registers[0].Address))
			}
			w.WriteString("}\n")

			for _, register := range cluster.registers {
				regName := register.Name
				if register.Array == -1 {
					writeGoRegisterBitfieldType(w, register, cluster.name, regName)
				}
			}
		}
	}

	// Define bitfields.
	for _, peripheral := range device.Peripherals {
		if peripheral.Registers == nil {
			// This peripheral was derived from another peripheral. Constants are
			// already defined.
			continue
		}
		fmt.Fprintf(w, "\n// Constants for %s", peripheral.Name)
		if isMultiline(peripheral.Description) {
			for _, l := range splitLine(peripheral.Description) {
				fmt.Fprintf(w, "\n// %s", l)
			}
		} else if peripheral.Description != "" {
			fmt.Fprintf(w, ": %s", peripheral.Description)
		}

		fmt.Fprint(w, "\nconst(")
		for _, register := range peripheral.Registers {
			if len(register.Constants) != 0 {
				writeGoRegisterConstants(w, register, register.Name)
			}
			if register.Registers == nil {
				continue
			}
			for _, subregister := range register.Registers {
				writeGoRegisterConstants(w, subregister, register.Name+"."+subregister.Name)
			}
		}
		w.WriteString(")\n")
	}

	return w.Flush()
}

func writeGoRegisterConstants(w *bufio.Writer, register *PeripheralField, name string) {
	w.WriteString("\n\t// " + name)
	if register.Description != "" {
		if isMultiline(register.Description) {
			for _, l := range splitLine(register.Description) {
				w.WriteString("\n\t// " + l)
			}
		} else {
			w.WriteString(": " + register.Description)
		}
	}
	w.WriteByte('\n')
	for _, bitfield := range register.Constants {
		if bitfield.Description != "" {
			for _, l := range splitLine(bitfield.Description) {
				w.WriteString("\t// " + l + "\n")
			}
		}
		fmt.Fprintf(w, "\t%s = 0x%x\n", bitfield.Name, bitfield.Value)
	}
}

func writeGoRegisterBitfieldType(w *bufio.Writer, register *PeripheralField, peripheralName, registerName string) {
	if len(register.Bitfields) == 0 {
		return
	}
	w.WriteString("\n// " + peripheralName + "." + registerName)
	if register.Description != "" {
		if isMultiline(register.Description) {
			for _, l := range splitLine(register.Description) {
				w.WriteString("\n\t// " + l)
			}
		} else {
			w.WriteString(": " + register.Description)
		}
	}
	w.WriteByte('\n')
	var bitSize int
	var maxMask uint32
	switch register.ElementSize {
	case 8:
		bitSize = 64
		maxMask = 0xffffffff
		// maxMask = 0xffffffffffffffff // TODO how to handle 64-bit fields
	case 4:
		bitSize = 32
		maxMask = 0xffffffff
	case 2:
		bitSize = 16
		maxMask = 0xffff
	case 1:
		bitSize = 8
		maxMask = 0xff
	default:
		bitSize = 32
		maxMask = 0xffffffff
	}

	typeName := fmt.Sprintf("%s_Type", peripheralName)

	for _, bitfield := range register.Bitfields {
		idxArg := ""
		regAccess := "&o." + registerName + ".Reg"
		if register.Array != -1 {
			idxArg = "idx int, "
			regAccess = "&o." + registerName + "[idx].Reg"
		}
		var funcSuffix string
		if maxMask == bitfield.Mask || registerName == bitfield.Name {
			funcSuffix = registerName
		} else {
			funcSuffix = registerName + "_" + bitfield.Name
		}
		fmt.Fprintf(w, "func (o *%s) Set%s(%s value uint%d) {\n", typeName, funcSuffix, idxArg, bitSize)
		if maxMask == bitfield.Mask {
			fmt.Fprintf(w, "\tvolatile.StoreUint%d(%s, value)\n", bitSize, regAccess)
		} else if bitfield.Offset > 0 {
			fmt.Fprintf(w, "\tvolatile.StoreUint%d(%s, volatile.LoadUint%d(%s)&^(0x%x)|value<<%d)\n", bitSize, regAccess, bitSize, regAccess, bitfield.Mask, bitfield.Offset)
		} else {
			fmt.Fprintf(w, "\tvolatile.StoreUint%d(%s, volatile.LoadUint%d(%s)&^(0x%x)|value)\n", bitSize, regAccess, bitSize, regAccess, bitfield.Mask)
		}
		w.WriteString("}\n")
		fmt.Fprintf(w, "func (o *%s) Get%s(%s) uint%d {\n", typeName, funcSuffix, idxArg, bitSize)
		if maxMask == bitfield.Mask {
			fmt.Fprintf(w, "\treturn volatile.LoadUint%d(%s)\n", bitSize, regAccess)
		} else if bitfield.Offset > 0 {
			fmt.Fprintf(w, "\treturn (volatile.LoadUint%d(%s)&0x%x) >> %d\n", bitSize, regAccess, bitfield.Mask, bitfield.Offset)
		} else {
			fmt.Fprintf(w, "\treturn volatile.LoadUint%d(%s)&0x%x\n", bitSize, regAccess, bitfield.Mask)
		}
		w.WriteString("}\n")
	}
}

// The interrupt vector, which is hard to write directly in Go.
func writeAsm(outdir string, device *Device) error {
	outf, err := os.Create(filepath.Join(outdir, device.Metadata.NameLower+".s"))
	if err != nil {
		return err
	}
	defer outf.Close()
	w := bufio.NewWriter(outf)

	t := template.Must(template.New("go").Parse(`// Automatically generated file. DO NOT EDIT.
// Generated by gen-device-svd.go from {{.File}}, see {{.DescriptorSource}}

/*
{{.Description}}
*/

{{.LicenseBlock}}

.syntax unified

// This is the default handler for interrupts, if triggered but not defined.
.section .text.Default_Handler
.global  Default_Handler
.type    Default_Handler, %function
Default_Handler:
    wfe
    b    Default_Handler
.size Default_Handler, .-Default_Handler

// Avoid the need for repeated .weak and .set instructions.
.macro IRQ handler
    .weak  \handler
    .set   \handler, Default_Handler
.endm

// Must set the "a" flag on the section:
// https://svnweb.freebsd.org/base/stable/11/sys/arm/arm/locore-v4.S?r1=321049&r2=321048&pathrev=321049
// https://sourceware.org/binutils/docs/as/Section.html#ELF-Version
.section .isr_vector, "a", %progbits
.global  __isr_vector
__isr_vector:
    // Interrupt vector as defined by Cortex-M, starting with the stack top.
    // On reset, SP is initialized with *0x0 and PC is loaded with *0x4, loading
    // _stack_top and Reset_Handler.
    .long _stack_top
    .long Reset_Handler
    .long NMI_Handler
    .long HardFault_Handler
    .long MemoryManagement_Handler
    .long BusFault_Handler
    .long UsageFault_Handler
    .long 0
    .long 0
    .long 0
    .long 0
    .long SVC_Handler
    .long DebugMon_Handler
    .long 0
    .long PendSV_Handler
    .long SysTick_Handler

    // Extra interrupts for peripherals defined by the hardware vendor.
`))
	err = t.Execute(w, device.Metadata)
	if err != nil {
		return err
	}
	num := 0
	for _, intr := range device.Interrupts {
		if intr.Value == num-1 {
			continue
		}
		if intr.Value < num {
			panic("interrupt numbers are not sorted")
		}
		for intr.Value > num {
			w.WriteString("    .long 0\n")
			num++
		}
		num++
		fmt.Fprintf(w, "    .long %s\n", intr.HandlerName)
	}

	w.WriteString(`
    // Define default implementations for interrupts, redirecting to
    // Default_Handler when not implemented.
    IRQ NMI_Handler
    IRQ HardFault_Handler
    IRQ MemoryManagement_Handler
    IRQ BusFault_Handler
    IRQ UsageFault_Handler
    IRQ SVC_Handler
    IRQ DebugMon_Handler
    IRQ PendSV_Handler
    IRQ SysTick_Handler
`)
	for _, intr := range device.Interrupts {
		fmt.Fprintf(w, "    IRQ %s_IRQHandler\n", intr.Name)
	}
	w.WriteString(`
.size __isr_vector, .-__isr_vector
`)
	return w.Flush()
}

func generate(indir, outdir, sourceURL, interruptSystem string) error {
	if _, err := os.Stat(indir); errors.Is(err, fs.ErrNotExist) {
		fmt.Fprintln(os.Stderr, "cannot find input directory:", indir)
		os.Exit(1)
	}
	os.MkdirAll(outdir, 0777)

	infiles, err := filepath.Glob(filepath.Join(indir, "*.svd"))
	if err != nil {
		fmt.Fprintln(os.Stderr, "could not read .svd files:", err)
		os.Exit(1)
	}
	sort.Strings(infiles)
	for _, infile := range infiles {
		fmt.Println(infile)
		device, err := readSVD(infile, sourceURL)
		if err != nil {
			return fmt.Errorf("failed to read: %w", err)
		}
		err = writeGo(outdir, device, interruptSystem)
		if err != nil {
			return fmt.Errorf("failed to write Go file: %w", err)
		}
		switch interruptSystem {
		case "software":
			// Nothing to do.
		case "hardware":
			err = writeAsm(outdir, device)
			if err != nil {
				return fmt.Errorf("failed to write assembly file: %w", err)
			}
		default:
			return fmt.Errorf("unknown interrupt system: %s", interruptSystem)
		}
	}
	return nil
}

func main() {
	sourceURL := flag.String("source", "", "source SVD file")
	interruptSystem := flag.String("interrupts", "hardware", "interrupt system in use (software, hardware)")
	flag.Parse()
	if flag.NArg() != 2 {
		fmt.Fprintln(os.Stderr, "provide exactly two arguments: input directory (with .svd files) and output directory for generated files")
		flag.PrintDefaults()
		return
	}
	indir := flag.Arg(0)
	outdir := flag.Arg(1)
	err := generate(indir, outdir, *sourceURL, *interruptSystem)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}


================================================
FILE: tools/sizediff
================================================
#!/usr/bin/env python3

# Small tool to compare code size between TinyGo runs (with the -size=short flag).

import sys

class Comparison:
    def __init__(self, command, flash0, ram0, flash1, ram1):
        self.command = command
        self.flash0 = flash0
        self.ram0 = ram0
        self.flash1 = flash1
        self.ram1 = ram1

    @property
    def ramdiff(self):
        return self.ram1 - self.ram0

    @property
    def flashdiff(self):
        return self.flash1 - self.flash0

def readSizes(path):
    sizes = []
    lines = open(path).readlines()
    for i in range(len(lines)):
        if not lines[i].strip().startswith('code '):
            continue
        # found a size header
        code, data, bss, flash, ram = map(int, lines[i+1].replace("|", "").split())
        command = lines[i-1].strip()
        sizes.append({
            'command': command,
            'flash':   flash,
            'ram':     ram
        })
    return sizes

def main():
    path0 = sys.argv[1]
    path1 = sys.argv[2]
    sizes0 = readSizes(path0)
    sizes1 = readSizes(path1)
    comparisons = []
    for i in range(len(sizes0)):
        if i >= len(sizes1):
            print('%s has more commands than %s' % (path0, path1))
            print('   ', sizes0[i]['command'])
            break
        if sizes0[i]['command'] != sizes1[i]['command']:
            print('not the same command!')
            print('   ', sizes0[i]['command'])
            print('   ', sizes1[i]['command'])
        comparisons.append(Comparison(sizes0[i]['command'], sizes0[i]['flash'], sizes0[i]['ram'], sizes1[i]['flash'], sizes1[i]['ram']))
    if len(sizes0) < len(sizes1):
        print('%s has more commands than %s' % (path1, path0))
        print('   ', sizes1[len(sizes0)]['command'])
    comparisons.sort(key=lambda x: x.flashdiff)
    totalFlash0 = 0
    totalFlash1 = 0
    totalRam0 = 0
    totalRam1 = 0
    totalDiff = 0
    totalRamDiff = 0
    totalProduct = 1
    totalRamProduct = 1
    print(' flash                          ram')
    print(' before   after   diff          before   after   diff')
    for comparison in comparisons:
        diffPct = comparison.flashdiff / comparison.flash0
        diffRamPct = comparison.ramdiff / comparison.ram0
        print('%7d %7d %6d %6.2f%% %7d %7d %6d %6.2f%% %s' % (comparison.flash0, comparison.flash1, comparison.flashdiff, diffPct * 100, comparison.ram0, comparison.ram1, comparison.ramdiff, diffRamPct * 100, comparison.command))
        totalFlash0 += comparison.flash0
        totalFlash1 += comparison.flash1
        totalDiff += comparison.flashdiff
        totalProduct *= (1 + diffPct)
        totalRam0 += comparison.ram0
        totalRam1 += comparison.ram1
        totalRamDiff += comparison.ramdiff
        totalRamProduct *= (1 + diffRamPct)
    geomean = totalProduct ** (1.0 / float(len(comparisons)))
    geomeanRam = totalRamProduct ** (1.0 / float(len(comparisons)))
    print('%7d %7d %6d %6.2f%% %7d %7d %6d %6.2f%%' % (totalFlash0, totalFlash1, totalDiff, geomean - 1, totalRam0, totalRam1, totalRamDiff, geomeanRam - 1))


if __name__ == '__main__':
    main()


================================================
FILE: tools/tgtestjson.sh
================================================
#!/usr/bin/env bash

# Run tests and convert output to json with go tool test2json.
# This is a workaround for the lack of -json output in tinygo test.
# Some variables must be set in the environment beforehand.
# TODO: let's just add -json support to tinygo test.

TINYGO="${TINYGO:-tinygo}"
PACKAGES="${PACKAGES:-"./tests"}"
TARGET="${TARGET:-wasip2}"
TESTOPTS="${TESTOPTS:-"-x -work"}"

# go clean -testcache
for pkg in $PACKAGES; do
    # Example invocation with test2json in BigGo:
    # go test -test.v=test2json ./$pkg 2>&1 | go tool test2json -p $pkg

    # Uncomment to see resolved commands in output
    # >&2 echo "${TINYGO} test -v -target $TARGET $TESTOPTS $pkg 2>&1 | go tool test2json -p $pkg"
    "${TINYGO}" test -v -target $TARGET $TESTOPTS $pkg 2>&1 | go tool test2json -p $pkg

done


================================================
FILE: transform/allocs.go
================================================
package transform

// This file implements an escape analysis pass. It looks for calls to
// runtime.alloc and replaces these calls with a stack allocation if the
// allocated value does not escape. It uses the LLVM nocapture flag for
// interprocedural escape analysis.

import (
	"fmt"
	"go/token"
	"regexp"

	"tinygo.org/x/go-llvm"
)

// OptimizeAllocs tries to replace heap allocations with stack allocations
// whenever possible. It relies on the LLVM 'nocapture' flag for interprocedural
// escape analysis, and within a function looks whether an allocation can escape
// to the heap.
// If printAllocs is non-nil, it indicates the regexp of functions for which a
// heap allocation explanation should be printed (why the object can't be stack
// allocated).
func OptimizeAllocs(mod llvm.Module, printAllocs *regexp.Regexp, maxStackAlloc uint64, logger func(token.Position, string)) {
	allocator := mod.NamedFunction("runtime.alloc")
	if allocator.IsNil() {
		// nothing to optimize
		return
	}

	targetData := llvm.NewTargetData(mod.DataLayout())
	defer targetData.Dispose()
	ctx := mod.Context()
	builder := ctx.NewBuilder()
	defer builder.Dispose()

	// Determine the maximum alignment on this platform.
	complex128Type := ctx.StructType([]llvm.Type{ctx.DoubleType(), ctx.DoubleType()}, false)
	maxAlign := int64(targetData.ABITypeAlignment(complex128Type))

	for _, heapalloc := range getUses(allocator) {
		logAllocs := printAllocs != nil && printAllocs.MatchString(heapalloc.InstructionParent().Parent().Name())
		if heapalloc.Operand(0).IsAConstantInt().IsNil() {
			// Do not allocate variable length arrays on the stack.
			if logAllocs {
				logAlloc(logger, heapalloc, "size is not constant")
			}
			continue
		}

		size := heapalloc.Operand(0).ZExtValue()
		if size > maxStackAlloc {
			// The maximum size for a stack allocation.
			if logAllocs {
				logAlloc(logger, heapalloc, fmt.Sprintf("object size %d exceeds maximum stack allocation size %d", size, maxStackAlloc))
			}
			continue
		}

		if size == 0 {
			// If the size is 0, the pointer is allowed to alias other
			// zero-sized pointers. Use the pointer to the global that would
			// also be returned by runtime.alloc.
			zeroSizedAlloc := mod.NamedGlobal("runtime.zeroSizedAlloc")
			if !zeroSizedAlloc.IsNil() {
				heapalloc.ReplaceAllUsesWith(zeroSizedAlloc)
				heapalloc.EraseFromParentAsInstruction()
			}
			continue
		}

		// In general the pattern is:
		//     %0 = call i8* @runtime.alloc(i32 %size, i8* null)
		//     %1 = bitcast i8* %0 to type*
		//     (use %1 only)
		// But the bitcast might sometimes be dropped when allocating an *i8.
		// The 'bitcast' variable below is thus usually a bitcast of the
		// heapalloc but not always.
		bitcast := heapalloc // instruction that creates the value
		if uses := getUses(heapalloc); len(uses) == 1 && !uses[0].IsABitCastInst().IsNil() {
			// getting only bitcast use
			bitcast = uses[0]
		}

		if at := valueEscapesAt(bitcast); !at.IsNil() {
			if logAllocs {
				atPos := getPosition(at)
				msg := "escapes at unknown line"
				if atPos.Line != 0 {
					msg = fmt.Sprintf("escapes at line %d", atPos.Line)
				}
				logAlloc(logger, heapalloc, msg)
			}
			continue
		}
		// The pointer value does not escape.

		// Determine the appropriate alignment of the alloca.
		attr := heapalloc.GetCallSiteEnumAttribute(0, llvm.AttributeKindID("align"))
		alignment := int(maxAlign)
		if !attr.IsNil() {
			// 'align' return value attribute is set, so use it.
			// This is basically always the case, but to be sure we'll default
			// to maxAlign if it isn't.
			alignment = int(attr.GetEnumValue())
		}

		// Insert alloca in the entry block. Do it here so that mem2reg can
		// promote it to a SSA value.
		fn := bitcast.InstructionParent().Parent()
		builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction())
		allocaType := llvm.ArrayType(mod.Context().Int8Type(), int(size))
		alloca := builder.CreateAlloca(allocaType, "stackalloc")
		alloca.SetAlignment(alignment)

		// Zero the allocation inside the block where the value was originally allocated.
		zero := llvm.ConstNull(alloca.AllocatedType())
		builder.SetInsertPointBefore(bitcast)
		store := builder.CreateStore(zero, alloca)
		store.SetAlignment(alignment)

		// Replace heap alloc bitcast with stack alloc bitcast.
		bitcast.ReplaceAllUsesWith(alloca)
		if heapalloc != bitcast {
			bitcast.EraseFromParentAsInstruction()
		}
		heapalloc.EraseFromParentAsInstruction()
	}
}

// valueEscapesAt returns the instruction where the given value may escape and a
// nil llvm.Value if it definitely doesn't. The value must be an instruction.
func valueEscapesAt(value llvm.Value) llvm.Value {
	uses := getUses(value)
	for _, use := range uses {
		if use.IsAInstruction().IsNil() {
			panic("expected instruction use")
		}
		switch use.InstructionOpcode() {
		case llvm.GetElementPtr:
			if at := valueEscapesAt(use); !at.IsNil() {
				return at
			}
		case llvm.BitCast:
			// A bitcast escapes if the casted-to value escapes.
			if at := valueEscapesAt(use); !at.IsNil() {
				return at
			}
		case llvm.Load:
			// Load does not escape.
		case llvm.Store:
			// Store only escapes when the value is stored to, not when the
			// value is stored into another value.
			if use.Operand(0) == value {
				return use
			}
		case llvm.Call:
			if !hasFlag(use, value, "nocapture") {
				return use
			}
		case llvm.ICmp:
			// Comparing pointers don't let the pointer escape.
			// This is often a compiler-inserted nil check.
		default:
			// Unknown instruction, might escape.
			return use
		}
	}

	// Checked all uses, and none let the pointer value escape.
	return llvm.Value{}
}

// logAlloc prints a message to stderr explaining why the given object had to be
// allocated on the heap.
func logAlloc(logger func(token.Position, string), allocCall llvm.Value, reason string) {
	logger(getPosition(allocCall), "object allocated on the heap: "+reason)
}


================================================
FILE: transform/allocs_test.go
================================================
package transform_test

import (
	"go/token"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"testing"

	"github.com/tinygo-org/tinygo/transform"
	"tinygo.org/x/go-llvm"
)

func TestAllocs(t *testing.T) {
	t.Parallel()
	testTransform(t, "testdata/allocs", func(mod llvm.Module) {
		transform.OptimizeAllocs(mod, nil, 256, nil)
	})
}

type allocsTestOutput struct {
	filename string
	line     int
	msg      string
}

func (out allocsTestOutput) String() string {
	return out.filename + ":" + strconv.Itoa(out.line) + ": " + out.msg
}

// Test with a Go file as input (for more accurate tests).
func TestAllocs2(t *testing.T) {
	t.Parallel()

	mod := compileGoFileForTesting(t, "./testdata/allocs2.go")

	// Run functionattrs pass, which is necessary for escape analysis.
	po := llvm.NewPassBuilderOptions()
	defer po.Dispose()
	err := mod.RunPasses("function(instcombine),function-attrs", llvm.TargetMachine{}, po)
	if err != nil {
		t.Error("failed to run passes:", err)
	}

	// Run heap to stack transform.
	var testOutputs []allocsTestOutput
	transform.OptimizeAllocs(mod, regexp.MustCompile("."), 256, func(pos token.Position, msg string) {
		testOutputs = append(testOutputs, allocsTestOutput{
			filename: filepath.Base(pos.Filename),
			line:     pos.Line,
			msg:      msg,
		})
	})
	sort.Slice(testOutputs, func(i, j int) bool {
		return testOutputs[i].line < testOutputs[j].line
	})
	testOutput := ""
	for _, out := range testOutputs {
		testOutput += out.String() + "\n"
	}

	// Load expected test output (the OUT: lines).
	testInput, err := os.ReadFile("./testdata/allocs2.go")
	if err != nil {
		t.Fatal("could not read test input:", err)
	}
	var expectedTestOutput string
	for i, line := range strings.Split(strings.ReplaceAll(string(testInput), "\r\n", "\n"), "\n") {
		if idx := strings.Index(line, " // OUT: "); idx > 0 {
			msg := line[idx+len(" // OUT: "):]
			expectedTestOutput += "allocs2.go:" + strconv.Itoa(i+1) + ": " + msg + "\n"
		}
	}

	if testOutput != expectedTestOutput {
		t.Errorf("output does not match expected output:\n%s", testOutput)
	}
}


================================================
FILE: transform/errors.go
================================================
package transform

import (
	"go/scanner"
	"go/token"
	"path/filepath"

	"tinygo.org/x/go-llvm"
)

// errorAt returns an error value at the location of the value.
// The location information may not be complete as it depends on debug
// information in the IR.
func errorAt(val llvm.Value, msg string) scanner.Error {
	return scanner.Error{
		Pos: getPosition(val),
		Msg: msg,
	}
}

// getPosition returns the position information for the given value, as far as
// it is available.
func getPosition(val llvm.Value) token.Position {
	if !val.IsAInstruction().IsNil() {
		loc := val.InstructionDebugLoc()
		if loc.IsNil() {
			return token.Position{}
		}
		file := loc.LocationScope().ScopeFile()
		return token.Position{
			Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
			Line:     int(loc.LocationLine()),
			Column:   int(loc.LocationColumn()),
		}
	} else if !val.IsAFunction().IsNil() {
		loc := val.Subprogram()
		if loc.IsNil() {
			return token.Position{}
		}
		file := loc.ScopeFile()
		return token.Position{
			Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
			Line:     int(loc.SubprogramLine()),
		}
	} else {
		return token.Position{}
	}
}

// ErrMissingIntrinsic is an error indicating that a required intrinsic was not found in the module.
type ErrMissingIntrinsic struct {
	Name string
}

func (err ErrMissingIntrinsic) Error() string {
	return "missing intrinsic: " + err.Name
}


================================================
FILE: transform/gc.go
================================================
package transform

import (
	"tinygo.org/x/go-llvm"
)

// This is somewhat ugly to access through the API.
// https://github.com/llvm/llvm-project/blob/94ebcfd16dac67486bae624f74e1c5c789448bae/llvm/include/llvm/Support/ModRef.h#L62
// https://github.com/llvm/llvm-project/blob/94ebcfd16dac67486bae624f74e1c5c789448bae/llvm/include/llvm/Support/ModRef.h#L87
const shiftExcludeArgMem = 2

// MakeGCStackSlots converts all calls to runtime.trackPointer to explicit
// stores to stack slots that are scannable by the GC.
func MakeGCStackSlots(mod llvm.Module) bool {
	// Check whether there are allocations at all.
	alloc := mod.NamedFunction("runtime.alloc")
	if alloc.IsNil() {
		// Nothing to. Make sure all remaining bits and pieces for stack
		// chains are neutralized.
		for _, call := range getUses(mod.NamedFunction("runtime.trackPointer")) {
			call.EraseFromParentAsInstruction()
		}
		stackChainStart := mod.NamedGlobal("runtime.stackChainStart")
		if !stackChainStart.IsNil() {
			stackChainStart.SetLinkage(llvm.InternalLinkage)
			stackChainStart.SetInitializer(llvm.ConstNull(stackChainStart.GlobalValueType()))
			stackChainStart.SetGlobalConstant(true)
		}
		return false
	}

	trackPointer := mod.NamedFunction("runtime.trackPointer")
	if trackPointer.IsNil() || trackPointer.FirstUse().IsNil() {
		return false // nothing to do
	}

	ctx := mod.Context()
	builder := ctx.NewBuilder()
	defer builder.Dispose()
	targetData := llvm.NewTargetData(mod.DataLayout())
	defer targetData.Dispose()
	uintptrType := ctx.IntType(targetData.PointerSize() * 8)

	// All functions that call runtime.alloc needs stack objects.
	trackFuncs := map[llvm.Value]struct{}{}
	markParentFunctions(trackFuncs, alloc)

	// External functions may indirectly suspend the goroutine or perform a heap allocation.
	// Their callers should get stack objects.
	memAttr := llvm.AttributeKindID("memory")
	for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
		if _, ok := trackFuncs[fn]; ok {
			continue // already found
		}
		if !fn.FirstBasicBlock().IsNil() {
			// This is not an external function.
			continue
		}
		if fn == trackPointer {
			// Manually exclude trackPointer.
			continue
		}

		mem := fn.GetEnumFunctionAttribute(memAttr)
		if !mem.IsNil() && mem.GetEnumValue()>>shiftExcludeArgMem == 0 {
			// This does not access non-argument memory.
			// Exclude it.
			continue
		}

		// The callers need stack objects.
		markParentFunctions(trackFuncs, fn)
	}

	// Look at all other functions to see whether they contain function pointer
	// calls.
	// This takes less than 5ms for ~100kB of WebAssembly but would perhaps be
	// faster when written in C++ (to avoid the CGo overhead).
	for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
		if _, ok := trackFuncs[fn]; ok {
			continue // already found
		}

	scanBody:
		for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
			for call := bb.FirstInstruction(); !call.IsNil(); call = llvm.NextInstruction(call) {
				if call.IsACallInst().IsNil() {
					continue // only looking at calls
				}
				called := call.CalledValue()
				if !called.IsAFunction().IsNil() {
					continue // only looking for function pointers
				}
				trackFuncs[fn] = struct{}{}
				markParentFunctions(trackFuncs, fn)
				break scanBody
			}
		}
	}

	// Collect some variables used below in the loop.
	stackChainStart := mod.NamedGlobal("runtime.stackChainStart")
	if stackChainStart.IsNil() {
		// This may be reached in a weird scenario where we call runtime.alloc but the garbage collector is unreachable.
		// This can be accomplished by allocating 0 bytes.
		// There is no point in tracking anything.
		for _, use := range getUses(trackPointer) {
			use.EraseFromParentAsInstruction()
		}
		return false
	}
	stackChainStart.SetLinkage(llvm.InternalLinkage)
	stackChainStartType := stackChainStart.GlobalValueType()
	stackChainStart.SetInitializer(llvm.ConstNull(stackChainStartType))

	// Iterate until runtime.trackPointer has no uses left.
	for use := trackPointer.FirstUse(); !use.IsNil(); use = trackPointer.FirstUse() {
		// Pick the first use of runtime.trackPointer.
		call := use.User()
		if call.IsACallInst().IsNil() {
			panic("expected runtime.trackPointer use to be a call")
		}

		// Pick the parent function.
		fn := call.InstructionParent().Parent()

		if _, ok := trackFuncs[fn]; !ok {
			// This function nor any of the functions it calls (recursively)
			// allocate anything from the heap, so it will not trigger a garbage
			// collection cycle. Thus, it does not need to track local pointer
			// values.
			// This is a useful optimization but not as big as you might guess,
			// as described above (it avoids stack objects for ~12% of
			// functions).
			call.EraseFromParentAsInstruction()
			continue
		}

		// Find all calls to runtime.trackPointer in this function.
		var calls []llvm.Value
		var returns []llvm.Value
		for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
			for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
				switch inst.InstructionOpcode() {
				case llvm.Call:
					if inst.CalledValue() == trackPointer {
						calls = append(calls, inst)
					}
				case llvm.Ret:
					returns = append(returns, inst)
				}
			}
		}

		// Determine what to do with each call.
		var pointers []llvm.Value
		for _, call := range calls {
			ptr := call.Operand(0)
			call.EraseFromParentAsInstruction()

			// Some trivial optimizations.
			if ptr.IsAInstruction().IsNil() {
				continue
			}
			switch ptr.InstructionOpcode() {
			case llvm.GetElementPtr:
				// Check for all zero offsets.
				// Sometimes LLVM rewrites bitcasts to zero-index GEPs, and we still need to track the GEP.
				n := ptr.OperandsCount()
				var hasOffset bool
				for i := 1; i < n; i++ {
					offset := ptr.Operand(i)
					if offset.IsAConstantInt().IsNil() || offset.ZExtValue() != 0 {
						hasOffset = true
						break
					}
				}

				if hasOffset {
					// These values do not create new values: the values already
					// existed locally in this function so must have been tracked
					// already.
					continue
				}
			case llvm.PHI:
				// While the value may have already been tracked, it may be overwritten in a loop.
				// Therefore, a second copy must be created to ensure that it is tracked over the entirety of its lifetime.
			case llvm.ExtractValue, llvm.BitCast:
				// These instructions do not create new values, but their
				// original value may not be tracked. So keep tracking them for
				// now.
				// With more analysis, it should be possible to optimize a
				// significant chunk of these away.
			case llvm.Call, llvm.Load, llvm.IntToPtr:
				// These create new values so must be stored locally. But
				// perhaps some of these can be fused when they actually refer
				// to the same value.
			default:
				// Ambiguous. These instructions are uncommon, but perhaps could
				// be optimized if needed.
			}

			if ptr := stripPointerCasts(ptr); !ptr.IsAAllocaInst().IsNil() {
				// Allocas don't need to be tracked because they are allocated
				// on the C stack which is scanned separately.
				continue
			}
			pointers = append(pointers, ptr)
		}

		if len(pointers) == 0 {
			// This function does not need to keep track of stack pointers.
			continue
		}

		// Determine the type of the required stack slot.
		fields := []llvm.Type{
			stackChainStartType, // Pointer to parent frame.
			uintptrType,         // Number of elements in this frame.
		}
		for _, ptr := range pointers {
			fields = append(fields, ptr.Type())
		}
		stackObjectType := ctx.StructType(fields, false)

		// Create the stack object at the function entry.
		builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction())
		stackObject := builder.CreateAlloca(stackObjectType, "gc.stackobject")
		initialStackObject := llvm.ConstNull(stackObjectType)
		numSlots := (targetData.TypeAllocSize(stackObjectType) - uint64(targetData.PointerSize())*2) / uint64(targetData.ABITypeAlignment(uintptrType))
		numSlotsValue := llvm.ConstInt(uintptrType, numSlots, false)
		initialStackObject = builder.CreateInsertValue(initialStackObject, numSlotsValue, 1, "")
		builder.CreateStore(initialStackObject, stackObject)

		// Update stack start.
		parent := builder.CreateLoad(stackChainStartType, stackChainStart, "")
		gep := builder.CreateGEP(stackObjectType, stackObject, []llvm.Value{
			llvm.ConstInt(ctx.Int32Type(), 0, false),
			llvm.ConstInt(ctx.Int32Type(), 0, false),
		}, "")
		builder.CreateStore(parent, gep)
		builder.CreateStore(stackObject, stackChainStart)

		// Do a store to the stack object after each new pointer that is created.
		pointerStores := make(map[llvm.Value]struct{})
		for i, ptr := range pointers {
			// Insert the store after the pointer value is created.
			insertionPoint := llvm.NextInstruction(ptr)
			for !insertionPoint.IsAPHINode().IsNil() {
				// PHI nodes are required to be at the start of the block.
				// Insert after the last PHI node.
				insertionPoint = llvm.NextInstruction(insertionPoint)
			}
			builder.SetInsertPointBefore(insertionPoint)

			// Extract a pointer to the appropriate section of the stack object.
			gep := builder.CreateGEP(stackObjectType, stackObject, []llvm.Value{
				llvm.ConstInt(ctx.Int32Type(), 0, false),
				llvm.ConstInt(ctx.Int32Type(), uint64(2+i), false),
			}, "")

			// Store the pointer into the stack slot.
			store := builder.CreateStore(ptr, gep)
			pointerStores[store] = struct{}{}
		}

		// Make sure this stack object is popped from the linked list of stack
		// objects at return.
		for _, ret := range returns {
			// Check for any tail calls at this return.
			prev := llvm.PrevInstruction(ret)
			if !prev.IsNil() && !prev.IsABitCastInst().IsNil() {
				// A bitcast can appear before a tail call, so skip backwards more.
				prev = llvm.PrevInstruction(prev)
			}
			if !prev.IsNil() && !prev.IsACallInst().IsNil() {
				// This is no longer a tail call.
				prev.SetTailCall(false)
			}
			builder.SetInsertPointBefore(ret)
			builder.CreateStore(parent, stackChainStart)
		}
	}

	return true
}

// markParentFunctions traverses all parent function calls (recursively) and
// adds them to the set of marked functions. It only considers function calls:
// any other uses of such a function is ignored.
func markParentFunctions(marked map[llvm.Value]struct{}, fn llvm.Value) {
	worklist := []llvm.Value{fn}
	for len(worklist) != 0 {
		fn := worklist[len(worklist)-1]
		worklist = worklist[:len(worklist)-1]
		for _, use := range getUses(fn) {
			if use.IsACallInst().IsNil() || use.CalledValue() != fn {
				// Not the parent function.
				continue
			}
			parent := use.InstructionParent().Parent()
			if _, ok := marked[parent]; !ok {
				marked[parent] = struct{}{}
				worklist = append(worklist, parent)
			}
		}
	}
}


================================================
FILE: transform/gc_test.go
================================================
package transform_test

import (
	"testing"

	"github.com/tinygo-org/tinygo/transform"
	"tinygo.org/x/go-llvm"
)

func TestMakeGCStackSlots(t *testing.T) {
	t.Parallel()
	testTransform(t, "testdata/gc-stackslots", func(mod llvm.Module) {
		transform.MakeGCStackSlots(mod)
	})
}


================================================
FILE: transform/interface-lowering.go
================================================
package transform

// This file provides function to lower interface intrinsics to their final LLVM
// form, optimizing them in the process.
//
// During SSA construction, the following pseudo-call is created (see
// src/runtime/interface.go):
//     runtime.typeAssert(typecode, assertedType)
// Additionally, interface type asserts and interface invoke functions are
// declared but not defined, so the optimizer will leave them alone.
//
// This pass lowers these functions to their final form:
//
// typeAssert:
//     Replaced with an icmp instruction so it can be directly used in a type
//     switch.
//
// interface type assert:
//     These functions are defined by creating a big type switch over all the
//     concrete types implementing this interface.
//
// interface invoke:
//     These functions are defined with a similar type switch, but instead of
//     checking for the appropriate type, these functions will call the
//     underlying method instead.
//
// Note that this way of implementing interfaces is very different from how the
// main Go compiler implements them. For more details on how the main Go
// compiler does it: https://research.swtch.com/interfaces

import (
	"sort"
	"strings"

	"github.com/tinygo-org/tinygo/compileopts"
	"tinygo.org/x/go-llvm"
)

// signatureInfo is a Go signature of an interface method. It does not represent
// any method in particular.
type signatureInfo struct {
	name       string
	methods    []*methodInfo
	interfaces []*interfaceInfo
}

// methodInfo describes a single method on a concrete type.
type methodInfo struct {
	*signatureInfo
	function llvm.Value
}

// typeInfo describes a single concrete Go type, which can be a basic or a named
// type. If it is a named type, it may have methods.
type typeInfo struct {
	name        string
	typecode    llvm.Value
	typecodeGEP llvm.Value
	methodSet   llvm.Value
	methods     []*methodInfo
}

// getMethod looks up the method on this type with the given signature and
// returns it. The method must exist on this type, otherwise getMethod will
// panic.
func (t *typeInfo) getMethod(signature *signatureInfo) *methodInfo {
	for _, method := range t.methods {
		if method.signatureInfo == signature {
			return method
		}
	}
	panic("could not find method")
}

// interfaceInfo keeps information about a Go interface type, including all
// methods it has.
type interfaceInfo struct {
	name       string                    // "tinygo-methods" attribute
	signatures map[string]*signatureInfo // method set
	types      []*typeInfo               // types this interface implements
}

// lowerInterfacesPass keeps state related to the interface lowering pass. The
// pass has been implemented as an object type because of its complexity, but
// should be seen as a regular function call (see LowerInterfaces).
type lowerInterfacesPass struct {
	mod         llvm.Module
	config      *compileopts.Config
	builder     llvm.Builder
	dibuilder   *llvm.DIBuilder
	difiles     map[string]llvm.Metadata
	ctx         llvm.Context
	uintptrType llvm.Type
	targetData  llvm.TargetData
	ptrType     llvm.Type
	types       map[string]*typeInfo
	signatures  map[string]*signatureInfo
	interfaces  map[string]*interfaceInfo
}

// LowerInterfaces lowers all intermediate interface calls and globals that are
// emitted by the compiler as higher-level intrinsics. They need some lowering
// before LLVM can work on them. This is done so that a few cleanup passes can
// run before assigning the final type codes.
func LowerInterfaces(mod llvm.Module, config *compileopts.Config) error {
	ctx := mod.Context()
	targetData := llvm.NewTargetData(mod.DataLayout())
	defer targetData.Dispose()
	p := &lowerInterfacesPass{
		mod:         mod,
		config:      config,
		builder:     ctx.NewBuilder(),
		ctx:         ctx,
		targetData:  targetData,
		uintptrType: mod.Context().IntType(targetData.PointerSize() * 8),
		ptrType:     llvm.PointerType(ctx.Int8Type(), 0),
		types:       make(map[string]*typeInfo),
		signatures:  make(map[string]*signatureInfo),
		interfaces:  make(map[string]*interfaceInfo),
	}
	defer p.builder.Dispose()

	if config.Debug() {
		p.dibuilder = llvm.NewDIBuilder(mod)
		defer p.dibuilder.Destroy()
		defer p.dibuilder.Finalize()
		p.difiles = make(map[string]llvm.Metadata)
	}

	return p.run()
}

// run runs the pass itself.
func (p *lowerInterfacesPass) run() error {
	if p.dibuilder != nil {
		p.dibuilder.CreateCompileUnit(llvm.DICompileUnit{
			Language:  0xb, // DW_LANG_C99 (0xc, off-by-one?)
			File:      "",
			Dir:       "",
			Producer:  "TinyGo",
			Optimized: true,
		})
	}

	// Collect all type codes.
	for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
		if strings.HasPrefix(global.Name(), "reflect/types.type:") {
			// Retrieve Go type information based on an opaque global variable.
			// Only the name of the global is relevant, the object itself is
			// discarded afterwards.
			name := strings.TrimPrefix(global.Name(), "reflect/types.type:")
			if _, ok := p.types[name]; !ok {
				t := &typeInfo{
					name:     name,
					typecode: global,
				}
				p.types[name] = t
				initializer := global.Initializer()
				firstField := p.builder.CreateExtractValue(initializer, 0, "")
				if firstField.Type() != p.ctx.Int8Type() {
					// This type has a method set at index 0. Change the GEP to
					// point to index 1 (the meta byte).
					t.typecodeGEP = llvm.ConstGEP(global.GlobalValueType(), global, []llvm.Value{
						llvm.ConstInt(p.ctx.Int32Type(), 0, false),
						llvm.ConstInt(p.ctx.Int32Type(), 1, false),
					})
					methodSet := stripPointerCasts(firstField)
					if !strings.HasSuffix(methodSet.Name(), "$methodset") {
						panic("expected method set")
					}
					p.addTypeMethods(t, methodSet)
				} else {
					// This type has no method set.
					t.typecodeGEP = llvm.ConstGEP(global.GlobalValueType(), global, []llvm.Value{
						llvm.ConstInt(p.ctx.Int32Type(), 0, false),
						llvm.ConstInt(p.ctx.Int32Type(), 0, false),
					})
				}
			}
		}
	}

	// Find all interface type asserts and interface method thunks.
	var interfaceAssertFunctions []llvm.Value
	var interfaceInvokeFunctions []llvm.Value
	for fn := p.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
		methodsAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-methods")
		if methodsAttr.IsNil() {
			continue
		}
		if !hasUses(fn) {
			// Don't bother defining this function.
			continue
		}
		p.addInterface(methodsAttr.GetStringValue())
		invokeAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-invoke")
		if invokeAttr.IsNil() {
			// Type assert.
			interfaceAssertFunctions = append(interfaceAssertFunctions, fn)
		} else {
			// Interface invoke.
			interfaceInvokeFunctions = append(interfaceInvokeFunctions, fn)
		}
	}

	// Find all the interfaces that are implemented per type.
	for _, t := range p.types {
		// This type has no methods, so don't spend time calculating them.
		if len(t.methods) == 0 {
			continue
		}

		// Pre-calculate a set of signatures that this type has, for easy
		// lookup/check.
		typeSignatureSet := make(map[*signatureInfo]struct{})
		for _, method := range t.methods {
			typeSignatureSet[method.signatureInfo] = struct{}{}
		}

		// A set of interfaces, mapped from the name to the info.
		// When the name maps to a nil pointer, one of the methods of this type
		// exists in the given interface but not all of them so this type
		// doesn't implement the interface.
		satisfiesInterfaces := make(map[string]*interfaceInfo)

		for _, method := range t.methods {
			for _, itf := range method.interfaces {
				if _, ok := satisfiesInterfaces[itf.name]; ok {
					// interface already checked with a different method
					continue
				}
				// check whether this interface satisfies this type
				satisfies := true
				for _, itfSignature := range itf.signatures {
					if _, ok := typeSignatureSet[itfSignature]; !ok {
						satisfiesInterfaces[itf.name] = nil // does not satisfy
						satisfies = false
						break
					}
				}
				if !satisfies {
					continue
				}
				satisfiesInterfaces[itf.name] = itf
			}
		}

		// Add this type to all interfaces that satisfy this type.
		for _, itf := range satisfiesInterfaces {
			if itf == nil {
				// Interface does not implement this type, but one of the
				// methods on this type also exists on the interface.
				continue
			}
			itf.types = append(itf.types, t)
		}
	}

	// Sort all types added to the interfaces.
	for _, itf := range p.interfaces {
		sort.Slice(itf.types, func(i, j int) bool {
			return itf.types[i].name > itf.types[j].name
		})
	}

	// Define all interface invoke thunks.
	for _, fn := range interfaceInvokeFunctions {
		methodsAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-methods")
		invokeAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-invoke")
		itf := p.interfaces[methodsAttr.GetStringValue()]
		signature := itf.signatures[invokeAttr.GetStringValue()]
		p.defineInterfaceMethodFunc(fn, itf, signature)
	}

	// Define all interface type assert functions.
	for _, fn := range interfaceAssertFunctions {
		methodsAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-methods")
		itf := p.interfaces[methodsAttr.GetStringValue()]
		p.defineInterfaceImplementsFunc(fn, itf)
	}

	// Replace each type assert with an actual type comparison or (if the type
	// assert is impossible) the constant false.
	llvmFalse := llvm.ConstInt(p.ctx.Int1Type(), 0, false)
	for _, use := range getUses(p.mod.NamedFunction("runtime.typeAssert")) {
		actualType := use.Operand(0)
		name := strings.TrimPrefix(use.Operand(1).Name(), "reflect/types.typeid:")
		gepOffset := uint64(0)
		for strings.HasPrefix(name, "pointer:pointer:") {
			// This is a type like **int, which has the name pointer:pointer:int
			// but is encoded using pointer tagging.
			// Calculate the pointer tag, which is emitted as a GEP instruction.
			name = name[len("pointer:"):]
			gepOffset++
		}

		if t, ok := p.types[name]; ok {
			// The type exists in the program, so lower to a regular pointer
			// comparison.
			p.builder.SetInsertPointBefore(use)
			typecodeGEP := t.typecodeGEP
			if gepOffset != 0 {
				// This is a tagged pointer.
				typecodeGEP = llvm.ConstInBoundsGEP(p.ctx.Int8Type(), typecodeGEP, []llvm.Value{
					llvm.ConstInt(p.ctx.Int64Type(), gepOffset, false),
				})
			}
			commaOk := p.builder.CreateICmp(llvm.IntEQ, typecodeGEP, actualType, "typeassert.ok")
			use.ReplaceAllUsesWith(commaOk)
		} else {
			// The type does not exist in the program, so lower to a constant
			// false. This is trivially further optimized.
			// TODO: eventually it'll be necessary to handle reflect.PtrTo and
			// reflect.New calls which create new types not present in the
			// original program.
			use.ReplaceAllUsesWith(llvmFalse)
		}
		use.EraseFromParentAsInstruction()
	}

	// Create a sorted list of type names, for predictable iteration.
	var typeNames []string
	for name := range p.types {
		typeNames = append(typeNames, name)
	}
	sort.Strings(typeNames)

	// Remove all method sets, which are now unnecessary and inhibit later
	// optimizations if they are left in place.
	zero := llvm.ConstInt(p.ctx.Int32Type(), 0, false)
	for _, name := range typeNames {
		t := p.types[name]
		if !t.methodSet.IsNil() {
			initializer := t.typecode.Initializer()
			var newInitializerFields []llvm.Value
			for i := 1; i < initializer.Type().StructElementTypesCount(); i++ {
				newInitializerFields = append(newInitializerFields, p.builder.CreateExtractValue(initializer, i, ""))
			}
			newInitializer := p.ctx.ConstStruct(newInitializerFields, false)
			typecodeName := t.typecode.Name()
			newGlobal := llvm.AddGlobal(p.mod, newInitializer.Type(), typecodeName+".tmp")
			newGlobal.SetInitializer(newInitializer)
			newGlobal.SetLinkage(t.typecode.Linkage())
			newGlobal.SetGlobalConstant(true)
			newGlobal.SetAlignment(t.typecode.Alignment())
			for _, use := range getUses(t.typecode) {
				if !use.IsAConstantExpr().IsNil() {
					opcode := use.Opcode()
					if opcode == llvm.GetElementPtr && use.OperandsCount() == 3 {
						if use.Operand(1).ZExtValue() == 0 && use.Operand(2).ZExtValue() == 1 {
							gep := p.builder.CreateInBoundsGEP(newGlobal.GlobalValueType(), newGlobal, []llvm.Value{zero, zero}, "")
							use.ReplaceAllUsesWith(gep)
						}
					}
				}
			}
			// Fallback.
			if hasUses(t.typecode) {
				negativeOffset := -int64(p.targetData.TypeAllocSize(p.ptrType))
				gep := p.builder.CreateInBoundsGEP(p.ctx.Int8Type(), newGlobal, []llvm.Value{llvm.ConstInt(p.ctx.Int32Type(), uint64(negativeOffset), true)}, "")
				t.typecode.ReplaceAllUsesWith(gep)
			}
			t.typecode.EraseFromParentAsGlobal()
			newGlobal.SetName(typecodeName)
			t.typecode = newGlobal
		}
	}

	return nil
}

// addTypeMethods reads the method set of the given type info struct. It
// retrieves the signatures and the references to the method functions
// themselves for later type<->interface matching.
func (p *lowerInterfacesPass) addTypeMethods(t *typeInfo, methodSet llvm.Value) {
	if !t.methodSet.IsNil() {
		// no methods or methods already read
		return
	}

	// This type has methods, collect all methods of this type.
	t.methodSet = methodSet
	set := methodSet.Initializer() // get value from global
	signatures := p.builder.CreateExtractValue(set, 1, "")
	wrappers := p.builder.CreateExtractValue(set, 2, "")
	numMethods := signatures.Type().ArrayLength()
	for i := 0; i < numMethods; i++ {
		signatureGlobal := p.builder.CreateExtractValue(signatures, i, "")
		function := p.builder.CreateExtractValue(wrappers, i, "")
		function = stripPointerCasts(function) // strip bitcasts
		signatureName := signatureGlobal.Name()
		signature := p.getSignature(signatureName)
		method := &methodInfo{
			function:      function,
			signatureInfo: signature,
		}
		signature.methods = append(signature.methods, method)
		t.methods = append(t.methods, method)
	}
}

// addInterface reads information about an interface, which is the
// fully-qualified name and the signatures of all methods it has.
func (p *lowerInterfacesPass) addInterface(methodsString string) {
	if _, ok := p.interfaces[methodsString]; ok {
		return
	}
	t := &interfaceInfo{
		name:       methodsString,
		signatures: make(map[string]*signatureInfo),
	}
	p.interfaces[methodsString] = t
	for _, method := range strings.Split(methodsString, "; ") {
		signature := p.getSignature(method)
		signature.interfaces = append(signature.interfaces, t)
		t.signatures[method] = signature
	}
}

// getSignature returns a new *signatureInfo, creating it if it doesn't already
// exist.
func (p *lowerInterfacesPass) getSignature(name string) *signatureInfo {
	if _, ok := p.signatures[name]; !ok {
		p.signatures[name] = &signatureInfo{
			name: name,
		}
	}
	return p.signatures[name]
}

// defineInterfaceImplementsFunc defines the interface type assert function. It
// checks whether the given interface type (passed as an argument) is one of the
// types it implements.
//
// The type match is implemented using an if/else chain over all possible types.
// This if/else chain is easily converted to a big switch over all possible
// types by the LLVM simplifycfg pass.
func (p *lowerInterfacesPass) defineInterfaceImplementsFunc(fn llvm.Value, itf *interfaceInfo) {
	// Create the function and function signature.
	fn.Param(0).SetName("actualType")
	fn.SetLinkage(llvm.InternalLinkage)
	fn.SetUnnamedAddr(true)
	AddStandardAttributes(fn, p.config)

	// Start the if/else chain at the entry block.
	entry := p.ctx.AddBasicBlock(fn, "entry")
	thenBlock := p.ctx.AddBasicBlock(fn, "then")
	p.builder.SetInsertPointAtEnd(entry)

	if p.dibuilder != nil {
		difile := p.getDIFile("")
		diFuncType := p.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
			File: difile,
		})
		difunc := p.dibuilder.CreateFunction(difile, llvm.DIFunction{
			Name:         "(Go interface assert)",
			File:         difile,
			Line:         0,
			Type:         diFuncType,
			LocalToUnit:  true,
			IsDefinition: true,
			ScopeLine:    0,
			Flags:        llvm.FlagPrototyped,
			Optimized:    true,
		})
		fn.SetSubprogram(difunc)
		p.builder.SetCurrentDebugLocation(0, 0, difunc, llvm.Metadata{})
	}

	// Iterate over all possible types.  Each iteration creates a new branch
	// either to the 'then' block (success) or the .next block, for the next
	// check.
	actualType := fn.Param(0)
	for _, typ := range itf.types {
		nextBlock := p.ctx.AddBasicBlock(fn, typ.name+".next")
		cmp := p.builder.CreateICmp(llvm.IntEQ, actualType, typ.typecodeGEP, typ.name+".icmp")
		p.builder.CreateCondBr(cmp, thenBlock, nextBlock)
		p.builder.SetInsertPointAtEnd(nextBlock)
	}

	// The builder is now inserting at the last *.next block.  Once we reach
	// this point, all types have been checked so the type assert will have
	// failed.
	p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 0, false))

	// Fill 'then' block (type assert was successful).
	p.builder.SetInsertPointAtEnd(thenBlock)
	p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 1, false))
}

// defineInterfaceMethodFunc defines this thunk by calling the concrete method
// of the type that implements this interface.
//
// Matching the actual type is implemented using an if/else chain over all
// possible types.  This is later converted to a switch statement by the LLVM
// simplifycfg pass.
func (p *lowerInterfacesPass) defineInterfaceMethodFunc(fn llvm.Value, itf *interfaceInfo, signature *signatureInfo) {
	context := fn.LastParam()
	actualType := llvm.PrevParam(context)
	returnType := fn.GlobalValueType().ReturnType()
	context.SetName("context")
	actualType.SetName("actualType")
	fn.SetLinkage(llvm.InternalLinkage)
	fn.SetUnnamedAddr(true)
	AddStandardAttributes(fn, p.config)

	// Collect the params that will be passed to the functions to call.
	// These params exclude the receiver (which may actually consist of multiple
	// parts).
	params := make([]llvm.Value, fn.ParamsCount()-3)
	for i := range params {
		params[i] = fn.Param(i + 1)
	}
	params = append(params,
		llvm.Undef(p.ptrType),
	)

	// Start chain in the entry block.
	entry := p.ctx.AddBasicBlock(fn, "entry")
	p.builder.SetInsertPointAtEnd(entry)

	if p.dibuilder != nil {
		difile := p.getDIFile("")
		diFuncType := p.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
			File: difile,
		})
		difunc := p.dibuilder.CreateFunction(difile, llvm.DIFunction{
			Name:         "(Go interface method)",
			File:         difile,
			Line:         0,
			Type:         diFuncType,
			LocalToUnit:  true,
			IsDefinition: true,
			ScopeLine:    0,
			Flags:        llvm.FlagPrototyped,
			Optimized:    true,
		})
		fn.SetSubprogram(difunc)
		p.builder.SetCurrentDebugLocation(0, 0, difunc, llvm.Metadata{})
	}

	// Define all possible functions that can be called.
	for _, typ := range itf.types {
		// Create type check (if/else).
		bb := p.ctx.AddBasicBlock(fn, typ.name)
		next := p.ctx.AddBasicBlock(fn, typ.name+".next")
		cmp := p.builder.CreateICmp(llvm.IntEQ, actualType, typ.typecodeGEP, typ.name+".icmp")
		p.builder.CreateCondBr(cmp, bb, next)

		// The function we will redirect to when the interface has this type.
		function := typ.getMethod(signature).function

		p.builder.SetInsertPointAtEnd(bb)
		receiver := fn.FirstParam()

		paramTypes := []llvm.Type{receiver.Type()}
		for _, param := range params {
			paramTypes = append(paramTypes, param.Type())
		}
		functionType := llvm.FunctionType(returnType, paramTypes, false)
		retval := p.builder.CreateCall(functionType, function, append([]llvm.Value{receiver}, params...), "")
		if retval.Type().TypeKind() == llvm.VoidTypeKind {
			p.builder.CreateRetVoid()
		} else {
			p.builder.CreateRet(retval)
		}

		// Start next comparison in the 'next' block (which is jumped to when
		// the type doesn't match).
		p.builder.SetInsertPointAtEnd(next)
	}

	// The builder now points to the last *.then block, after all types have
	// been checked. Call runtime.nilPanic here.
	// The only other possible value remaining is nil for nil interfaces. We
	// could panic with a different message here such as "nil interface" but
	// that would increase code size and "nil panic" is close enough. Most
	// importantly, it avoids undefined behavior when accidentally calling a
	// method on a nil interface.
	nilPanic := p.mod.NamedFunction("runtime.nilPanic")
	p.builder.CreateCall(nilPanic.GlobalValueType(), nilPanic, []llvm.Value{
		llvm.Undef(p.ptrType),
	}, "")
	p.builder.CreateUnreachable()
}

func (p *lowerInterfacesPass) getDIFile(file string) llvm.Metadata {
	difile, ok := p.difiles[file]
	if !ok {
		difile = p.dibuilder.CreateFile(file, "")
		p.difiles[file] = difile
	}
	return difile
}


================================================
FILE: transform/interface-lowering_test.go
================================================
package transform_test

import (
	"testing"

	"github.com/tinygo-org/tinygo/transform"
	"tinygo.org/x/go-llvm"
)

func TestInterfaceLowering(t *testing.T) {
	t.Parallel()
	testTransform(t, "testdata/interface", func(mod llvm.Module) {
		err := transform.LowerInterfaces(mod, defaultTestConfig)
		if err != nil {
			t.Error(err)
		}

		po := llvm.NewPassBuilderOptions()
		defer po.Dispose()
		err = mod.RunPasses("globaldce", llvm.TargetMachine{}, po)
		if err != nil {
			t.Error("failed to run passes:", err)
		}
	})
}


================================================
FILE: transform/interrupt.go
================================================
package transform

import (
	"fmt"
	"strings"

	"tinygo.org/x/go-llvm"
)

// LowerInterrupts creates interrupt handlers for the interrupts created by
// runtime/interrupt.New.
//
// The operation is as follows. The compiler creates the following during IR
// generation:
//   - calls to runtime/interrupt.callHandlers with an interrupt number.
//   - runtime/interrupt.handle objects that store the (constant) interrupt ID and
//     interrupt handler func value.
//
// This pass then replaces those callHandlers calls with calls to the actual
// interrupt handlers. If there are no interrupt handlers for the given call,
// the interrupt handler is removed. For hardware vectoring, that means that the
// entire function is removed. For software vectoring, that means that the call
// is replaced with an 'unreachable' instruction.
// This might seem like it causes extra overhead, but in fact inlining and const
// propagation will eliminate most if not all of that.
func LowerInterrupts(mod llvm.Module) []error {
	var errs []error

	ctx := mod.Context()
	builder := ctx.NewBuilder()
	defer builder.Dispose()

	// Collect a map of interrupt handle objects. The fact that they still
	// exist in the IR indicates that they could not be optimized away,
	// therefore we need to make real interrupt handlers for them.
	handleMap := map[int64][]llvm.Value{}
	handleType := mod.GetTypeByName("runtime/interrupt.handle")
	if !handleType.IsNil() {
		for global := mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
			if global.GlobalValueType() != handleType {
				continue
			}

			// Get the interrupt number from the initializer
			initializer := global.Initializer()
			interrupt := builder.CreateExtractValue(initializer, 2, "")
			num := builder.CreateExtractValue(interrupt, 0, "").SExtValue()
			pkg := packageFromInterruptHandle(global)

			handles, exists := handleMap[num]

			// If there is an existing interrupt handler, ensure it is in the same package
			// as the new one.  This is to prevent any assumptions in code that a single
			// compiler pass can see all packages to chain interrupt handlers. When packages are
			// compiled to separate object files, the linker should spot the duplicate symbols
			// for the wrapper function, failing the build.
			if exists && packageFromInterruptHandle(handles[0]) != pkg {
				errs = append(errs, errorAt(global,
					fmt.Sprintf("handlers for interrupt %d in multiple packages: %s and %s",
						num, pkg, packageFromInterruptHandle(handles[0]))))
				continue
			}

			handleMap[num] = append(handles, global)
		}
	}

	// Discover interrupts. The runtime/interrupt.callHandlers call is a
	// compiler intrinsic that is replaced with the handlers for the given
	// function.
	for _, call := range getUses(mod.NamedFunction("runtime/interrupt.callHandlers")) {
		if call.IsACallInst().IsNil() {
			errs = append(errs, errorAt(call, "expected a call to runtime/interrupt.callHandlers?"))
			continue
		}

		num := call.Operand(0)
		if num.IsAConstantInt().IsNil() {
			errs = append(errs, errorAt(call, "non-constant interrupt number?"))
			call.InstructionParent().Parent().Dump()
			continue
		}

		handlers := handleMap[num.SExtValue()]
		if len(handlers) != 0 {
			// This interrupt has at least one handler.
			// Replace the callHandlers call with (possibly multiple) calls to
			// these handlers.
			builder.SetInsertPointBefore(call)
			for _, handler := range handlers {
				initializer := handler.Initializer()
				context := builder.CreateExtractValue(initializer, 0, "")
				funcPtr := builder.CreateExtractValue(initializer, 1, "").Operand(0)
				builder.CreateCall(funcPtr.GlobalValueType(), funcPtr, []llvm.Value{
					num,
					context,
				}, "")
			}
			call.EraseFromParentAsInstruction()
		} else {
			// No handlers. Remove the call.
			fn := call.InstructionParent().Parent()
			if fn.Linkage() == llvm.ExternalLinkage {
				// Hardware vectoring. Remove the function entirely (redirecting
				// it to the default handler).
				fn.ReplaceAllUsesWith(llvm.Undef(fn.Type()))
				fn.EraseFromParentAsFunction()
			} else {
				// Software vectoring. Erase the instruction and replace it with
				// 'unreachable'.
				builder.SetInsertPointBefore(call)
				builder.CreateUnreachable()
				// Erase all instructions that follow the unreachable
				// instruction (which is a block terminator).
				inst := call
				for !inst.IsNil() {
					next := llvm.NextInstruction(inst)
					inst.EraseFromParentAsInstruction()
					inst = next
				}
			}
		}
	}

	// Replace all ptrtoint uses of the interrupt handler globals with the real
	// interrupt ID.
	// This can now be safely done after interrupts have been lowered, doing it
	// earlier may result in this interrupt handler being optimized away
	// entirely (which is not what we want).
	for num, handlers := range handleMap {
		for _, handler := range handlers {
			for _, user := range getUses(handler) {
				if user.IsAConstantExpr().IsNil() || user.Opcode() != llvm.PtrToInt {
					errs = append(errs, errorAt(handler, "internal error: expected a ptrtoint"))
					continue
				}
				user.ReplaceAllUsesWith(llvm.ConstInt(user.Type(), uint64(num), true))
			}

			// The runtime/interrupt.handle struct can finally be removed.
			// It would probably be eliminated anyway by a globaldce pass but it's
			// better to do it now to be sure.
			handler.EraseFromParentAsGlobal()
		}
	}

	// Remove now-useless runtime/interrupt.use calls. These are used for some
	// platforms like AVR that do not need to enable interrupts to use them, so
	// need another way to keep them alive.
	// After interrupts have been lowered, this call is useless and would cause
	// a linker error so must be removed.
	for _, call := range getUses(mod.NamedFunction("runtime/interrupt.use")) {
		if call.IsACallInst().IsNil() {
			errs = append(errs, errorAt(call, "internal error: expected call to runtime/interrupt.use"))
			continue
		}

		call.EraseFromParentAsInstruction()
	}

	return errs
}

func packageFromInterruptHandle(handle llvm.Value) string {
	return strings.Split(handle.Name(), "$")[0]
}


================================================
FILE: transform/interrupt_test.go
================================================
package transform_test

import (
	"testing"

	"github.com/tinygo-org/tinygo/transform"
	"tinygo.org/x/go-llvm"
)

func TestInterruptLowering(t *testing.T) {
	t.Parallel()
	testTransform(t, "testdata/interrupt", func(mod llvm.Module) {
		errs := transform.LowerInterrupts(mod)
		if len(errs) != 0 {
			t.Fail()
			for _, err := range errs {
				t.Error(err)
			}
		}
	})
}


================================================
FILE: transform/llvm.go
================================================
package transform

import (
	"reflect"

	"tinygo.org/x/go-llvm"
)

// Return a list of values (actually, instructions) where this value is used as
// an operand.
func getUses(value llvm.Value) []llvm.Value {
	if value.IsNil() {
		return nil
	}
	var uses []llvm.Value
	use := value.FirstUse()
	for !use.IsNil() {
		uses = append(uses, use.User())
		use = use.NextUse()
	}
	return uses
}

// hasUses returns whether the given value has any uses. It is equivalent to
// getUses(value) != nil but faster.
func hasUses(value llvm.Value) bool {
	if value.IsNil() {
		return false
	}
	return !value.FirstUse().IsNil()
}

// makeGlobalArray creates a new LLVM global with the given name and integers as
// contents, and returns the global and initializer type.
// Note that it is left with the default linkage etc., you should set
// linkage/constant/etc properties yourself.
func makeGlobalArray(mod llvm.Module, bufItf interface{}, name string, elementType llvm.Type) (llvm.Type, llvm.Value) {
	buf := reflect.ValueOf(bufItf)
	var values []llvm.Value
	for i := 0; i < buf.Len(); i++ {
		ch := buf.Index(i).Uint()
		values = append(values, llvm.ConstInt(elementType, ch, false))
	}
	value := llvm.ConstArray(elementType, values)
	global := llvm.AddGlobal(mod, value.Type(), name)
	global.SetInitializer(value)
	return value.Type(), global
}

// getGlobalBytes returns the slice contained in the array of the provided
// global. It can recover the bytes originally created using makeGlobalArray, if
// makeGlobalArray was given a byte slice.
//
// The builder parameter is only used for constant operations.
func getGlobalBytes(global llvm.Value, builder llvm.Builder) []byte {
	value := global.Initializer()
	buf := make([]byte, value.Type().ArrayLength())
	for i := range buf {
		buf[i] = byte(builder.CreateExtractValue(value, i, "").ZExtValue())
	}
	return buf
}

// replaceGlobalByteWithArray replaces a global integer type in the module with
// an integer array, using a GEP to make the types match. It is a convenience
// function used for creating reflection sidetables, for example.
func replaceGlobalIntWithArray(mod llvm.Module, name string, buf interface{}) llvm.Value {
	oldGlobal := mod.NamedGlobal(name)
	globalType, global := makeGlobalArray(mod, buf, name+".tmp", oldGlobal.GlobalValueType())
	gep := llvm.ConstGEP(globalType, global, []llvm.Value{
		llvm.ConstInt(mod.Context().Int32Type(), 0, false),
		llvm.ConstInt(mod.Context().Int32Type(), 0, false),
	})
	oldGlobal.ReplaceAllUsesWith(gep)
	oldGlobal.EraseFromParentAsGlobal()
	global.SetName(name)
	return global
}

// stripPointerCasts strips instruction pointer casts (getelementptr and
// bitcast) and returns the original value without the casts.
func stripPointerCasts(value llvm.Value) llvm.Value {
	if !value.IsAConstantExpr().IsNil() {
		switch value.Opcode() {
		case llvm.GetElementPtr, llvm.BitCast:
			return stripPointerCasts(value.Operand(0))
		}
	}
	if !value.IsAInstruction().IsNil() {
		switch value.InstructionOpcode() {
		case llvm.GetElementPtr, llvm.BitCast:
			return stripPointerCasts(value.Operand(0))
		}
	}
	return value
}


================================================
FILE: transform/maps.go
================================================
package transform

import (
	"tinygo.org/x/go-llvm"
)

// OptimizeMaps eliminates created but unused maps.
//
// In the future, this should statically allocate created but never modified
// maps. This has not yet been implemented, however.
func OptimizeMaps(mod llvm.Module) {
	hashmapMake := mod.NamedFunction("runtime.hashmapMake")
	if hashmapMake.IsNil() {
		// nothing to optimize
		return
	}

	hashmapBinarySet := mod.NamedFunction("runtime.hashmapBinarySet")
	hashmapStringSet := mod.NamedFunction("runtime.hashmapStringSet")

	for _, makeInst := range getUses(hashmapMake) {
		updateInsts := []llvm.Value{}
		unknownUses := false // are there any uses other than setting a value?

		for _, use := range getUses(makeInst) {
			if use := use.IsACallInst(); !use.IsNil() {
				switch use.CalledValue() {
				case hashmapBinarySet, hashmapStringSet:
					updateInsts = append(updateInsts, use)
				default:
					unknownUses = true
				}
			} else {
				unknownUses = true
			}
		}

		if !unknownUses {
			// This map can be entirely removed, as it is only created but never
			// used.
			for _, inst := range updateInsts {
				inst.EraseFromParentAsInstruction()
			}
			makeInst.EraseFromParentAsInstruction()
		}
	}
}


================================================
FILE: transform/maps_test.go
================================================
package transform_test

import (
	"testing"

	"github.com/tinygo-org/tinygo/transform"
	"tinygo.org/x/go-llvm"
)

func TestOptimizeMaps(t *testing.T) {
	t.Parallel()
	testTransform(t, "testdata/maps", func(mod llvm.Module) {
		// Run optimization pass.
		transform.OptimizeMaps(mod)

		// Run an optimization pass, to clean up the result.
		// This shows that all code related to the map is really eliminated.
		po := llvm.NewPassBuilderOptions()
		defer po.Dispose()
		err := mod.RunPasses("dse,adce", llvm.TargetMachine{}, po)
		if err != nil {
			t.Error("failed to run passes:", err)
		}
	})
}


================================================
FILE: transform/optimizer.go
================================================
package transform

import (
	"errors"
	"fmt"
	"go/token"
	"os"

	"github.com/tinygo-org/tinygo/compileopts"
	"github.com/tinygo-org/tinygo/compiler/ircheck"
	"github.com/tinygo-org/tinygo/compiler/llvmutil"
	"tinygo.org/x/go-llvm"
)

// OptimizePackage runs optimization passes over the LLVM module for the given
// Go package.
func OptimizePackage(mod llvm.Module, config *compileopts.Config) {
	_, speedLevel, _ := config.OptLevel()

	// Run TinyGo-specific optimization passes.
	if speedLevel > 0 {
		OptimizeMaps(mod)
	}
}

// Optimize runs a number of optimization and transformation passes over the
// given module. Some passes are specific to TinyGo, others are generic LLVM
// passes.
//
// Please note that some optimizations are not optional, thus Optimize must
// always be run before emitting machine code.
func Optimize(mod llvm.Module, config *compileopts.Config) []error {
	optLevel, speedLevel, _ := config.OptLevel()

	// Make sure these functions are kept in tact during TinyGo transformation passes.
	for _, name := range functionsUsedInTransforms {
		fn := mod.NamedFunction(name)
		if fn.IsNil() {
			panic(fmt.Errorf("missing core function %q", name))
		}
		fn.SetLinkage(llvm.ExternalLinkage)
	}

	// run a check of all of our code
	if config.VerifyIR() {
		errs := ircheck.Module(mod)
		if errs != nil {
			return errs
		}
	}

	if speedLevel > 0 {
		// Run some preparatory passes for the Go optimizer.
		po := llvm.NewPassBuilderOptions()
		defer po.Dispose()
		optPasses := "globaldce,globalopt,ipsccp,instcombine,adce,function-attrs"
		if llvmutil.Version() < 18 {
			// LLVM 17 doesn't have the no-verify-fixpoint flag.
			optPasses = "globaldce,globalopt,ipsccp,instcombine,adce,function-attrs"
		}
		err := mod.RunPasses(optPasses, llvm.TargetMachine{}, po)
		if err != nil {
			return []error{fmt.Errorf("could not build pass pipeline: %w", err)}
		}

		// Run TinyGo-specific optimization passes.
		OptimizeStringToBytes(mod)
		OptimizeReflectImplements(mod)
		maxStackSize := config.MaxStackAlloc()
		OptimizeAllocs(mod, nil, maxStackSize, nil)
		err = LowerInterfaces(mod, config)
		if err != nil {
			return []error{err}
		}

		errs := LowerInterrupts(mod)
		if len(errs) > 0 {
			return errs
		}

		// After interfaces are lowered, there are many more opportunities for
		// interprocedural optimizations. To get them to work, function
		// attributes have to be updated first.
		err = mod.RunPasses(optPasses, llvm.TargetMachine{}, po)
		if err != nil {
			return []error{fmt.Errorf("could not build pass pipeline: %w", err)}
		}

		// Run TinyGo-specific interprocedural optimizations.
		OptimizeAllocs(mod, config.Options.PrintAllocs, maxStackSize, func(pos token.Position, msg string) {
			fmt.Fprintln(os.Stderr, pos.String()+": "+msg)
		})
		OptimizeStringToBytes(mod)
		OptimizeStringEqual(mod)

	} else {
		// Must be run at any optimization level.
		err := LowerInterfaces(mod, config)
		if err != nil {
			return []error{err}
		}
		errs := LowerInterrupts(mod)
		if len(errs) > 0 {
			return errs
		}

		// Clean up some leftover symbols of the previous transformations.
		po := llvm.NewPassBuilderOptions()
		defer po.Dispose()
		err = mod.RunPasses("globaldce", llvm.TargetMachine{}, po)
		if err != nil {
			return []error{fmt.Errorf("could not build pass pipeline: %w", err)}
		}
	}

	if config.Scheduler() == "none" {
		// Check for any goroutine starts.
		if start := mod.NamedFunction("internal/task.start"); !start.IsNil() && len(getUses(start)) > 0 {
			errs := []error{}
			for _, call := range getUses(start) {
				errs = append(errs, errorAt(call, "attempted to start a goroutine without a scheduler"))
			}
			return errs
		}
	}

	if config.VerifyIR() {
		if errs := ircheck.Module(mod); errs != nil {
			return errs
		}
	}
	if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
		return []error{errors.New("optimizations caused a verification failure")}
	}

	// After TinyGo-specific transforms have finished, undo exporting these functions.
	for _, name := range functionsUsedInTransforms {
		fn := mod.NamedFunction(name)
		if fn.IsNil() || fn.IsDeclaration() {
			continue
		}
		fn.SetLinkage(llvm.InternalLinkage)
	}

	// Run the ThinLTO pre-link passes, meant to be run on each individual
	// module. This saves compilation time compared to "default<#>" and is meant
	// to better match the optimization passes that are happening during
	// ThinLTO.
	po := llvm.NewPassBuilderOptions()
	defer po.Dispose()
	passes := fmt.Sprintf("thinlto-pre-link<%s>", optLevel)
	err := mod.RunPasses(passes, llvm.TargetMachine{}, po)
	if err != nil {
		return []error{fmt.Errorf("could not build pass pipeline: %w", err)}
	}

	hasGCPass := MakeGCStackSlots(mod)
	if hasGCPass {
		if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
			return []error{errors.New("GC pass caused a verification failure")}
		}
	}

	return nil
}

// functionsUsedInTransform is a list of function symbols that may be used
// during TinyGo optimization passes so they have to be marked as external
// linkage until all TinyGo passes have finished.
var functionsUsedInTransforms = []string{
	"runtime.alloc",
	"runtime.free",
	"runtime.nilPanic",
}


================================================
FILE: transform/rtcalls.go
================================================
package transform

// This file implements several small optimizations of runtime and reflect
// calls.

import (
	"strings"

	"tinygo.org/x/go-llvm"
)

// OptimizeStringToBytes transforms runtime.stringToBytes(...) calls into const
// []byte slices whenever possible. This optimizes the following pattern:
//
//	w.Write([]byte("foo"))
//
// where Write does not store to the slice.
func OptimizeStringToBytes(mod llvm.Module) {
	stringToBytes := mod.NamedFunction("runtime.stringToBytes")
	if stringToBytes.IsNil() {
		// nothing to optimize
		return
	}

	for _, call := range getUses(stringToBytes) {
		strptr := call.Operand(0)
		strlen := call.Operand(1)

		// strptr is always constant because strings are always constant.

		var pointerUses []llvm.Value
		canConvertPointer := true
		for _, use := range getUses(call) {
			if use.IsAExtractValueInst().IsNil() {
				// Expected an extractvalue, but this is something else.
				canConvertPointer = false
				break
			}
			switch use.Type().TypeKind() {
			case llvm.IntegerTypeKind:
				// A length (len or cap). Propagate the length value.
				// This can always be done because the byte slice is always the
				// same length as the original string.
				use.ReplaceAllUsesWith(strlen)
				use.EraseFromParentAsInstruction()
			case llvm.PointerTypeKind:
				// The string pointer itself.
				if !isReadOnly(use) {
					// There is a store to the byte slice. This means that none
					// of the pointer uses can't be propagated.
					canConvertPointer = false
					break
				}
				// It may be that the pointer value can be propagated, if all of
				// the pointer uses are readonly.
				pointerUses = append(pointerUses, use)
			default:
				// should not happen
				panic("unknown return type of runtime.stringToBytes: " + use.Type().String())
			}
		}
		if canConvertPointer {
			// All pointer uses are readonly, so they can be converted.
			for _, use := range pointerUses {
				use.ReplaceAllUsesWith(strptr)
				use.EraseFromParentAsInstruction()
			}

			// Call to runtime.stringToBytes can be eliminated: both the input
			// and the output is constant.
			call.EraseFromParentAsInstruction()
		}
	}
}

// OptimizeStringEqual transforms runtime.stringEqual(...) calls into simple
// integer comparisons if at least one of the sides of the comparison is zero.
// Ths converts str == "" into len(str) == 0 and "" == "" into false.
func OptimizeStringEqual(mod llvm.Module) {
	stringEqual := mod.NamedFunction("runtime.stringEqual")
	if stringEqual.IsNil() {
		// nothing to optimize
		return
	}

	builder := mod.Context().NewBuilder()
	defer builder.Dispose()

	for _, call := range getUses(stringEqual) {
		str1len := call.Operand(1)
		str2len := call.Operand(3)

		zero := llvm.ConstInt(str1len.Type(), 0, false)
		if str1len == zero || str2len == zero {
			builder.SetInsertPointBefore(call)
			icmp := builder.CreateICmp(llvm.IntEQ, str1len, str2len, "")
			call.ReplaceAllUsesWith(icmp)
			call.EraseFromParentAsInstruction()
			continue
		}
	}
}

// OptimizeReflectImplements optimizes the following code:
//
//	implements := someType.Implements(someInterfaceType)
//
// where someType is an arbitrary reflect.Type and someInterfaceType is a
// reflect.Type of interface kind, to the following code:
//
//	_, implements := someType.(interfaceType)
//
// if the interface type is known at compile time (that is, someInterfaceType is
// a LLVM constant aggregate). This optimization is especially important for the
// encoding/json package, which uses this method.
//
// As of this writing, the (reflect.Type).Interface method has not yet been
// implemented so this optimization is critical for the encoding/json package.
func OptimizeReflectImplements(mod llvm.Module) {
	implementsSignature1 := mod.NamedGlobal("reflect/methods.Implements(reflect.Type) bool")
	implementsSignature2 := mod.NamedGlobal("reflect/methods.Implements(internal/reflectlite.Type) bool")
	if implementsSignature1.IsNil() && implementsSignature2.IsNil() {
		return
	}

	builder := mod.Context().NewBuilder()
	defer builder.Dispose()

	// Look up the (reflect.Value).Implements() method.
	var implementsFunc llvm.Value
	for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
		attr := fn.GetStringAttributeAtIndex(-1, "tinygo-invoke")
		if attr.IsNil() {
			continue
		}
		val := attr.GetStringValue()
		if val == "reflect/methods.Implements(reflect.Type) bool" || val == "reflect/methods.Implements(internal/reflectlite.Type) bool" {
			implementsFunc = fn
			break
		}
	}
	if implementsFunc.IsNil() {
		// Doesn't exist in the program, so nothing to do.
		return
	}

	for _, call := range getUses(implementsFunc) {
		if call.IsACallInst().IsNil() {
			continue
		}
		interfaceType := stripPointerCasts(call.Operand(2))
		if interfaceType.IsAGlobalVariable().IsNil() {
			// Interface is unknown at compile time. This can't be optimized.
			continue
		}

		if strings.HasPrefix(interfaceType.Name(), "reflect/types.type:named:") {
			// Get the underlying type.
			interfaceType = stripPointerCasts(builder.CreateExtractValue(interfaceType.Initializer(), 3, ""))
		}
		if !strings.HasPrefix(interfaceType.Name(), "reflect/types.type:interface:") {
			// This is an error. The Type passed to Implements should be of
			// interface type. Ignore it here (don't report it), it will be
			// reported at runtime.
			continue
		}
		typeAssertFunction := mod.NamedFunction(strings.TrimPrefix(interfaceType.Name(), "reflect/types.type:") + ".$typeassert")
		if typeAssertFunction.IsNil() {
			continue
		}

		// Replace Implements call with the type assert call.
		builder.SetInsertPointBefore(call)
		implements := builder.CreateCall(typeAssertFunction.GlobalValueType(), typeAssertFunction, []llvm.Value{
			call.Operand(0), // typecode to check
		}, "")
		call.ReplaceAllUsesWith(implements)
		call.EraseFromParentAsInstruction()
	}
}


================================================
FILE: transform/rtcalls_test.go
================================================
package transform_test

import (
	"testing"

	"github.com/tinygo-org/tinygo/transform"
	"tinygo.org/x/go-llvm"
)

func TestOptimizeStringToBytes(t *testing.T) {
	t.Parallel()
	testTransform(t, "testdata/stringtobytes", func(mod llvm.Module) {
		// Run optimization pass.
		transform.OptimizeStringToBytes(mod)
	})
}

func TestOptimizeStringEqual(t *testing.T) {
	t.Parallel()
	testTransform(t, "testdata/stringequal", func(mod llvm.Module) {
		// Run optimization pass.
		transform.OptimizeStringEqual(mod)
	})
}

func TestOptimizeReflectImplements(t *testing.T) {
	t.Parallel()
	testTransform(t, "testdata/reflect-implements", func(mod llvm.Module) {
		// Run optimization pass.
		transform.OptimizeReflectImplements(mod)
	})
}


================================================
FILE: transform/stacksize.go
================================================
package transform

import (
	"path/filepath"

	"github.com/tinygo-org/tinygo/compileopts"
	"github.com/tinygo-org/tinygo/compiler/llvmutil"
	"github.com/tinygo-org/tinygo/goenv"
	"tinygo.org/x/go-llvm"
)

// CreateStackSizeLoads replaces internal/task.getGoroutineStackSize calls with
// loads from internal/task.stackSizes that will be updated after linking. This
// way the stack sizes are loaded from a separate section and can easily be
// modified after linking.
func CreateStackSizeLoads(mod llvm.Module, config *compileopts.Config) []string {
	functionMap := map[llvm.Value][]llvm.Value{}
	var functions []llvm.Value // ptrtoint values of functions
	var functionNames []string
	var functionValues []llvm.Value // direct references to functions
	for _, use := range getUses(mod.NamedFunction("internal/task.getGoroutineStackSize")) {
		if use.FirstUse().IsNil() {
			// Apparently this stack size isn't used.
			use.EraseFromParentAsInstruction()
			continue
		}
		ptrtoint := use.Operand(0)
		if _, ok := functionMap[ptrtoint]; !ok {
			functions = append(functions, ptrtoint)
			functionNames = append(functionNames, ptrtoint.Operand(0).Name())
			functionValues = append(functionValues, ptrtoint.Operand(0))
		}
		functionMap[ptrtoint] = append(functionMap[ptrtoint], use)
	}

	if len(functions) == 0 {
		// Nothing to do.
		return nil
	}

	ctx := mod.Context()
	targetData := llvm.NewTargetData(mod.DataLayout())
	defer targetData.Dispose()
	uintptrType := ctx.IntType(targetData.PointerSize() * 8)

	// Create the new global with stack sizes, that will be put in a new section
	// just for itself.
	stackSizesGlobalType := llvm.ArrayType(functions[0].Type(), len(functions))
	stackSizesGlobal := llvm.AddGlobal(mod, stackSizesGlobalType, "internal/task.stackSizes")
	stackSizesGlobal.SetSection(".tinygo_stacksizes")
	defaultStackSizes := make([]llvm.Value, len(functions))
	defaultStackSize := llvm.ConstInt(functions[0].Type(), config.StackSize(), false)
	alignment := targetData.ABITypeAlignment(functions[0].Type())
	for i := range defaultStackSizes {
		defaultStackSizes[i] = defaultStackSize
	}
	stackSizesGlobal.SetInitializer(llvm.ConstArray(functions[0].Type(), defaultStackSizes))
	stackSizesGlobal.SetAlignment(alignment)
	// TODO: make this a constant. For some reason, that incrases code size though.
	if config.Debug() {
		dibuilder := llvm.NewDIBuilder(mod)
		dibuilder.CreateCompileUnit(llvm.DICompileUnit{
			Language:  0xb, // DW_LANG_C99 (0xc, off-by-one?)
			File:      "",
			Dir:       "",
			Producer:  "TinyGo",
			Optimized: true,
		})
		ditype := dibuilder.CreateArrayType(llvm.DIArrayType{
			SizeInBits:  targetData.TypeAllocSize(stackSizesGlobalType) * 8,
			AlignInBits: uint32(alignment * 8),
			ElementType: dibuilder.CreateBasicType(llvm.DIBasicType{
				Name:       "uintptr",
				SizeInBits: targetData.TypeAllocSize(functions[0].Type()) * 8,
				Encoding:   llvm.DW_ATE_unsigned,
			}),
			Subscripts: []llvm.DISubrange{
				{
					Lo:    0,
					Count: int64(len(functions)),
				},
			},
		})
		diglobal := dibuilder.CreateGlobalVariableExpression(llvm.Metadata{}, llvm.DIGlobalVariableExpression{
			Name: "internal/task.stackSizes",
			File: dibuilder.CreateFile("internal/task/task_stack.go", filepath.Join(goenv.Get("TINYGOROOT"), "src")),
			Line: 1,
			Type: ditype,
			Expr: dibuilder.CreateExpression(nil),
		})
		stackSizesGlobal.AddMetadata(0, diglobal)

		dibuilder.Finalize()
		dibuilder.Destroy()
	}

	// Add all relevant values to llvm.used (for LTO).
	llvmutil.AppendToGlobal(mod, "llvm.used", append([]llvm.Value{stackSizesGlobal}, functionValues...)...)

	// Replace the calls with loads from the new global with stack sizes.
	irbuilder := ctx.NewBuilder()
	defer irbuilder.Dispose()
	for i, function := range functions {
		for _, use := range functionMap[function] {
			ptr := llvm.ConstGEP(stackSizesGlobalType, stackSizesGlobal, []llvm.Value{
				llvm.ConstInt(ctx.Int32Type(), 0, false),
				llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
			})
			irbuilder.SetInsertPointBefore(use)
			stacksize := irbuilder.CreateLoad(uintptrType, ptr, "stacksize")
			use.ReplaceAllUsesWith(stacksize)
			use.EraseFromParentAsInstruction()
		}
	}

	return functionNames
}


================================================
FILE: transform/stacksize_test.go
================================================
package transform_test

import (
	"testing"

	"github.com/tinygo-org/tinygo/compileopts"
	"github.com/tinygo-org/tinygo/transform"
	"tinygo.org/x/go-llvm"
)

func TestCreateStackSizeLoads(t *testing.T) {
	t.Parallel()
	testTransform(t, "testdata/stacksize", func(mod llvm.Module) {
		// Run optimization pass.
		transform.CreateStackSizeLoads(mod, &compileopts.Config{
			Options: &compileopts.Options{},
			Target: &compileopts.TargetSpec{
				DefaultStackSize: 1024,
			},
		})
	})
}


================================================
FILE: transform/testdata/allocs.ll
================================================
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"

@runtime.zeroSizedAlloc = internal global i8 0, align 1

declare nonnull ptr @runtime.alloc(i32, ptr)

; Test allocating a single int (i32) that should be allocated on the stack.
define void @testInt() {
  %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null)
  store i32 5, ptr %alloc
  ret void
}

; Test allocating an array of 3 i16 values that should be allocated on the
; stack.
define i16 @testArray() {
  %alloc = call align 2 ptr @runtime.alloc(i32 6, ptr null)
  %alloc.1 = getelementptr i16, ptr %alloc, i32 1
  store i16 5, ptr %alloc.1
  %alloc.2 = getelementptr i16, ptr %alloc, i32 2
  %val = load i16, ptr %alloc.2
  ret i16 %val
}

; Test allocating objects with an unknown alignment.
define void @testUnknownAlign() {
  %alloc32 = call ptr @runtime.alloc(i32 32, ptr null)
  store i8 5, ptr %alloc32
  %alloc24 = call ptr @runtime.alloc(i32 24, ptr null)
  store i16 5, ptr %alloc24
  %alloc12 = call ptr @runtime.alloc(i32 12, ptr null)
  store i16 5, ptr %alloc12
  %alloc6 = call ptr @runtime.alloc(i32 6, ptr null)
  store i16 5, ptr %alloc6
  %alloc3 = call ptr @runtime.alloc(i32 3, ptr null)
  store i16 5, ptr %alloc3
  ret void
}

; Call a function that will let the pointer escape, so the heap-to-stack
; transform shouldn't be applied.
define void @testEscapingCall() {
  %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null)
  %val = call ptr @escapeIntPtr(ptr %alloc)
  ret void
}

define void @testEscapingCall2() {
  %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null)
  %val = call ptr @escapeIntPtrSometimes(ptr %alloc, ptr %alloc)
  ret void
}

; Call a function that doesn't let the pointer escape.
define void @testNonEscapingCall() {
  %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null)
  %val = call ptr @noescapeIntPtr(ptr %alloc)
  ret void
}

; Return the allocated value, which lets it escape.
define ptr @testEscapingReturn() {
  %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null)
  ret ptr %alloc
}

; Do a non-escaping allocation in a loop.
define void @testNonEscapingLoop() {
entry:
  br label %loop
loop:
  %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null)
  %ptr = call ptr @noescapeIntPtr(ptr %alloc)
  %result = icmp eq ptr null, %ptr
  br i1 %result, label %loop, label %end
end:
  ret void
}

; Test a zero-sized allocation.
define void @testZeroSizedAlloc() {
  %alloc = call align 1 ptr @runtime.alloc(i32 0, ptr null)
  %ptr = call ptr @noescapeIntPtr(ptr %alloc)
  ret void
}

declare ptr @escapeIntPtr(ptr)

declare ptr @noescapeIntPtr(ptr nocapture)

declare ptr @escapeIntPtrSometimes(ptr nocapture, ptr)


================================================
FILE: transform/testdata/allocs.out.ll
================================================
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"

@runtime.zeroSizedAlloc = internal global i8 0, align 1

declare nonnull ptr @runtime.alloc(i32, ptr)

define void @testInt() {
  %stackalloc = alloca [4 x i8], align 4
  store [4 x i8] zeroinitializer, ptr %stackalloc, align 4
  store i32 5, ptr %stackalloc, align 4
  ret void
}

define i16 @testArray() {
  %stackalloc = alloca [6 x i8], align 2
  store [6 x i8] zeroinitializer, ptr %stackalloc, align 2
  %alloc.1 = getelementptr i16, ptr %stackalloc, i32 1
  store i16 5, ptr %alloc.1, align 2
  %alloc.2 = getelementptr i16, ptr %stackalloc, i32 2
  %val = load i16, ptr %alloc.2, align 2
  ret i16 %val
}

define void @testUnknownAlign() {
  %stackalloc4 = alloca [32 x i8], align 8
  %stackalloc3 = alloca [24 x i8], align 8
  %stackalloc2 = alloca [12 x i8], align 8
  %stackalloc1 = alloca [6 x i8], align 8
  %stackalloc = alloca [3 x i8], align 8
  store [32 x i8] zeroinitializer, ptr %stackalloc4, align 8
  store i8 5, ptr %stackalloc4, align 1
  store [24 x i8] zeroinitializer, ptr %stackalloc3, align 8
  store i16 5, ptr %stackalloc3, align 2
  store [12 x i8] zeroinitializer, ptr %stackalloc2, align 8
  store i16 5, ptr %stackalloc2, align 2
  store [6 x i8] zeroinitializer, ptr %stackalloc1, align 8
  store i16 5, ptr %stackalloc1, align 2
  store [3 x i8] zeroinitializer, ptr %stackalloc, align 8
  store i16 5, ptr %stackalloc, align 2
  ret void
}

define void @testEscapingCall() {
  %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null)
  %val = call ptr @escapeIntPtr(ptr %alloc)
  ret void
}

define void @testEscapingCall2() {
  %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null)
  %val = call ptr @escapeIntPtrSometimes(ptr %alloc, ptr %alloc)
  ret void
}

define void @testNonEscapingCall() {
  %stackalloc = alloca [4 x i8], align 4
  store [4 x i8] zeroinitializer, ptr %stackalloc, align 4
  %val = call ptr @noescapeIntPtr(ptr %stackalloc)
  ret void
}

define ptr @testEscapingReturn() {
  %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null)
  ret ptr %alloc
}

define void @testNonEscapingLoop() {
entry:
  %stackalloc = alloca [4 x i8], align 4
  br label %loop

loop:                                             ; preds = %loop, %entry
  store [4 x i8] zeroinitializer, ptr %stackalloc, align 4
  %ptr = call ptr @noescapeIntPtr(ptr %stackalloc)
  %result = icmp eq ptr null, %ptr
  br i1 %result, label %loop, label %end

end:                                              ; preds = %loop
  ret void
}

define void @testZeroSizedAlloc() {
  %ptr = call ptr @noescapeIntPtr(ptr @runtime.zeroSizedAlloc)
  ret void
}

declare ptr @escapeIntPtr(ptr)

declare ptr @noescapeIntPtr(ptr nocapture)

declare ptr @escapeIntPtrSometimes(ptr nocapture, ptr)


================================================
FILE: transform/testdata/allocs2.go
================================================
package main

import (
	"runtime/volatile"
	"unsafe"
)

func main() {
	n1 := 5
	derefInt(&n1)

	// This should eventually be modified to not escape.
	n2 := 6 // OUT: object allocated on the heap: escapes at line 14
	returnIntPtr(&n2)

	s1 := make([]int, 3)
	readIntSlice(s1)

	s2 := [3]int{}
	readIntSlice(s2[:])

	// This should also be modified to not escape.
	s3 := make([]int, 3) // OUT: object allocated on the heap: escapes at line 24
	returnIntSlice(s3)

	useSlice(make([]int, getUnknownNumber())) // OUT: object allocated on the heap: size is not constant

	s4 := make([]byte, 300) // OUT: object allocated on the heap: object size 300 exceeds maximum stack allocation size 256
	readByteSlice(s4)

	s5 := make([]int, 4) // OUT: object allocated on the heap: escapes at line 32
	_ = append(s5, 5)

	s6 := make([]int, 3)
	s7 := []int{1, 2, 3}
	copySlice(s6, s7)

	c1 := getComplex128() // OUT: object allocated on the heap: escapes at line 39
	useInterface(c1)

	n3 := 5
	func() int {
		return n3
	}()

	callVariadic(3, 5, 8) // OUT: object allocated on the heap: escapes at line 46

	s8 := []int{3, 5, 8} // OUT: object allocated on the heap: escapes at line 49
	callVariadic(s8...)

	n4 := 3 // OUT: object allocated on the heap: escapes at line 53
	n5 := 7 // OUT: object allocated on the heap: escapes at line 53
	func() {
		n4 = n5
	}()
	println(n4, n5)

	// This shouldn't escape.
	var buf [32]byte
	s := string(buf[:])
	println(len(s))

	var rbuf [5]rune
	s = string(rbuf[:])
	println(s)

	// Unsafe usage of DMA buffers: the compiler thinks this buffer won't be
	// used anymore after the volatile store.
	var dmaBuf1 [4]byte
	pseudoVolatile.Set(uint32(unsafeNoEscape(unsafe.Pointer(&dmaBuf1[0]))))

	// Safe usage of DMA buffers: keep the buffer alive until it is no longer
	// needed, but don't mark it as needing to be heap allocated. The compiler
	// will keep the buffer stack allocated if possible.
	var dmaBuf2 [4]byte
	pseudoVolatile.Set(uint32(unsafeNoEscape(unsafe.Pointer(&dmaBuf2[0]))))
	// ...use the buffer in the DMA peripheral
	keepAliveNoEscape(unsafe.Pointer(&dmaBuf2[0]))
}

func derefInt(x *int) int {
	return *x
}

func returnIntPtr(x *int) *int {
	return x
}

func readIntSlice(s []int) int {
	return s[1]
}

func readByteSlice(s []byte) byte {
	return s[1]
}

func returnIntSlice(s []int) []int {
	return s
}

func getUnknownNumber() int

func copySlice(out, in []int) {
	copy(out, in)
}

func getComplex128() complex128

func useInterface(interface{})

func callVariadic(...int)

func useSlice([]int)

// See the function with the same name in the machine package.
//
//go:linkname unsafeNoEscape machine.unsafeNoEscape
func unsafeNoEscape(ptr unsafe.Pointer) uintptr

//go:linkname keepAliveNoEscape machine.keepAliveNoEscape
func keepAliveNoEscape(ptr unsafe.Pointer)

var pseudoVolatile volatile.Register32


================================================
FILE: transform/testdata/gc-stackslots.ll
================================================
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-unknown-wasm"

@runtime.stackChainStart = external global ptr
@someGlobal = global i8 3
@ptrGlobal = global ptr null
@arrGlobal = global [8 x i8] zeroinitializer

declare void @runtime.trackPointer(ptr nocapture readonly)

declare noalias nonnull ptr @runtime.alloc(i32, ptr)

; Generic function that returns a pointer (that must be tracked).
define ptr @getPointer() {
    ret ptr @someGlobal
}

define ptr @needsStackSlots() {
  ; Tracked pointer. Although, in this case the value is immediately returned
  ; so tracking it is not really necessary.
  %ptr = call ptr @runtime.alloc(i32 4, ptr null)
  call void @runtime.trackPointer(ptr %ptr)
  call void @someArbitraryFunction()
  %val = load i8, ptr @someGlobal
  ret ptr %ptr
}

; Check some edge cases of pointer tracking.
define ptr @needsStackSlots2() {
  ; Only one stack slot should be created for this (but at the moment, one is
  ; created for each call to runtime.trackPointer).
  %ptr1 = call ptr @getPointer()
  call void @runtime.trackPointer(ptr %ptr1)
  call void @runtime.trackPointer(ptr %ptr1)
  call void @runtime.trackPointer(ptr %ptr1)

  ; Create a pointer that does not need to be tracked (but is tracked).
  %ptr2 = getelementptr i8, ptr @someGlobal, i32 0
  call void @runtime.trackPointer(ptr %ptr2)

  ; Here is finally the point where an allocation happens.
  %unused = call ptr @runtime.alloc(i32 4, ptr null)
  call void @runtime.trackPointer(ptr %unused)

  ret ptr %ptr1
}

; Return a pointer from a caller. Because it doesn't allocate, no stack objects
; need to be created.
define ptr @noAllocatingFunction() {
  %ptr = call ptr @getPointer()
  call void @runtime.trackPointer(ptr %ptr)
  ret ptr %ptr
}

define ptr @fibNext(ptr %x, ptr %y) {
  %x.val = load i8, ptr %x
  %y.val = load i8, ptr %y
  %out.val = add i8 %x.val, %y.val
  %out.alloc = call ptr @runtime.alloc(i32 1, ptr null)
  call void @runtime.trackPointer(ptr %out.alloc)
  store i8 %out.val, ptr %out.alloc
  ret ptr %out.alloc
}

define ptr @allocLoop() {
entry:
  %entry.x = call ptr @runtime.alloc(i32 1, ptr null)
  call void @runtime.trackPointer(ptr %entry.x)
  %entry.y = call ptr @runtime.alloc(i32 1, ptr null)
  call void @runtime.trackPointer(ptr %entry.y)
  store i8 1, ptr %entry.y
  br label %loop

loop:
  %prev.y = phi ptr [ %entry.y, %entry ], [ %prev.x, %loop ]
  %prev.x = phi ptr [ %entry.x, %entry ], [ %next.x, %loop ]
  call void @runtime.trackPointer(ptr %prev.x)
  call void @runtime.trackPointer(ptr %prev.y)
  %next.x = call ptr @fibNext(ptr %prev.x, ptr %prev.y)
  call void @runtime.trackPointer(ptr %next.x)
  %next.x.val = load i8, ptr %next.x
  %loop.done = icmp ult i8 40, %next.x.val
  br i1 %loop.done, label %end, label %loop

end:
  ret ptr %next.x
}

declare ptr @arrayAlloc()

define void @testGEPBitcast() {
  %arr = call ptr @arrayAlloc()
  %arr.bitcast = getelementptr [32 x i8], ptr %arr, i32 0, i32 0
  call void @runtime.trackPointer(ptr %arr.bitcast)
  %other = call ptr @runtime.alloc(i32 1, ptr null)
  call void @runtime.trackPointer(ptr %other)
  ret void
}

define void @someArbitraryFunction() {
  ret void
}

define void @earlyPopRegression() {
  %x.alloc = call ptr @runtime.alloc(i32 4, ptr null)
  call void @runtime.trackPointer(ptr %x.alloc)
  ; At this point the pass used to pop the stack chain, resulting in a potential use-after-free during allocAndSave.
  musttail call void @allocAndSave(ptr %x.alloc)
  ret void
}

define void @allocAndSave(ptr %x) {
  %y = call ptr @runtime.alloc(i32 4, ptr null)
  call void @runtime.trackPointer(ptr %y)
  store ptr %y, ptr %x
  store ptr %x, ptr @ptrGlobal
  ret void
}

declare void @"(internal/task).Pause"()

define ptr @getAndPause() {
	%ptr = call ptr @getPointer()
	call void @runtime.trackPointer(ptr %ptr)
	; Calling a function with unknown memory access forces stack slot creation.
	call void @"(internal/task).Pause"()
	ret ptr %ptr
}

; Function Attrs: memory(readwrite)
declare void @externCallWithMemAttr() #0

define ptr @getAndCallWithMemAttr() {
	%ptr = call ptr @getPointer()
	call void @runtime.trackPointer(ptr %ptr)
	; Calling an external function which may access non-arg memory forces stack slot creation.
	call void @externCallWithMemAttr()
	ret ptr %ptr
}

; Generic function that returns a slice (that must be tracked).
define {ptr, i32, i32} @getSlice() {
  ret {ptr, i32, i32} {ptr @someGlobal, i32 8, i32 8}
}

define i32 @copyToSlice(ptr %src.ptr, i32 %src.len, i32 %src.cap) {
  %dst = call {ptr, i32, i32} @getSlice()
  %dst.ptr = extractvalue {ptr, i32, i32} %dst, 0
  call void @runtime.trackPointer(ptr %dst.ptr)
  %dst.len = extractvalue {ptr, i32, i32} %dst, 1
  ; Math intrinsics do not need stack slots.
  %minLen = call i32 @llvm.umin.i32(i32 %dst.len, i32 %src.len)
  ; Intrinsics which only access argument memory do not need stack slots.
  call void @llvm.memmove.p0.p0.i32(ptr %dst.ptr, ptr %src.ptr, i32 %minLen, i1 false)
  ret i32 %minLen
}

; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare i32 @llvm.umin.i32(i32, i32) #1

; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
declare void @llvm.memmove.p0.p0.i32(ptr nocapture writeonly, ptr nocapture readonly, i32, i1 immarg) #2

attributes #0 = { memory(readwrite) }
attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }


================================================
FILE: transform/testdata/gc-stackslots.out.ll
================================================
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-unknown-wasm"

@runtime.stackChainStart = internal global ptr null
@someGlobal = global i8 3
@ptrGlobal = global ptr null
@arrGlobal = global [8 x i8] zeroinitializer

declare void @runtime.trackPointer(ptr nocapture readonly)

declare noalias nonnull ptr @runtime.alloc(i32, ptr)

define ptr @getPointer() {
  ret ptr @someGlobal
}

define ptr @needsStackSlots() {
  %gc.stackobject = alloca { ptr, i32, ptr }, align 8
  store { ptr, i32, ptr } { ptr null, i32 1, ptr null }, ptr %gc.stackobject, align 4
  %1 = load ptr, ptr @runtime.stackChainStart, align 4
  %2 = getelementptr { ptr, i32, ptr }, ptr %gc.stackobject, i32 0, i32 0
  store ptr %1, ptr %2, align 4
  store ptr %gc.stackobject, ptr @runtime.stackChainStart, align 4
  %ptr = call ptr @runtime.alloc(i32 4, ptr null)
  %3 = getelementptr { ptr, i32, ptr }, ptr %gc.stackobject, i32 0, i32 2
  store ptr %ptr, ptr %3, align 4
  call void @someArbitraryFunction()
  %val = load i8, ptr @someGlobal, align 1
  store ptr %1, ptr @runtime.stackChainStart, align 4
  ret ptr %ptr
}

define ptr @needsStackSlots2() {
  %gc.stackobject = alloca { ptr, i32, ptr, ptr, ptr, ptr, ptr }, align 8
  store { ptr, i32, ptr, ptr, ptr, ptr, ptr } { ptr null, i32 5, ptr null, ptr null, ptr null, ptr null, ptr null }, ptr %gc.stackobject, align 4
  %1 = load ptr, ptr @runtime.stackChainStart, align 4
  %2 = getelementptr { ptr, i32, ptr, ptr, ptr, ptr, ptr }, ptr %gc.stackobject, i32 0, i32 0
  store ptr %1, ptr %2, align 4
  store ptr %gc.stackobject, ptr @runtime.stackChainStart, align 4
  %ptr1 = call ptr @getPointer()
  %3 = getelementptr { ptr, i32, ptr, ptr, ptr, ptr, ptr }, ptr %gc.stackobject, i32 0, i32 4
  store ptr %ptr1, ptr %3, align 4
  %4 = getelementptr { ptr, i32, ptr, ptr, ptr, ptr, ptr }, ptr %gc.stackobject, i32 0, i32 3
  store ptr %ptr1, ptr %4, align 4
  %5 = getelementptr { ptr, i32, ptr, ptr, ptr, ptr, ptr }, ptr %gc.stackobject, i32 0, i32 2
  store ptr %ptr1, ptr %5, align 4
  %ptr2 = getelementptr i8, ptr @someGlobal, i32 0
  %6 = getelementptr { ptr, i32, ptr, ptr, ptr, ptr, ptr }, ptr %gc.stackobject, i32 0, i32 5
  store ptr %ptr2, ptr %6, align 4
  %unused = call ptr @runtime.alloc(i32 4, ptr null)
  %7 = getelementptr { ptr, i32, ptr, ptr, ptr, ptr, ptr }, ptr %gc.stackobject, i32 0, i32 6
  store ptr %unused, ptr %7, align 4
  store ptr %1, ptr @runtime.stackChainStart, align 4
  ret ptr %ptr1
}

define ptr @noAllocatingFunction() {
  %ptr = call ptr @getPointer()
  ret ptr %ptr
}

define ptr @fibNext(ptr %x, ptr %y) {
  %gc.stackobject = alloca { ptr, i32, ptr }, align 8
  store { ptr, i32, ptr } { ptr null, i32 1, ptr null }, ptr %gc.stackobject, align 4
  %1 = load ptr, ptr @runtime.stackChainStart, align 4
  %2 = getelementptr { ptr, i32, ptr }, ptr %gc.stackobject, i32 0, i32 0
  store ptr %1, ptr %2, align 4
  store ptr %gc.stackobject, ptr @runtime.stackChainStart, align 4
  %x.val = load i8, ptr %x, align 1
  %y.val = load i8, ptr %y, align 1
  %out.val = add i8 %x.val, %y.val
  %out.alloc = call ptr @runtime.alloc(i32 1, ptr null)
  %3 = getelementptr { ptr, i32, ptr }, ptr %gc.stackobject, i32 0, i32 2
  store ptr %out.alloc, ptr %3, align 4
  store i8 %out.val, ptr %out.alloc, align 1
  store ptr %1, ptr @runtime.stackChainStart, align 4
  ret ptr %out.alloc
}

define ptr @allocLoop() {
entry:
  %gc.stackobject = alloca { ptr, i32, ptr, ptr, ptr, ptr, ptr }, align 8
  store { ptr, i32, ptr, ptr, ptr, ptr, ptr } { ptr null, i32 5, ptr null, ptr null, ptr null, ptr null, ptr null }, ptr %gc.stackobject, align 4
  %0 = load ptr, ptr @runtime.stackChainStart, align 4
  %1 = getelementptr { ptr, i32, ptr, ptr, ptr, ptr, ptr }, ptr %gc.stackobject, i32 0, i32 0
  store ptr %0, ptr %1, align 4
  store ptr %gc.stackobject, ptr @runtime.stackChainStart, align 4
  %entry.x = call ptr @runtime.alloc(i32 1, ptr null)
  %2 = getelementptr { ptr, i32, ptr, ptr, ptr, ptr, ptr }, ptr %gc.stackobject, i32 0, i32 2
  store ptr %entry.x, ptr %2, align 4
  %entry.y = call ptr @runtime.alloc(i32 1, ptr null)
  %3 = getelementptr { ptr, i32, ptr, ptr, ptr, ptr, ptr }, ptr %gc.stackobject, i32 0, i32 3
  store ptr %entry.y, ptr %3, align 4
  store i8 1, ptr %entry.y, align 1
  br label %loop

loop:                                             ; preds = %loop, %entry
  %prev.y = phi ptr [ %entry.y, %entry ], [ %prev.x, %loop ]
  %prev.x = phi ptr [ %entry.x, %entry ], [ %next.x, %loop ]
  %4 = getelementptr { ptr, i32, ptr, ptr, ptr, ptr, ptr }, ptr %gc.stackobject, i32 0, i32 5
  store ptr %prev.y, ptr %4, align 4
  %5 = getelementptr { ptr, i32, ptr, ptr, ptr, ptr, ptr }, ptr %gc.stackobject, i32 0, i32 4
  store ptr %prev.x, ptr %5, align 4
  %next.x = call ptr @fibNext(ptr %prev.x, ptr %prev.y)
  %6 = getelementptr { ptr, i32, ptr, ptr, ptr, ptr, ptr }, ptr %gc.stackobject, i32 0, i32 6
  store ptr %next.x, ptr %6, align 4
  %next.x.val = load i8, ptr %next.x, align 1
  %loop.done = icmp ult i8 40, %next.x.val
  br i1 %loop.done, label %end, label %loop

end:                                              ; preds = %loop
  store ptr %0, ptr @runtime.stackChainStart, align 4
  ret ptr %next.x
}

declare ptr @arrayAlloc()

define void @testGEPBitcast() {
  %gc.stackobject = alloca { ptr, i32, ptr, ptr }, align 8
  store { ptr, i32, ptr, ptr } { ptr null, i32 2, ptr null, ptr null }, ptr %gc.stackobject, align 4
  %1 = load ptr, ptr @runtime.stackChainStart, align 4
  %2 = getelementptr { ptr, i32, ptr, ptr }, ptr %gc.stackobject, i32 0, i32 0
  store ptr %1, ptr %2, align 4
  store ptr %gc.stackobject, ptr @runtime.stackChainStart, align 4
  %arr = call ptr @arrayAlloc()
  %arr.bitcast = getelementptr [32 x i8], ptr %arr, i32 0, i32 0
  %3 = getelementptr { ptr, i32, ptr, ptr }, ptr %gc.stackobject, i32 0, i32 2
  store ptr %arr.bitcast, ptr %3, align 4
  %other = call ptr @runtime.alloc(i32 1, ptr null)
  %4 = getelementptr { ptr, i32, ptr, ptr }, ptr %gc.stackobject, i32 0, i32 3
  store ptr %other, ptr %4, align 4
  store ptr %1, ptr @runtime.stackChainStart, align 4
  ret void
}

define void @someArbitraryFunction() {
  ret void
}

define void @earlyPopRegression() {
  %gc.stackobject = alloca { ptr, i32, ptr }, align 8
  store { ptr, i32, ptr } { ptr null, i32 1, ptr null }, ptr %gc.stackobject, align 4
  %1 = load ptr, ptr @runtime.stackChainStart, align 4
  %2 = getelementptr { ptr, i32, ptr }, ptr %gc.stackobject, i32 0, i32 0
  store ptr %1, ptr %2, align 4
  store ptr %gc.stackobject, ptr @runtime.stackChainStart, align 4
  %x.alloc = call ptr @runtime.alloc(i32 4, ptr null)
  %3 = getelementptr { ptr, i32, ptr }, ptr %gc.stackobject, i32 0, i32 2
  store ptr %x.alloc, ptr %3, align 4
  call void @allocAndSave(ptr %x.alloc)
  store ptr %1, ptr @runtime.stackChainStart, align 4
  ret void
}

define void @allocAndSave(ptr %x) {
  %gc.stackobject = alloca { ptr, i32, ptr }, align 8
  store { ptr, i32, ptr } { ptr null, i32 1, ptr null }, ptr %gc.stackobject, align 4
  %1 = load ptr, ptr @runtime.stackChainStart, align 4
  %2 = getelementptr { ptr, i32, ptr }, ptr %gc.stackobject, i32 0, i32 0
  store ptr %1, ptr %2, align 4
  store ptr %gc.stackobject, ptr @runtime.stackChainStart, align 4
  %y = call ptr @runtime.alloc(i32 4, ptr null)
  %3 = getelementptr { ptr, i32, ptr }, ptr %gc.stackobject, i32 0, i32 2
  store ptr %y, ptr %3, align 4
  store ptr %y, ptr %x, align 4
  store ptr %x, ptr @ptrGlobal, align 4
  store ptr %1, ptr @runtime.stackChainStart, align 4
  ret void
}

declare void @"(internal/task).Pause"()

define ptr @getAndPause() {
  %gc.stackobject = alloca { ptr, i32, ptr }, align 8
  store { ptr, i32, ptr } { ptr null, i32 1, ptr null }, ptr %gc.stackobject, align 4
  %1 = load ptr, ptr @runtime.stackChainStart, align 4
  %2 = getelementptr { ptr, i32, ptr }, ptr %gc.stackobject, i32 0, i32 0
  store ptr %1, ptr %2, align 4
  store ptr %gc.stackobject, ptr @runtime.stackChainStart, align 4
  %ptr = call ptr @getPointer()
  %3 = getelementptr { ptr, i32, ptr }, ptr %gc.stackobject, i32 0, i32 2
  store ptr %ptr, ptr %3, align 4
  call void @"(internal/task).Pause"()
  store ptr %1, ptr @runtime.stackChainStart, align 4
  ret ptr %ptr
}

; Function Attrs: memory(readwrite)
declare void @externCallWithMemAttr() #0

define ptr @getAndCallWithMemAttr() {
  %gc.stackobject = alloca { ptr, i32, ptr }, align 8
  store { ptr, i32, ptr } { ptr null, i32 1, ptr null }, ptr %gc.stackobject, align 4
  %1 = load ptr, ptr @runtime.stackChainStart, align 4
  %2 = getelementptr { ptr, i32, ptr }, ptr %gc.stackobject, i32 0, i32 0
  store ptr %1, ptr %2, align 4
  store ptr %gc.stackobject, ptr @runtime.stackChainStart, align 4
  %ptr = call ptr @getPointer()
  %3 = getelementptr { ptr, i32, ptr }, ptr %gc.stackobject, i32 0, i32 2
  store ptr %ptr, ptr %3, align 4
  call void @externCallWithMemAttr()
  store ptr %1, ptr @runtime.stackChainStart, align 4
  ret ptr %ptr
}

define { ptr, i32, i32 } @getSlice() {
  ret { ptr, i32, i32 } { ptr @someGlobal, i32 8, i32 8 }
}

define i32 @copyToSlice(ptr %src.ptr, i32 %src.len, i32 %src.cap) {
  %dst = call { ptr, i32, i32 } @getSlice()
  %dst.ptr = extractvalue { ptr, i32, i32 } %dst, 0
  %dst.len = extractvalue { ptr, i32, i32 } %dst, 1
  %minLen = call i32 @llvm.umin.i32(i32 %dst.len, i32 %src.len)
  call void @llvm.memmove.p0.p0.i32(ptr %dst.ptr, ptr %src.ptr, i32 %minLen, i1 false)
  ret i32 %minLen
}

; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare i32 @llvm.umin.i32(i32, i32) #1

; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
declare void @llvm.memmove.p0.p0.i32(ptr nocapture writeonly, ptr nocapture readonly, i32, i1 immarg) #2

attributes #0 = { memory(readwrite) }
attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }


================================================
FILE: transform/testdata/interface.ll
================================================
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"

@"reflect/types.type:basic:uint8" = linkonce_odr constant { i8, ptr } { i8 8, ptr @"reflect/types.type:pointer:basic:uint8" }, align 4
@"reflect/types.type:pointer:basic:uint8" = linkonce_odr constant { i8, ptr } { i8 21, ptr @"reflect/types.type:basic:uint8" }, align 4
@"reflect/types.typeid:basic:uint8" = external constant i8
@"reflect/types.typeid:basic:int16" = external constant i8
@"reflect/types.type:basic:int" = linkonce_odr constant { i8, ptr } { i8 2, ptr @"reflect/types.type:pointer:basic:int" }, align 4
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, ptr } { i8 21, ptr @"reflect/types.type:basic:int" }, align 4
@"reflect/methods.NeverImplementedMethod()" = linkonce_odr constant i8 0
@"reflect/methods.Double() int" = linkonce_odr constant i8 0
@"Number$methodset" = linkonce_odr unnamed_addr constant { i32, [1 x ptr], { ptr } } { i32 1, [1 x ptr] [ptr @"reflect/methods.Double() int"], { ptr } { ptr @"(Number).Double$invoke" } }
@"reflect/types.type:named:Number" = linkonce_odr constant { ptr, i8, ptr, ptr } { ptr @"Number$methodset", i8 34, ptr @"reflect/types.type:pointer:named:Number", ptr @"reflect/types.type:basic:int" }, align 4
@"reflect/types.type:pointer:named:Number" = linkonce_odr constant { i8, ptr } { i8 21, ptr getelementptr inbounds ({ ptr, i8, ptr, ptr }, ptr @"reflect/types.type:named:Number", i32 0, i32 1) }, align 4

declare i1 @runtime.typeAssert(ptr, ptr)
declare void @runtime.printuint8(i8)
declare void @runtime.printint16(i16)
declare void @runtime.printint32(i32)
declare void @runtime.printptr(i32)
declare void @runtime.printnl()
declare void @runtime.nilPanic(ptr)

define void @printInterfaces() {
  call void @printInterface(ptr @"reflect/types.type:basic:int", ptr inttoptr (i32 5 to ptr))
  call void @printInterface(ptr @"reflect/types.type:basic:uint8", ptr inttoptr (i8 120 to ptr))
  call void @printInterface(ptr getelementptr inbounds ({ ptr, i8, ptr, ptr }, ptr @"reflect/types.type:named:Number", i32 0, i32 1), ptr inttoptr (i32 3 to ptr))

  ret void
}

define void @printInterface(ptr %typecode, ptr %value) {
  %isUnmatched = call i1 @Unmatched$typeassert(ptr %typecode)
  br i1 %isUnmatched, label %typeswitch.Unmatched, label %typeswitch.notUnmatched

typeswitch.Unmatched:
  %unmatched = ptrtoint ptr %value to i32
  call void @runtime.printptr(i32 %unmatched)
  call void @runtime.printnl()
  ret void

typeswitch.notUnmatched:
  %isDoubler = call i1 @Doubler$typeassert(ptr %typecode)
  br i1 %isDoubler, label %typeswitch.Doubler, label %typeswitch.notDoubler

typeswitch.Doubler:
  %doubler.result = call i32 @"Doubler.Double$invoke"(ptr %value, ptr %typecode, ptr undef)
  call void @runtime.printint32(i32 %doubler.result)
  ret void

typeswitch.notDoubler:
  %isByte = call i1 @runtime.typeAssert(ptr %typecode, ptr nonnull @"reflect/types.typeid:basic:uint8")
  br i1 %isByte, label %typeswitch.byte, label %typeswitch.notByte

typeswitch.byte:
  %byte = ptrtoint ptr %value to i8
  call void @runtime.printuint8(i8 %byte)
  call void @runtime.printnl()
  ret void

typeswitch.notByte:
  ; this is a type assert that always fails
  %isInt16 = call i1 @runtime.typeAssert(ptr %typecode, ptr nonnull @"reflect/types.typeid:basic:int16")
  br i1 %isInt16, label %typeswitch.int16, label %typeswitch.notInt16

typeswitch.int16:
  %int16 = ptrtoint ptr %value to i16
  call void @runtime.printint16(i16 %int16)
  call void @runtime.printnl()
  ret void

typeswitch.notInt16:
  ret void
}

define i32 @"(Number).Double"(i32 %receiver, ptr %context) {
  %ret = mul i32 %receiver, 2
  ret i32 %ret
}

define i32 @"(Number).Double$invoke"(ptr %receiverPtr, ptr %context) {
  %receiver = ptrtoint ptr %receiverPtr to i32
  %ret = call i32 @"(Number).Double"(i32 %receiver, ptr undef)
  ret i32 %ret
}

declare i32 @"Doubler.Double$invoke"(ptr %receiver, ptr %typecode, ptr %context) #0

declare i1 @Doubler$typeassert(ptr %typecode) #1

declare i1 @Unmatched$typeassert(ptr %typecode) #2

attributes #0 = { "tinygo-invoke"="reflect/methods.Double() int" "tinygo-methods"="reflect/methods.Double() int" }
attributes #1 = { "tinygo-methods"="reflect/methods.Double() int" }
attributes #2 = { "tinygo-methods"="reflect/methods.NeverImplementedMethod()" }


================================================
FILE: transform/testdata/interface.out.ll
================================================
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"

@"reflect/types.type:basic:uint8" = linkonce_odr constant { i8, ptr } { i8 8, ptr @"reflect/types.type:pointer:basic:uint8" }, align 4
@"reflect/types.type:pointer:basic:uint8" = linkonce_odr constant { i8, ptr } { i8 21, ptr @"reflect/types.type:basic:uint8" }, align 4
@"reflect/types.type:basic:int" = linkonce_odr constant { i8, ptr } { i8 2, ptr @"reflect/types.type:pointer:basic:int" }, align 4
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, ptr } { i8 21, ptr @"reflect/types.type:basic:int" }, align 4
@"reflect/types.type:pointer:named:Number" = linkonce_odr constant { i8, ptr } { i8 21, ptr @"reflect/types.type:named:Number" }, align 4
@"reflect/types.type:named:Number" = linkonce_odr constant { i8, ptr, ptr } { i8 34, ptr @"reflect/types.type:pointer:named:Number", ptr @"reflect/types.type:basic:int" }, align 4

declare void @runtime.printuint8(i8)

declare void @runtime.printint16(i16)

declare void @runtime.printint32(i32)

declare void @runtime.printptr(i32)

declare void @runtime.printnl()

declare void @runtime.nilPanic(ptr)

define void @printInterfaces() {
  call void @printInterface(ptr @"reflect/types.type:basic:int", ptr inttoptr (i32 5 to ptr))
  call void @printInterface(ptr @"reflect/types.type:basic:uint8", ptr inttoptr (i8 120 to ptr))
  call void @printInterface(ptr @"reflect/types.type:named:Number", ptr inttoptr (i32 3 to ptr))
  ret void
}

define void @printInterface(ptr %typecode, ptr %value) {
  %isUnmatched = call i1 @"Unmatched$typeassert"(ptr %typecode)
  br i1 %isUnmatched, label %typeswitch.Unmatched, label %typeswitch.notUnmatched

typeswitch.Unmatched:                             ; preds = %0
  %unmatched = ptrtoint ptr %value to i32
  call void @runtime.printptr(i32 %unmatched)
  call void @runtime.printnl()
  ret void

typeswitch.notUnmatched:                          ; preds = %0
  %isDoubler = call i1 @"Doubler$typeassert"(ptr %typecode)
  br i1 %isDoubler, label %typeswitch.Doubler, label %typeswitch.notDoubler

typeswitch.Doubler:                               ; preds = %typeswitch.notUnmatched
  %doubler.result = call i32 @"Doubler.Double$invoke"(ptr %value, ptr %typecode, ptr undef)
  call void @runtime.printint32(i32 %doubler.result)
  ret void

typeswitch.notDoubler:                            ; preds = %typeswitch.notUnmatched
  %typeassert.ok = icmp eq ptr @"reflect/types.type:basic:uint8", %typecode
  br i1 %typeassert.ok, label %typeswitch.byte, label %typeswitch.notByte

typeswitch.byte:                                  ; preds = %typeswitch.notDoubler
  %byte = ptrtoint ptr %value to i8
  call void @runtime.printuint8(i8 %byte)
  call void @runtime.printnl()
  ret void

typeswitch.notByte:                               ; preds = %typeswitch.notDoubler
  br i1 false, label %typeswitch.int16, label %typeswitch.notInt16

typeswitch.int16:                                 ; preds = %typeswitch.notByte
  %int16 = ptrtoint ptr %value to i16
  call void @runtime.printint16(i16 %int16)
  call void @runtime.printnl()
  ret void

typeswitch.notInt16:                              ; preds = %typeswitch.notByte
  ret void
}

define i32 @"(Number).Double"(i32 %receiver, ptr %context) {
  %ret = mul i32 %receiver, 2
  ret i32 %ret
}

define i32 @"(Number).Double$invoke"(ptr %receiverPtr, ptr %context) {
  %receiver = ptrtoint ptr %receiverPtr to i32
  %ret = call i32 @"(Number).Double"(i32 %receiver, ptr undef)
  ret i32 %ret
}

define internal i32 @"Doubler.Double$invoke"(ptr %receiver, ptr %actualType, ptr %context) unnamed_addr #0 {
entry:
  %"named:Number.icmp" = icmp eq ptr %actualType, @"reflect/types.type:named:Number"
  br i1 %"named:Number.icmp", label %"named:Number", label %"named:Number.next"

"named:Number":                                   ; preds = %entry
  %0 = call i32 @"(Number).Double$invoke"(ptr %receiver, ptr undef)
  ret i32 %0

"named:Number.next":                              ; preds = %entry
  call void @runtime.nilPanic(ptr undef)
  unreachable
}

define internal i1 @"Doubler$typeassert"(ptr %actualType) unnamed_addr #1 {
entry:
  %"named:Number.icmp" = icmp eq ptr %actualType, @"reflect/types.type:named:Number"
  br i1 %"named:Number.icmp", label %then, label %"named:Number.next"

then:                                             ; preds = %entry
  ret i1 true

"named:Number.next":                              ; preds = %entry
  ret i1 false
}

define internal i1 @"Unmatched$typeassert"(ptr %actualType) unnamed_addr #2 {
entry:
  ret i1 false

then:                                             ; No predecessors!
  ret i1 true
}

attributes #0 = { "tinygo-invoke"="reflect/methods.Double() int" "tinygo-methods"="reflect/methods.Double() int" }
attributes #1 = { "tinygo-methods"="reflect/methods.Double() int" }
attributes #2 = { "tinygo-methods"="reflect/methods.NeverImplementedMethod()" }


================================================
FILE: transform/testdata/interrupt.ll
================================================
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7em-none-eabi"

%machine.UART = type { ptr }
%machine.RingBuffer = type { [128 x %"runtime/volatile.Register8"], %"runtime/volatile.Register8", %"runtime/volatile.Register8" }
%"runtime/volatile.Register8" = type { i8 }
%"runtime/interrupt.handle" = type { ptr, i32, %"runtime/interrupt.Interrupt" }
%"runtime/interrupt.Interrupt" = type { i32 }

@"runtime/interrupt.$interrupt2" = private unnamed_addr constant %"runtime/interrupt.handle" { ptr @machine.UART0, i32 ptrtoint (ptr @"(*machine.UART).handleInterrupt$bound" to i32), %"runtime/interrupt.Interrupt" { i32 2 } }
@machine.UART0 = internal global %machine.UART { ptr @"machine$alloc.335" }
@"machine$alloc.335" = internal global %machine.RingBuffer zeroinitializer

declare void @"runtime/interrupt.callHandlers"(i32, ptr) local_unnamed_addr

declare void @"device/arm.EnableIRQ"(i32, ptr nocapture readnone)

declare void @"device/arm.SetPriority"(i32, i32, ptr nocapture readnone)

declare void @"runtime/interrupt.use"(%"runtime/interrupt.Interrupt")

define void @runtime.initAll(ptr nocapture readnone) unnamed_addr {
entry:
  call void @"device/arm.SetPriority"(i32 ptrtoint (ptr @"runtime/interrupt.$interrupt2" to i32), i32 192, ptr undef)
  call void @"device/arm.EnableIRQ"(i32 ptrtoint (ptr @"runtime/interrupt.$interrupt2" to i32), ptr undef)
  call void @"runtime/interrupt.use"(%"runtime/interrupt.Interrupt" { i32 ptrtoint (ptr @"runtime/interrupt.$interrupt2" to i32) })
  ret void
}

define void @UARTE0_UART0_IRQHandler() {
  call void @"runtime/interrupt.callHandlers"(i32 2, ptr undef)
  ret void
}

define void @NFCT_IRQHandler() {
  call void @"runtime/interrupt.callHandlers"(i32 5, ptr undef)
  ret void
}

define internal void @interruptSWVector(i32 %num) {
entry:
  switch i32 %num, label %switch.done [
    i32 2, label %switch.body2
    i32 5, label %switch.body5
  ]

switch.body2:
  call void @"runtime/interrupt.callHandlers"(i32 2, ptr undef)
  ret void

switch.body5:
  call void @"runtime/interrupt.callHandlers"(i32 5, ptr undef)
  ret void

switch.done:
  ret void
}

define internal void @"(*machine.UART).handleInterrupt$bound"(i32, ptr nocapture %context) {
entry:
  call void @"(*machine.UART).handleInterrupt"(ptr %context, i32 %0, ptr undef)
  ret void
}

declare void @"(*machine.UART).handleInterrupt"(ptr nocapture, i32, ptr nocapture readnone)


================================================
FILE: transform/testdata/interrupt.out.ll
================================================
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7em-none-eabi"

%machine.UART = type { ptr }
%machine.RingBuffer = type { [128 x %"runtime/volatile.Register8"], %"runtime/volatile.Register8", %"runtime/volatile.Register8" }
%"runtime/volatile.Register8" = type { i8 }
%"runtime/interrupt.Interrupt" = type { i32 }

@machine.UART0 = internal global %machine.UART { ptr @"machine$alloc.335" }
@"machine$alloc.335" = internal global %machine.RingBuffer zeroinitializer

declare void @"runtime/interrupt.callHandlers"(i32, ptr) local_unnamed_addr

declare void @"device/arm.EnableIRQ"(i32, ptr nocapture readnone)

declare void @"device/arm.SetPriority"(i32, i32, ptr nocapture readnone)

declare void @"runtime/interrupt.use"(%"runtime/interrupt.Interrupt")

define void @runtime.initAll(ptr nocapture readnone %0) unnamed_addr {
entry:
  call void @"device/arm.SetPriority"(i32 2, i32 192, ptr undef)
  call void @"device/arm.EnableIRQ"(i32 2, ptr undef)
  ret void
}

define void @UARTE0_UART0_IRQHandler() {
  call void @"(*machine.UART).handleInterrupt$bound"(i32 2, ptr @machine.UART0)
  ret void
}

define internal void @interruptSWVector(i32 %num) {
entry:
  switch i32 %num, label %switch.done [
    i32 2, label %switch.body2
    i32 5, label %switch.body5
  ]

switch.body2:                                     ; preds = %entry
  call void @"(*machine.UART).handleInterrupt$bound"(i32 2, ptr @machine.UART0)
  ret void

switch.body5:                                     ; preds = %entry
  unreachable

switch.done:                                      ; preds = %entry
  ret void
}

define internal void @"(*machine.UART).handleInterrupt$bound"(i32 %0, ptr nocapture %context) {
entry:
  call void @"(*machine.UART).handleInterrupt"(ptr %context, i32 %0, ptr undef)
  ret void
}

declare void @"(*machine.UART).handleInterrupt"(ptr nocapture, i32, ptr nocapture readnone)


================================================
FILE: transform/testdata/maps.ll
================================================
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"

@answer = constant [6 x i8] c"answer"

; func(keySize, valueSize uint8, sizeHint uintptr) *runtime.hashmap
declare nonnull ptr @runtime.hashmapMake(i8, i8, i32)

; func(map[string]int, string, unsafe.Pointer)
declare void @runtime.hashmapStringSet(ptr nocapture, ptr, i32, ptr nocapture readonly)

; func(map[string]int, string, unsafe.Pointer)
declare i1 @runtime.hashmapStringGet(ptr nocapture, ptr, i32, ptr nocapture)

define void @testUnused() {
    ; create the map
    %map = call ptr @runtime.hashmapMake(i8 4, i8 4, i32 0)
    ; create the value to be stored
    %hashmap.value = alloca i32
    store i32 42, ptr %hashmap.value
    ; store the value
    call void @runtime.hashmapStringSet(ptr %map, ptr @answer, i32 6, ptr %hashmap.value)
    ret void
}

; Note that the following function should ideally be optimized (it could simply
; return 42), but isn't at the moment.
define i32 @testReadonly() {
    ; create the map
    %map = call ptr @runtime.hashmapMake(i8 4, i8 4, i32 0)

    ; create the value to be stored
    %hashmap.value = alloca i32
    store i32 42, ptr %hashmap.value

    ; store the value
    call void @runtime.hashmapStringSet(ptr %map, ptr @answer, i32 6, ptr %hashmap.value)

    ; load the value back
    %hashmap.value2 = alloca i32
    %commaOk = call i1 @runtime.hashmapStringGet(ptr %map, ptr @answer, i32 6, ptr %hashmap.value2)
    %loadedValue = load i32, ptr %hashmap.value2

    ret i32 %loadedValue
}

define ptr @testUsed() {
    %1 = call ptr @runtime.hashmapMake(i8 4, i8 4, i32 0)
    ret ptr %1
}


================================================
FILE: transform/testdata/maps.out.ll
================================================
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"

@answer = constant [6 x i8] c"answer"

declare nonnull ptr @runtime.hashmapMake(i8, i8, i32)

declare void @runtime.hashmapStringSet(ptr nocapture, ptr, i32, ptr nocapture readonly)

declare i1 @runtime.hashmapStringGet(ptr nocapture, ptr, i32, ptr nocapture)

define void @testUnused() {
  ret void
}

define i32 @testReadonly() {
  %map = call ptr @runtime.hashmapMake(i8 4, i8 4, i32 0)
  %hashmap.value = alloca i32, align 4
  store i32 42, ptr %hashmap.value, align 4
  call void @runtime.hashmapStringSet(ptr %map, ptr @answer, i32 6, ptr %hashmap.value)
  %hashmap.value2 = alloca i32, align 4
  %commaOk = call i1 @runtime.hashmapStringGet(ptr %map, ptr @answer, i32 6, ptr %hashmap.value2)
  %loadedValue = load i32, ptr %hashmap.value2, align 4
  ret i32 %loadedValue
}

define ptr @testUsed() {
  %1 = call ptr @runtime.hashmapMake(i8 4, i8 4, i32 0)
  ret ptr %1
}


================================================
FILE: transform/testdata/reflect-implements.ll
================================================
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"

%runtime._interface = type { ptr, ptr }

@"reflect/types.type:named:error" = internal constant { i8, i16, ptr, ptr } { i8 52, i16 0, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = internal constant { i8, ptr } { i8 20, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = internal constant { i8, ptr } { i8 21, ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:pointer:named:error" = internal constant { i8, i16, ptr } { i8 21, i16 0, ptr @"reflect/types.type:named:error" }, align 4
@"reflect/types.type:pointer:named:reflect.rawType" = internal constant { ptr, i8, i16, ptr } { ptr null, i8 21, i16 0, ptr null }, align 4
@"reflect/methods.Implements(reflect.Type) bool" = internal constant i8 0, align 1

; var errorType = reflect.TypeOf((*error)(nil)).Elem()
; func isError(typ reflect.Type) bool {
;   return typ.Implements(errorType)
; }
; The type itself is stored in %typ.value, %typ.typecode just refers to the
; type of reflect.Type. This function can be optimized because errorType is
; known at compile time (after the interp pass has run).
define i1 @main.isError(ptr %typ.typecode, ptr %typ.value, ptr %context) {
entry:
  %result = call i1 @"reflect.Type.Implements$invoke"(ptr %typ.value, ptr getelementptr inbounds ({ ptr, i8, ptr }, ptr @"reflect/types.type:pointer:named:reflect.rawType", i32 0, i32 1), ptr @"reflect/types.type:named:error", ptr %typ.typecode, ptr undef)
  ret i1 %result
}

; This Implements method call can not be optimized because itf is not known at
; compile time.
; func isUnknown(typ, itf reflect.Type) bool {
;   return typ.Implements(itf)
; }
define i1 @main.isUnknown(ptr %typ.typecode, ptr %typ.value, ptr %itf.typecode, ptr %itf.value, ptr %context) {
entry:
  %result = call i1 @"reflect.Type.Implements$invoke"(ptr %typ.value, ptr %itf.typecode, ptr %itf.value, ptr %typ.typecode, ptr undef)
  ret i1 %result
}

declare i1 @"reflect.Type.Implements$invoke"(ptr, ptr, ptr, ptr, ptr) #0
declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(ptr %0) #1

attributes #0 = { "tinygo-invoke"="reflect/methods.Implements(reflect.Type) bool" "tinygo-methods"="reflect/methods.Align() int; reflect/methods.Implements(reflect.Type) bool" }
attributes #1 = { "tinygo-methods"="reflect/methods.Error() string" }


================================================
FILE: transform/testdata/reflect-implements.out.ll
================================================
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"

@"reflect/types.type:named:error" = internal constant { i8, i16, ptr, ptr } { i8 52, i16 0, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = internal constant { i8, ptr } { i8 20, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = internal constant { i8, ptr } { i8 21, ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:pointer:named:error" = internal constant { i8, i16, ptr } { i8 21, i16 0, ptr @"reflect/types.type:named:error" }, align 4
@"reflect/types.type:pointer:named:reflect.rawType" = internal constant { ptr, i8, i16, ptr } { ptr null, i8 21, i16 0, ptr null }, align 4
@"reflect/methods.Implements(reflect.Type) bool" = internal constant i8 0, align 1

define i1 @main.isError(ptr %typ.typecode, ptr %typ.value, ptr %context) {
entry:
  %0 = call i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(ptr %typ.value)
  ret i1 %0
}

define i1 @main.isUnknown(ptr %typ.typecode, ptr %typ.value, ptr %itf.typecode, ptr %itf.value, ptr %context) {
entry:
  %result = call i1 @"reflect.Type.Implements$invoke"(ptr %typ.value, ptr %itf.typecode, ptr %itf.value, ptr %typ.typecode, ptr undef)
  ret i1 %result
}

declare i1 @"reflect.Type.Implements$invoke"(ptr, ptr, ptr, ptr, ptr) #0

declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(ptr) #1

attributes #0 = { "tinygo-invoke"="reflect/methods.Implements(reflect.Type) bool" "tinygo-methods"="reflect/methods.Align() int; reflect/methods.Implements(reflect.Type) bool" }
attributes #1 = { "tinygo-methods"="reflect/methods.Error() string" }


================================================
FILE: transform/testdata/reflect.go
================================================
package main

// This file tests the type codes assigned by the reflect lowering pass.
// This test is not complete, most importantly, sidetables are not currently
// being tested.

import (
	"reflect"
	"unsafe"
)

const (
	// See the top of src/reflect/type.go
	prefixChan      = 0b0001
	prefixInterface = 0b0011
	prefixPtr       = 0b0101
	prefixSlice     = 0b0111
	prefixArray     = 0b1001
	prefixFunc      = 0b1011
	prefixMap       = 0b1101
	prefixStruct    = 0b1111
)

func main() {
	// Check for some basic types.
	assertType(3, uintptr(reflect.Int)<<1)
	assertType(uint8(3), uintptr(reflect.Uint8)<<1)
	assertType(byte(3), uintptr(reflect.Uint8)<<1)
	assertType(int64(3), uintptr(reflect.Int64)<<1)
	assertType("", uintptr(reflect.String)<<1)
	assertType(3.5, uintptr(reflect.Float64)<<1)
	assertType(unsafe.Pointer(nil), uintptr(reflect.UnsafePointer)<<1)

	// Check for named types: they are given names in order.
	// They are sorted in reverse, for no good reason.
	const intNum = uintptr(reflect.Int) << 1
	assertType(namedInt1(0), (3<<6)|intNum)
	assertType(namedInt2(0), (2<<6)|intNum)
	assertType(namedInt3(0), (1<<6)|intNum)

	// Check for some "prefix-style" types.
	assertType(make(chan int), (intNum<<5)|prefixChan)
	assertType(new(int), (intNum<<5)|prefixPtr)
	assertType([]int{}, (intNum<<5)|prefixSlice)
}

type (
	namedInt1 int
	namedInt2 int
	namedInt3 int
)

// Pseudo call that is being checked by the code in reflect_test.go.
// After reflect lowering, the type code as part of the interface should match
// the asserted type code.
func assertType(itf interface{}, assertedTypeCode uintptr)


================================================
FILE: transform/testdata/stacksize.ll
================================================
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"

declare i32 @"internal/task.getGoroutineStackSize"(i32, ptr, ptr)

declare void @"runtime.run$1$gowrapper"(ptr)

declare void @"internal/task.start"(i32, ptr, i32)

define void @Reset_Handler() {
entry:
  %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"runtime.run$1$gowrapper" to i32), ptr undef, ptr undef)
  call void @"internal/task.start"(i32 ptrtoint (ptr @"runtime.run$1$gowrapper" to i32), ptr undef, i32 %stacksize)
  ret void
}


================================================
FILE: transform/testdata/stacksize.out.ll
================================================
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"

@"internal/task.stackSizes" = global [1 x i32] [i32 1024], section ".tinygo_stacksizes", align 4
@llvm.used = appending global [2 x ptr] [ptr @"internal/task.stackSizes", ptr @"runtime.run$1$gowrapper"]

declare i32 @"internal/task.getGoroutineStackSize"(i32, ptr, ptr)

declare void @"runtime.run$1$gowrapper"(ptr)

declare void @"internal/task.start"(i32, ptr, i32)

define void @Reset_Handler() {
entry:
  %stacksize1 = load i32, ptr @"internal/task.stackSizes", align 4
  call void @"internal/task.start"(i32 ptrtoint (ptr @"runtime.run$1$gowrapper" to i32), ptr undef, i32 %stacksize1)
  ret void
}


================================================
FILE: transform/testdata/stringequal.ll
================================================
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"

@zeroString = constant [0 x i8] zeroinitializer

declare i1 @runtime.stringEqual(ptr, i32, ptr, i32, ptr)

define i1 @main.stringCompareEqualConstantZero(ptr %s1.data, i32 %s1.len, ptr %context) {
entry:
  %0 = call i1 @runtime.stringEqual(ptr %s1.data, i32 %s1.len, ptr @zeroString, i32 0, ptr undef)
  ret i1 %0
}

define i1 @main.stringCompareUnequalConstantZero(ptr %s1.data, i32 %s1.len, ptr %context) {
entry:
  %0 = call i1 @runtime.stringEqual(ptr %s1.data, i32 %s1.len, ptr @zeroString, i32 0, ptr undef)
  %1 = xor i1 %0, true
  ret i1 %1
}


================================================
FILE: transform/testdata/stringequal.out.ll
================================================
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"

@zeroString = constant [0 x i8] zeroinitializer

declare i1 @runtime.stringEqual(ptr, i32, ptr, i32, ptr)

define i1 @main.stringCompareEqualConstantZero(ptr %s1.data, i32 %s1.len, ptr %context) {
entry:
  %0 = icmp eq i32 %s1.len, 0
  ret i1 %0
}

define i1 @main.stringCompareUnequalConstantZero(ptr %s1.data, i32 %s1.len, ptr %context) {
entry:
  %0 = icmp eq i32 %s1.len, 0
  %1 = xor i1 %0, true
  ret i1 %1
}


================================================
FILE: transform/testdata/stringtobytes.ll
================================================
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"

@str = constant [6 x i8] c"foobar"

declare { ptr, i64, i64 } @runtime.stringToBytes(ptr, i64)

declare void @printByte(i8)

declare void @printSlice(ptr nocapture readonly, i64, i64)

declare void @writeToSlice(ptr nocapture, i64, i64)

; Test that runtime.stringToBytes can be fully optimized away.
define void @testReadOnly() {
entry:
  %0 = call fastcc { ptr, i64, i64 } @runtime.stringToBytes(ptr @str, i64 6)
  %1 = extractvalue { ptr, i64, i64 } %0, 0
  %2 = extractvalue { ptr, i64, i64 } %0, 1
  %3 = extractvalue { ptr, i64, i64 } %0, 2
  call fastcc void @printSlice(ptr %1, i64 %2, i64 %3)

  ; print(slice[0])
  %indexaddr.ptr1 = extractvalue { ptr, i64, i64 } %0, 0
  %4 = getelementptr inbounds i8, ptr %indexaddr.ptr1, i64 0
  %5 = load i8, ptr %4, align 1
  call fastcc void @printByte(i8 %5)
  ret void
}

; Test that even though the slice is written to, some values can be propagated.
define void @testReadWrite() {
entry:
  %0 = call fastcc { ptr, i64, i64 } @runtime.stringToBytes(ptr @str, i64 6)
  %1 = extractvalue { ptr, i64, i64 } %0, 0
  %2 = extractvalue { ptr, i64, i64 } %0, 1
  %3 = extractvalue { ptr, i64, i64 } %0, 2
  call fastcc void @writeToSlice(ptr %1, i64 %2, i64 %3)
  ret void
}

; Test that pointer values are never propagated if there is even a single write
; to the pointer value (but len/cap values still can be).
define void @testReadSome() {
entry:
  %s = call fastcc { ptr, i64, i64 } @runtime.stringToBytes(ptr @str, i64 6)
  %s.ptr = extractvalue { ptr, i64, i64 } %s, 0
  %s.len = extractvalue { ptr, i64, i64 } %s, 1
  %s.cap = extractvalue { ptr, i64, i64 } %s, 2
  call fastcc void @writeToSlice(ptr %s.ptr, i64 %s.len, i64 %s.cap)
  %s.ptr2 = extractvalue { ptr, i64, i64 } %s, 0
  %s.len2 = extractvalue { ptr, i64, i64 } %s, 1
  %s.cap2 = extractvalue { ptr, i64, i64 } %s, 2
  call fastcc void @printSlice(ptr %s.ptr2, i64 %s.len2, i64 %s.cap2)
  ret void
}


================================================
FILE: transform/testdata/stringtobytes.out.ll
================================================
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"

@str = constant [6 x i8] c"foobar"

declare { ptr, i64, i64 } @runtime.stringToBytes(ptr, i64)

declare void @printByte(i8)

declare void @printSlice(ptr nocapture readonly, i64, i64)

declare void @writeToSlice(ptr nocapture, i64, i64)

define void @testReadOnly() {
entry:
  call fastcc void @printSlice(ptr @str, i64 6, i64 6)
  %0 = getelementptr inbounds i8, ptr @str, i64 0
  %1 = load i8, ptr %0, align 1
  call fastcc void @printByte(i8 %1)
  ret void
}

define void @testReadWrite() {
entry:
  %0 = call fastcc { ptr, i64, i64 } @runtime.stringToBytes(ptr @str, i64 6)
  %1 = extractvalue { ptr, i64, i64 } %0, 0
  call fastcc void @writeToSlice(ptr %1, i64 6, i64 6)
  ret void
}

define void @testReadSome() {
entry:
  %s = call fastcc { ptr, i64, i64 } @runtime.stringToBytes(ptr @str, i64 6)
  %s.ptr = extractvalue { ptr, i64, i64 } %s, 0
  call fastcc void @writeToSlice(ptr %s.ptr, i64 6, i64 6)
  %s.ptr2 = extractvalue { ptr, i64, i64 } %s, 0
  call fastcc void @printSlice(ptr %s.ptr2, i64 6, i64 6)
  ret void
}


================================================
FILE: transform/transform.go
================================================
// Package transform contains transformation passes for the TinyGo compiler.
// These transformation passes may be optimization passes or lowering passes.
//
// Optimization passes transform the IR in such a way that they increase the
// performance of the generated code and/or help the LLVM optimizer better do
// its job by simplifying the IR. This usually means that certain
// TinyGo-specific runtime calls are removed or replaced with something simpler
// if that is a valid operation.
//
// Lowering passes are usually required to run. One example is the interface
// lowering pass, which replaces stub runtime calls to get an interface method
// with the method implementation (either a direct call or a thunk).
package transform

import (
	"github.com/tinygo-org/tinygo/compileopts"
	"tinygo.org/x/go-llvm"
)

// AddStandardAttributes is a helper function to add standard function
// attributes to a function. For example, it adds optsize when requested from
// the -opt= compiler flag.
func AddStandardAttributes(fn llvm.Value, config *compileopts.Config) {
	ctx := fn.Type().Context()
	_, _, sizeLevel := config.OptLevel()
	if sizeLevel >= 1 {
		fn.AddFunctionAttr(ctx.CreateEnumAttribute(llvm.AttributeKindID("optsize"), 0))
	}
	if sizeLevel >= 2 {
		fn.AddFunctionAttr(ctx.CreateEnumAttribute(llvm.AttributeKindID("minsize"), 0))
	}
	if config.CPU() != "" {
		fn.AddFunctionAttr(ctx.CreateStringAttribute("target-cpu", config.CPU()))
	}
	if config.Features() != "" {
		fn.AddFunctionAttr(ctx.CreateStringAttribute("target-features", config.Features()))
	}
}


================================================
FILE: transform/transform_test.go
================================================
package transform_test

// This file defines some helper functions for testing transforms.

import (
	"flag"
	"go/token"
	"go/types"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/tinygo-org/tinygo/compileopts"
	"github.com/tinygo-org/tinygo/compiler"
	"github.com/tinygo-org/tinygo/loader"
	"tinygo.org/x/go-llvm"
)

var update = flag.Bool("update", false, "update transform package tests")

var defaultTestConfig = &compileopts.Config{
	Target:  &compileopts.TargetSpec{},
	Options: &compileopts.Options{Opt: "2"},
}

// testTransform runs a transformation pass on an input file (pathPrefix+".ll")
// and checks whether it matches the expected output (pathPrefix+".out.ll"). The
// output is compared with a fuzzy match that ignores some irrelevant lines such
// as empty lines.
func testTransform(t *testing.T, pathPrefix string, transform func(mod llvm.Module)) {
	// Read the input IR.
	ctx := llvm.NewContext()
	defer ctx.Dispose()
	buf, err := llvm.NewMemoryBufferFromFile(pathPrefix + ".ll")
	os.Stat(pathPrefix + ".ll") // make sure this file is tracked by `go test` caching
	if err != nil {
		t.Fatalf("could not read file %s: %v", pathPrefix+".ll", err)
	}
	mod, err := ctx.ParseIR(buf)
	if err != nil {
		t.Fatalf("could not load module:\n%v", err)
	}
	defer mod.Dispose()

	// Perform the transform.
	transform(mod)

	// Check for any incorrect IR.
	err = llvm.VerifyModule(mod, llvm.PrintMessageAction)
	if err != nil {
		t.Fatal("IR verification failed")
	}

	// Get the output from the test and filter some irrelevant lines.
	actual := mod.String()
	actual = actual[strings.Index(actual, "\ntarget datalayout = ")+1:]

	if *update {
		err := os.WriteFile(pathPrefix+".out.ll", []byte(actual), 0666)
		if err != nil {
			t.Error("failed to write out new output:", err)
		}
	} else {
		// Read the expected output IR.
		out, err := os.ReadFile(pathPrefix + ".out.ll")
		if err != nil {
			t.Fatalf("could not read output file %s: %v", pathPrefix+".out.ll", err)
		}

		// See whether the transform output matches with the expected output IR.
		expected := string(out)
		if !fuzzyEqualIR(expected, actual) {
			t.Logf("output does not match expected output:\n%s", actual)
			t.Fail()
		}
	}
}

// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly
// equal. That means, only relevant lines are compared (excluding comments
// etc.).
func fuzzyEqualIR(s1, s2 string) bool {
	lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n"))
	lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n"))
	if len(lines1) != len(lines2) {
		return false
	}
	for i, line1 := range lines1 {
		line2 := lines2[i]
		if line1 != line2 {
			return false
		}
	}

	return true
}

// filterIrrelevantIRLines removes lines from the input slice of strings that
// are not relevant in comparing IR. For example, empty lines and comments are
// stripped out.
func filterIrrelevantIRLines(lines []string) []string {
	var out []string
	for _, line := range lines {
		line = strings.Split(line, ";")[0]    // strip out comments/info
		line = strings.TrimRight(line, "\r ") // drop '\r' on Windows and remove trailing spaces from comments
		if line == "" {
			continue
		}
		if strings.HasPrefix(line, "source_filename = ") {
			continue
		}
		out = append(out, line)
	}
	return out
}

// compileGoFileForTesting compiles the given Go file to run tests against.
// Only the given Go file is compiled (no dependencies) and no optimizations are
// run.
// If there are any errors, they are reported via the *testing.T instance.
func compileGoFileForTesting(t *testing.T, filename string) llvm.Module {
	target, err := compileopts.LoadTarget(&compileopts.Options{GOOS: "linux", GOARCH: "386"})
	if err != nil {
		t.Fatal("failed to load target:", err)
	}
	config := &compileopts.Config{
		Options: &compileopts.Options{},
		Target:  target,
	}
	compilerConfig := &compiler.Config{
		Triple:             config.Triple(),
		GOOS:               config.GOOS(),
		GOARCH:             config.GOARCH(),
		CodeModel:          config.CodeModel(),
		RelocationModel:    config.RelocationModel(),
		Scheduler:          config.Scheduler(),
		AutomaticStackSize: config.AutomaticStackSize(),
		Debug:              true,
		PanicStrategy:      config.PanicStrategy(),
	}
	machine, err := compiler.NewTargetMachine(compilerConfig)
	if err != nil {
		t.Fatal("failed to create target machine:", err)
	}
	defer machine.Dispose()

	// Load entire program AST into memory.
	lprogram, err := loader.Load(config, filename, types.Config{
		Sizes: compiler.Sizes(machine),
	})
	if err != nil {
		t.Fatal("failed to create target machine:", err)
	}
	err = lprogram.Parse()
	if err != nil {
		t.Fatal("could not parse", err)
	}

	// Compile AST to IR.
	program := lprogram.LoadSSA()
	pkg := lprogram.MainPkg()
	mod, errs := compiler.CompilePackage(filename, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
	if errs != nil {
		for _, err := range errs {
			t.Error(err)
		}
		t.FailNow()
	}
	return mod
}

// getPosition returns the position information for the given value, as far as
// it is available.
func getPosition(val llvm.Value) token.Position {
	if !val.IsAInstruction().IsNil() {
		loc := val.InstructionDebugLoc()
		if loc.IsNil() {
			return token.Position{}
		}
		file := loc.LocationScope().ScopeFile()
		return token.Position{
			Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
			Line:     int(loc.LocationLine()),
			Column:   int(loc.LocationColumn()),
		}
	} else if !val.IsAFunction().IsNil() {
		loc := val.Subprogram()
		if loc.IsNil() {
			return token.Position{}
		}
		file := loc.ScopeFile()
		return token.Position{
			Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
			Line:     int(loc.SubprogramLine()),
		}
	} else {
		return token.Position{}
	}
}


================================================
FILE: transform/util.go
================================================
package transform

// This file contains utilities used across transforms.

import (
	"tinygo.org/x/go-llvm"
)

// Check whether all uses of this param as parameter to the call have the given
// flag. In most cases, there will only be one use but a function could take the
// same parameter twice, in which case both must have the flag.
// A flag can be any enum flag, like "readonly".
func hasFlag(call, param llvm.Value, kind string) bool {
	fn := call.CalledValue()
	if fn.IsAFunction().IsNil() {
		// This is not a function but something else, like a function pointer.
		return false
	}
	kindID := llvm.AttributeKindID(kind)
	for i := 0; i < fn.ParamsCount(); i++ {
		if call.Operand(i) != param {
			// This is not the parameter we're checking.
			continue
		}
		index := i + 1 // param attributes start at 1
		attr := fn.GetEnumAttributeAtIndex(index, kindID)
		if attr.IsNil() {
			// At least one parameter doesn't have the flag (there may be
			// multiple).
			return false
		}
	}
	return true
}

// isReadOnly returns true if the given value (which must be of pointer type) is
// never stored to, and false if this cannot be proven.
func isReadOnly(value llvm.Value) bool {
	uses := getUses(value)
	for _, use := range uses {
		switch {
		case !use.IsAGetElementPtrInst().IsNil():
			if !isReadOnly(use) {
				return false
			}
		case !use.IsACallInst().IsNil():
			if !hasFlag(use, value, "readonly") {
				return false
			}
		case !use.IsALoadInst().IsNil():
			// Loads are read-only.
		default:
			// Unknown instruction, might not be readonly.
			return false
		}
	}
	return true
}


================================================
FILE: util_unix.go
================================================
//go:build !windows

package main

// This file contains utility functions for Unix-like systems (e.g. Linux).

import (
	"os/exec"
	"syscall"
)

// setCommandAsDaemon makes sure this command does not receive signals sent to
// the parent.
func setCommandAsDaemon(daemon *exec.Cmd) {
	// https://stackoverflow.com/a/35435038/559350
	daemon.SysProcAttr = &syscall.SysProcAttr{
		Setpgid: true,
		Pgid:    0,
	}
}


================================================
FILE: util_windows.go
================================================
package main

// This file contains utility functions for Windows.

import (
	"os/exec"
	"syscall"

	"golang.org/x/sys/windows"
)

// setCommandAsDaemon makes sure this command does not receive signals sent to
// the parent.
func setCommandAsDaemon(daemon *exec.Cmd) {
	daemon.SysProcAttr = &syscall.SysProcAttr{
		CreationFlags: windows.DETACHED_PROCESS,
	}
}