Repository navigation
Releases: modernc-org/ogo
Release list
OctoGo v0.56.2 (preview)
OctoGo v0.56.2 (preview) - prebuilt binaries for linux (amd64, arm64), windows/amd64 and macOS (arm64, amd64). Unzip and put ogo on PATH; the C backend and the board loader are embedded, so there is nothing else to install. ogo help starts you off.
A correction to v0.56.1, released a few hours earlier: its notes say a 64-bit value handed to a function value arrives right. That fix was partial. v0.56.1 bound the operations and constants the fault was found with, and a widening conversion or a call's result handed there still arrived wrong, in silence. This release finishes that fix, and fixes an older silent wrong answer the fuzzer found on the way. Upgrade if a program passes int64 or uint64 values to callbacks or calls with 64-bit constants. 3 commits; every entry is in CHANGELOG.md.
Fixed
- Wrong answers, all of them silent, measured on a P2-EDGE:
- A widening conversion or a call's result handed to a function value as a 64-bit argument arrived with garbage in its high word:
cv(int64(i32))printed 1623844961 for 9, andcv(id(3))andu(uint64(x))the same, in v0.56.1 and before. Every 64-bit argument of a call through a function value is bound to a variable first now, but one that is a variable already. - A 64-bit constant beside an argument that does something was cut to its low word:
take(1<<40, side())printed 1 for Go's 1099511627777, through a method, an interface and a deferred call too, on the host as on the board, in every release.
- A widening conversion or a call's result handed to a function value as a 64-bit argument arrived with garbage in its high word:
- A function value's 64-bit result reads in an operation,
w(x) + 1,w(x) >> 32,u(x) % 7, which the C compiler refused.
Toolchain and verification
ogo smithcalls through function values held in package variables, the form a checked build calls through its nil check, hands them a single 64-bit argument of every shape, and reads their 64-bit results in operations. Built by v0.56.1, every seed of the first seven carrying the new shapes fails on the board.- Under this release's compiler the host suite passes, and so does
make boardon a P2-EDGE: 1012 tests, the 971 run cases among them; fuzzer seeds 1-2000 pass on the host, and seeds 1-200 on the board but 13 that outgrow a cog's registers. p2-11, the PDP-11/40 emulator written in OctoGo, builds byte for byte as v0.56.0 built it, and its 72 tests pass on the board.
The five zips are cross-compiled from one host; see SHA256SUMS. As before, only the linux/amd64 binary has been run before publishing.
OctoGo v0.56.1 (preview)
OctoGo v0.56.1 (preview) - prebuilt binaries for linux (amd64, arm64), windows/amd64 and macOS (arm64, amd64). Unzip and put ogo on PATH; the C backend and the board loader are embedded, so there is nothing else to install. ogo help starts you off.
This release follows v0.56.0 by a day's work, 7 commits, because it fixes two silent wrong answers v0.56.0 has, as every release before it had: arguments of 64-bit operations handed to a function the target's C compiler passed wrong. Upgrade if a program passes an int64 or a uint64 computed where the call stands. Both were found writing a firmware that calls Spin2 driver objects, and by the fuzzer part written for the first. Every entry is in CHANGELOG.md; the headlines follow.
Fixed
- Wrong answers, all of them silent, measured on a P2-EDGE, the host right:
- An argument after a 64-bit operation reached the callee converted to the wrong type, or not at all: for a
scale(a int64, gain float32, k int64),scale(-m, gain, k)handed the gain over as 0 for 2.5, and an integer constant to a float parameter arrived as its bits. A negation, any binary operator, a shift or a negative constant followed by another argument did this, through a method, an interface, a function value and a variadic call alike, and so did a 64-bit value receiver,(q * 2).Mix(gain, k). - A 64-bit operation handed to a function value arrived wrong in any place:
cb(m, 30, m*3)printed 110104252 for 868, andc1(m*3)with one parameter the same. A positive 64-bit constant there,cb(3, 1<<40), did not build.
- An argument after a 64-bit operation reached the callee converted to the wrong type, or not at all: for a
- A 64-bit value shifted left by 32 passed to a function builds,
f(uint64(rpm)<<32, d);ogo buildrefused it over a warning about code that was right. - A struct holding an empty struct or an interface compares, and so does an array of either, and a method called on a literal of an empty struct type,
E{}.Two(), builds.
Language
//go:noescapeabove a function a Spin2 object implements says its method keeps nothing it is handed, as Go's directive says it of a function its assembly implements: a checksum or a copy written in PASM may be handed a local buffer, which was refused as kept by a driver's cog. The compiler takes the directive's word, as Go's does.
Behaviour changes
- A
//go:noescapewhere gc refuses one is refused, in gc's words: "misplaced compiler directive" above another declaration, in a body or after code on its line, and "can only use //go:noescape with external func implementations" above a function with a body. The comment had meant nothing before.
Toolchain and verification
ogo fmtwrites doc comments as gofmt does, a directive moved to the end of its comment among them, and spaces an array type's length at the head of a literal,[Rows + 1]string{...}, as gofmt does. Every source of the tree and of p2-11 now lays out as gofmt lays it out.ogo smithhands 64-bit operations on before arguments of other types, which found the second fault above on its first board run.- Under this release's compiler the host suite passes, and so does
make boardon a P2-EDGE: 1009 tests, the 968 run cases among them; fuzzer seeds 1-200 pass on the board, but 13 that outgrow a cog's registers. p2-11, the PDP-11/40 emulator written in OctoGo, builds byte for byte as v0.56.0 built it, and its 72 tests pass on the board.
The five zips are cross-compiled from one host; see SHA256SUMS. As before, only the linux/amd64 binary has been run before publishing.
OctoGo v0.56.0 (preview)
OctoGo v0.56.0 (preview) - prebuilt binaries for linux (amd64, arm64), windows/amd64 and macOS (arm64, amd64). Unzip and put ogo on PATH; the C backend and the board loader are embedded, so there is nothing else to install. ogo help starts you off.
This binary release carries five tags since v0.51.0: v0.52.0, v0.53.0, v0.54.0, v0.55.0 and v0.56.0, 68 commits in all. It breaks every program that uses a channel: a channel is now made as Go makes one, so var ch chan int is a nil channel, and a program written for v0.51.0 needs a make where it declares one (below). Beside that, ogo test gained benchmarks and subtests, there is an errors package, and a run of silent wrong answers on the board is fixed, most of them found by domain programs written as plain Go and by their mutants, judged against Go on the host and on a P2-EDGE. Every entry is in CHANGELOG.md; the headlines follow.
Channels, as Go makes them
make(chan T)makes a channel, and a declared one is nil (v0.52.0). Writevar ch = make(chan int)at package level andch := make(chan int)in a function; a struct's channel field and an array of channels are made element by element. A channel variable that nothing makes but that a send, a receive, acloseor arangeuses is refused, "channel ch is never made", with the line to write. On the board it would block for ever in silence. A nil channel behaves as in Go: a send or a receive blocks for ever, a select clause on it is never ready, and closing it panics.- Buffered channels,
make(chan T, n)(v0.54.0), for a constantn: the slots are declared beside the channel where themakestands, static at package level and the frame's in a function, with no heap.lenandcapwork on every channel. - Main's outermost block outlives every cog (v0.52.0), so what
maindeclares directly may go to a goroutine:var buf [64]byte; go fill(buf[:])andch := make(chan int); go worker(ch)inmain. A channel made in any other function stays in that function, as a local's address does.
Language
- Benchmarks and subtests (v0.55.0):
func BenchmarkX(b *testing.B)withb.N,b.Loop,b.Run, the timers,SetBytesandReportMetric, andt.Run, all run on the board. They print whatgo test -v -benchprints, held to go test's own output byte for byte.-bench,-benchtime,-benchmemand-timeoutare go test's flags, and-runselects subtests as go test does. - The
errorspackage (v0.55.0):New,Is,As,UnwrapandErrUnsupported, checked by running the same programs under Go and on the board.Newmakes its error where the call stands, there being no heap: a sentinelvar ErrTimeout = errors.New("timeout")is static.Joinandfmt.Errorfare not provided. string(b)of a byte slice where nothing keeps the string reads the bytes where they are (v0.56.0), as Go's compiler does:string(word) == "SET",switch string(word) { case "SET": ... }andlen(string(b)). The same goes for[]byte(s)and[]rune(s)ranged over, measured, or copied from. Each had been refused as needing allocation, which is how a command word read from a device is dispatched.- A method value of a field or an element binds its receiver (v0.56.0):
dev.Reg.get,rs[2].get,[...]func(bool){inst.motor.Enable, inst.heater.Enable}, wherever the receiver is reached from a package variable through struct fields and constant array indexes. - More of Go's forms build (v0.55.0): an alias of a type written out,
type Buf = []byte; a type switch case of an interface written out,case interface{ Unwrap() error }:; anddefer report(divmod(17, 5)),go worker(open(3))andprintln(divmod(17, 5))handed a call of several results.
Behaviour changes
Where this release treats a program differently from v0.51.0, beyond the channels above.
- A goroutine's panic stops the other cogs as soon as its message is written (v0.55.0), where
mainwent on printing for the message's ten-millisecond drain. - A program whose main cog reaches a recursion is refused when the deepest chain of direct calls does not fit in Hub RAM (v0.55.0). Such a program had built and printed nothing on the board.
- Mistakes Go refuses are refused in many more places (v0.52.0 to v0.56.0), in Go's words, most of them found by mutating programs and asking Go. So are more ways of leaving a local's address where it outlives its function: through a call of several results passed whole, a callee storing through a parameter, a method called on what a call returns, and an interface holding the address.
Fixed
- Wrong answers, all of them silent, measured on a P2-EDGE:
- A value of several words, such as a string, a slice, an interface, an int64 or a small struct, passed where its expression calls was evaluated once per word.
takeS(strs[idx()])calledidxtwice (v0.56.0; fixed in the backend this release carries). len(<-ch)of a string channel printed garbage (v0.56.0).hist[r.ch][k]++, the increment of a two-dimensional element whose row index calls or reads through a pointer, stored nothing (v0.56.0).- Function values called in parentheses,
(f)(1) << uint((f)(2)), ran in C's order rather than Go's (v0.56.0). - A method of a nil
*Builderwrote into the first longs of Hub RAM, where the program is; it panics now, as Go's does (v0.56.0). %qand%Uof a 64-bit expression printed garbage (v0.55.0).- A program's own
p2.NewLock()could return a lock that its channels used, and a cog holding it while it sent spun for ever (v0.53.0).
- A value of several words, such as a string, a slice, an interface, an int64 or a small struct, passed where its expression calls was evaluated once per word.
- A call returning
(T, bool),(T, error)or a larger struct no longer allocates (v0.55.0). The backend handed such a result back in a copy from a garbage-collected heap of 1 KB: 1689 clocks a call at least, and about one call in sixty paused for 5.5 ms to collect (71 ms in a program the size of p2-11). Such results are written through an out parameter now, at 141 clocks every time, and a program that used the heap is about 8 KB smaller.
Toolchain and verification
- The C backend is regenerated at spin2cpp's master of 2026-10-13 (v0.55.0). It includes two of our changes that upstream merged, and v0.56.0 adds two fixes of our own, carried ahead of upstream. The five platforms' backends compile the reproducers, the run cases and 400 fuzzer programs exactly as a native build of the same backend does.
ogo testprints whatgo test -vprints where tools read it (v0.56.0): each result's time,PASS, and the package's line by its import path with the run's time, so go-junit-report and an editor's test view read it.ogo smithwrites values of several words where a call stands, embedded structs and promoted methods, variadic calls and method values, calls into the strings, bytes and errors packages, and, with-o, a program of two packages.- Under this release's compiler the host suite passes, and so does
make boardon a P2-EDGE: 1006 tests, the 965 run cases among them. p2-11, the PDP-11/40 emulator written in OctoGo, builds without a warning, and its 72 tests pass on the board.
The five zips are cross-compiled from one host; see SHA256SUMS. As before, only the linux/amd64 binary has been run before publishing.
OctoGo v0.51.0 (preview)
OctoGo v0.51.0 (preview) - prebuilt binaries for linux (amd64, arm64), windows/amd64 and macOS (arm64, amd64). Unzip and put ogo on PATH; the C backend and the board loader are embedded, so there is nothing else to install. ogo help starts you off.
This binary release carries five tags since v0.48.0: v0.48.1, v0.49.0, v0.49.1, v0.50.0 and v0.51.0, 134 commits in all. Most of it came from turning Go itself into the oracle: run cases and domain programs mutated by tens of thousands of single edits, each judged against what Go accepts, prints and refuses, on the host and on a P2-EDGE. An outside review of the language design led to the rest: Go's results for the Builder's writes, a select that takes its ready clauses in turn, and DIFFERENCES.md, the page of what builds as Go and runs differently. Every entry is in CHANGELOG.md; the headlines follow.
Language
testing.Thas Go'sLog,Logf,Error,Errorf,Fatal,Fatalf,Skip,Skipf,FailNow,SkipNow,NameandHelper(v0.51.0). The output reads asgo test -v's does, andFailNowstops a test however deep it is called.unsafe.Sizeof,Alignof,Offsetof,AddandSlice(v0.50.0). The layout constants are the target's, read off the backend's own rules, and each size a program asks for is checked against the backend at build time.panictakes any value Go's runtime can write (v0.49.0),panic(err)above all, where it took only a string.- A type switch takes an init statement (v0.50.0),
switch s := pick(); v := s.(type). - A channel of receive-only channels is written as Go writes it (v0.51.0):
chan (<-chan T),chan<- <-chan T. - More of Go's expression shapes build (v0.49.0): a type assertion in the middle of a chain on any operand, a method of several results on a literal, a method of an interface reached by a call, a constant conversion to a string or a bool type, and a channel printed.
Behaviour changes
Where this release treats a program differently from v0.48.0.
- A program ends as Go's does, every cog with it (v0.50.0). A panic on any cog stops every cog, the drivers a Spin2 object started among them, and a return from
mainstops every cog too. Before, the other cogs ran on. - The
Builder's writes return Go's results (v0.51.0):WriteString,WriteandWriteRunereturn the bytes written and anerror, andWriteByteanerror, asstrings.Builderdeclares them. A write that does not fit returns "short write", where it truncated in silence. - A
selecttakes its ready clauses in turn (v0.51.0), where the first in source order won every time it was ready: on a P2-EDGE, 1000 of 1000 values from the first of two busy channels, 500 of each now. A send clause on a closed channel panics only where it is chosen, as in Go. ogo build,ogo runandogo testfail when the C backend warns about the C ogo wrote (v0.49.0). Every kind of warning that had reached a build was wrong code built in silence.--allow-backend-warningsbuilds as before.- A goroutine's stack is sized from what it needs (v0.50.0), read off the backend's listing, where every slot was 256 longs and an overrun was reported only if the goroutine ended. A goroutine that recurses is refused unless
--gostacksizes the stacks (v0.51.0), and so is a program whose main stack does not fit in Hub RAM (v0.50.0). t.Skip()stops the test, andogo testprints go test's=== RUNline before each test (v0.51.0).- Mistakes Go refuses are refused in many more places (v0.49.0 to v0.51.0). Of the mutants Go refuses, nearly all that this compiler took are refused now, in Go's words; most had reached the C compiler, and some built.
Fixed
- Wrong answers, all of them silent, measured on a P2-EDGE:
uint64(x) >> 32of a value of 32 bits or fewer printed garbage, and a 32-bit unsigned expression with a zero on its left lost its high word when widened (v0.50.0). Both are faults of the target's C compiler, now worked around.- The backend's CORDIC reordering moved a conditional move past a multiplication, so a value computed before it read as one computed after (v0.50.0; fixed in the backend this release carries).
- A function value from a table in a package variable compared nil (v0.49.1).
- An untyped constant computed from 64-bit constants was passed as two words (v0.48.1).
- An element of a two-dimensional table of 64-bit integers indexed by a call was read and written wrong (v0.49.0).
- A range key the body writes, a
continueor adeferin a select clause, a call through a nil function value, a 64-bit index and amakelength past its capacity each did something other than Go (v0.49.0). - A local's address could be left in a package variable through a loop's next pass, through a slice viewing the local, or through a pointer to the local (v0.50.0).
- A printf of one argument wrote its text before evaluating the argument, and
%Tprinted a directional channel without its direction (v0.51.0).
- The checker knows more types: a received value, another package's variable, a value two steps from its variable, what a call returns and the types three packages share. So the mistakes under Behaviour changes are refused in Go's words, and not handed to the C compiler.
Toolchain and verification
- The C backend is regenerated at spin2cpp's master of 2026-10-05 (v0.50.0), with upstream's fixes of flexprop#114 and #115 and one fix of our own carried ahead of upstream. The five platforms' backends compile the reproducers, the run cases and 400 fuzzer programs exactly as a native build of the same backend does.
ogo smithpasses structs and arrays by value and through function values and interfaces, indexes through 64-bit values, and converts and widens values between the sized types.- DIFFERENCES.md lists what builds as both OctoGo and Go and runs differently. Each entry is a test, run on the host, on the board and under Go.
- Under this release's compiler the host suite passes, and so does
make boardon a P2-EDGE: 950 cases, among them the whole run-case corpus. So does p2-11, the PDP-11/40 emulator written in OctoGo: its tests on the board, RT-11 and Unix V6 saying on the board what they say under SimH, and its benchmark unchanged from v0.50.0.
The five zips are cross-compiled from one host; see SHA256SUMS. As before, only the linux/amd64 binary has been run before publishing.
OctoGo v0.48.0 (preview)
OctoGo v0.48.0 (preview) - prebuilt binaries for linux (amd64, arm64), windows/amd64 and macOS (arm64, amd64). Unzip and put ogo on PATH; the C backend and the board loader are embedded, so there is nothing else to install. ogo help starts you off.
This binary release carries three tags since v0.46.0: v0.47.0, v0.47.1 and v0.48.0, 43 commits in all. Again much of it was asked for by p2-11, the PDP-11/40 emulator written in OctoGo. Its VGA console needed a driver written in Spin2. Its new CPU core, PASM on a cog of its own with the rest of the machine in OctoGo, needed memory shared between cogs to be defined. With that core, p2-11 runs its benchmark about one and a half times as fast as a real 11/40. Every entry is in CHANGELOG.md; the headlines follow.
Language
- A function may be implemented by a Spin2 object its package carries (v0.47.0), as a Go function may be implemented in assembly. A function declared without a body calls the PUB method of its name in a .spin2 file beside the package's .ogo files. That is how OctoGo uses a driver whose PASM runs on a cog of its own: p2-11's VGA console runs Eric Smith's tile driver as it is, and its PDP-11 CPU core is such an object.
unsafe.Pointer(v0.47.0), and a conversion to a pointer to a type written out (v0.48.0),(*[16]uint32)(unsafe.Pointer(addr)), so a block of Hub RAM is read as an array. A slice converts to a pointer to an array of its element, as in Go.- Memory shared between cogs is defined (v0.48.0), where Go calls it a data race. A loop makes its reads of Hub RAM on every pass, a cog's writes reach Hub RAM in program order, and a value of 32 bits or fewer is read and written whole. So a cog may spin on a flag another cog raises, and one writer and one reader need no lock. See specs.go, "Memory shared between cogs".
p2.ReadLUTandp2.WriteLUT(v0.48.0) read and write a long of the LUT RAM of the cog running the code: 3 clocks to read and 2 to write, where a long of Hub RAM takes about 15 and 8 to 15.- A type switch and an assertion on a pointer to a predeclared type, an array or a slice (v0.48.0):
case *int:,case *[4]uint32:,case *[]byte:.
Behaviour changes
Where this release treats a program differently from v0.46.0. Each is a refusal, as Go refuses the program.
- A function declared without a body needs a .spin2 file to implement it (v0.47.0). With none, it is refused where it is declared, "missing function body", as Go refuses one with no assembly behind it. v0.46.0 refused it only where it was called, in the words of the P2's C compiler.
- More of the lifetime rules hold (v0.47.0). A function passing its parameter on to another package's function, storing it converted to a pointer type, or storing it into another package's variable keeps it. The address of an element of a slice that views this frame's storage is a reference to the frame. Each gap had left a local's address in package storage, in silence.
- Mistakes Go refuses are refused in more places (v0.48.0): with a pointer to an array, with another package's results, types and struct fields, in a type switch on an expression, and with what a call's result reaches.
Fixed
- Wrong answers, all of them silent, measured on a P2-EDGE:
- A
forthat assigns its variable,var i int; for i = 0; i < n; i++, ran as many times asnsaid when it began, even if its body loweredn: five passes where Go makes three. The backend counts such a loop down from the bound it reads once (flexprop#119). - A comparison the backend could decide, stored and then or-ed after another value, was false whatever the other held (fixed in the backend this release carries; flexprop#118).
- A package variable of a struct with an array of one- or two-byte elements was read in other bytes than it was initialized in (flexprop#116).
- A
gostatement's arguments were lost when the program had a package variable named like a member of the runtime's argument blocks (flexprop#117). - A byte of a string followed by a digit 0-7 was misread on the board (flexprop#115).
- Inside a package, a name of its own was read as the main package's variable of the same name (v0.47.1).
- A
- The checker knows more types: another package's types, results and struct fields, a pointer to an array past the pointer, a type switch's cases on an operand that is no bare name, and the steps past a call. So the mistakes under Behaviour changes are refused in Go's words, and not handed to the C compiler.
- A goroutine started on a function of the
p2package, or on one ofmath's,go p2.WriteByte(c), builds; it did not.
Toolchain and verification
- The C backend is regenerated with a fix of its own (v0.48.0), carried ahead of upstream for the miscompile of flexprop#118, and transpiled for the first time with ccgo v4.36.1 and libc v1.77.1. The five platforms' backends compile the reproducers, the run cases and the fuzzer's programs exactly as a native build of the same backend does.
- Checked builds are faster (v0.48.0). Arithmetic on
uint8anduint16is written with fewer conversions, and a checked build leaves out the nil and bounds checks the program proves cannot fail. The same emulator code, checked at 160 MHz, went from 120,478 to 130,044 instructions a second with the two together. ogo smithwrites functions of several returns, constants, and package tables of structs holding small arrays into every program.- Under this release's compiler the host suite passes, and so does
make boardon a P2-EDGE: 836 cases, among them the whole run-case corpus.
The five zips are cross-compiled from one host; see SHA256SUMS. As before, only the linux/amd64 binary has been run before publishing.
OctoGo v0.46.0 (preview)
OctoGo v0.46.0 (preview) - prebuilt binaries for linux (amd64, arm64), windows/amd64 and macOS (arm64, amd64). Unzip and put ogo on PATH; the C backend and the board loader are embedded, so there is nothing else to install. ogo help starts you off.
This is the first binary release since v0.43.0, and it carries three tags, v0.44.0, v0.45.0 and v0.46.0: 103 commits in all. Much of it was found by p2-11, a PDP-11 emulator written in OctoGo and the first OctoGo program of any size, which asked what a program costs — build time, memory, clocks — where the sweeps before it had asked what a program computes. Every entry, with what each program used to become, is in CHANGELOG.md; the headlines follow.
Language
- A statement may stand alone in a header (v0.45.0): a call, a receive, a send or a step ahead of an
if's, aswitch's or afor's ";",if two(); ok {, as Go allows, each doing what it does on a line of its own. A call and a receive may be written in parentheses as statements,(f())and(<-ch). p2.ClockFreq()is how many clocks a second has (v0.44.0): the frequency chosen with--clockwhere the program is built, so what counts in clocks —GetCt,WaitUntil,WaitCycles, a smart pin's period — no longer needs the number written into the program.- A list assigns through a written dereference (v0.44.0):
(*s)[i], (*s)[j] = (*s)[j], (*s)[i], the Swap of a sort.Interface, in a statement, a for clause, an if or a switch init and a select's comma-ok flag. A parenthesised name is a target,(x) = 5and(x)++.
Behaviour changes
Where this release treats a program differently from v0.43.0; all but the first and the last are refusals, as Go refuses the program.
- A small function is inlined where it is called (v0.46.0), so what a call of one costs has changed, for a program that counted on it for its timing, and so has the size of a binary: of 1156 programs 285 are smaller and 191 larger, by 0.35% in all.
--no-inlinebuilds the binary v0.45.0 built. - Two defined types over one kind are two types in every position (v0.44.0). For
type A int; type B int, a value from a conversion,a := A(1), or from an operation,a + 1, passed where a B was wanted — a declaration, an argument, a return, a comparison, a store, a send, an append — in one package and across a package boundary. Each is refused with Go's message. - Every place a program stores asks every lifetime rule, and every way a call is written is asked what it hands back (v0.44.0): an interface method, a variadic pack, parentheses, a list, a for or range clause, a dereference, a call's result kept in a local or handed to another call. Each gap left a local's address in package storage, in silence.
- Go's refusals, in more places (v0.44.0): a received value has its element's type wherever it goes; a target reached through a call's result, a pointer or parentheses is asked what it stores, and a send through a dereference or parentheses what it sends; a method called on a variable of an unnamed type is refused; so is what is no slice where a slice is wanted, and a range over a float or over a constant no int can hold (
range 1 << 40wrapped to 0 on the board, in silence). - A
for's post that is a value nothing reads is refused (v0.45.0),for ; n < 2; x + 1, as Go refuses it. ogo fmt -lmay list a source an earlierogo fmtwrote (v0.44.0): the layout is gofmt's in more places, so a source in the earlier one is reformatted once.
Fixed
- Wrong answers, most of them silent, measured on a P2-EDGE: an untyped constant beside an unsigned operand —
b-a < patiencefor a uint32 difference past 2^31 was true where Go says false, andpatience / uwas 4294967295 for Go's 0 (the backend's part reported upstream as flexprop#114); a constant written as a float stored into a uint32,3e9as 2147483648; a uint64 dividing a constant; a block's constant outliving its block, and a library's constant reading main's of the same name; aforwith a call for its init, which never called it and never ended; an integer range over a uint32 bound past the signed maximum, which ran zero times; a select's clause operands, an else-if's init, a receive clause's target and a spread append's operands evaluated out of Go's order; a method called on a large struct result, corrupted on the board with only a warning about C nobody wrote (flexprop#113); a select clause receiving into a target in parentheses or behind a star, which left it unwritten; and a range over an array handing out a write made through parentheses. - What a program costs, found by p2-11: a named constant is its value where it is read — a loop bounded by one ran 69.0 clocks a pass for the literal's 56.5, and both build to the same binary now; a call's results assigned to the fields it reads took 25 seconds to build, and two such statements ran the compiler out of memory; an array literal of any length builds, where a table of 8000 uint16 values overflowed the preprocessor's line.
- An error comes back from a call given a local buffer,
if err := fill(buf[:]); err != nil { return 0, err }, which was refused as holding a pointer into the buffer. - Parentheses are read as Go reads them: a conversion to a type in parentheses,
(int)(f); a method or an index on a parenthesised call's array result,(mk(5)).Len(); a range operand, a method value's receiver, and a select clause's channel and target. ogo fmtagrees with gofmt on what continues a line, on columns and on line breaks — all thirty-six of p2-11's sources format as gofmt formats them — and writes a comment at the end of a file once.- And:
%Tof a channel, a function type and a variadic parameter prints Go's text; a string range's rune is a rune;ogo testreports a test that fails and then skips as failed;ogo fmtexits 1 when a path cannot be read.
Toolchain and verification
- Small functions are inlined (v0.46.0): the C backend is told to inline a function of six statements at most, and one of several returns is written to leave through one. On a P2-EDGE at 160 MHz, a method testing a field of its receiver takes 51 clocks a call for 211, a setter of an array's element 67 for 229, and
clamp(v, lo, hi)27 for 68. A program that outgrows a cog's registers with its functions inlined is built without, and nothing is said. - The dependencies are upgraded (v0.44.0), modernc.org/libc v1.77.1 and ccgo v4.36.1 among them. The backend is untouched: every binary of 1218 programs is byte-identical before and after. Building the compiler requires Go 1.26.
ogo smithwrites a call in a header (v0.45.0). With that generator, seeds 1–2000 pass on the host, and of seeds 25–200 on a P2-EDGE 168 pass and 8 outgrow a cog.- Under this release's compiler the host suite passes, and so does
make boardon a P2-EDGE: 813 cases, among them the whole run-case corpus.
The five zips are cross-compiled from one host; see SHA256SUMS. As before, only the linux/amd64 binary has been run before publishing.
OctoGo v0.43.0 (preview)
OctoGo v0.43.0 (preview) - prebuilt binaries for linux (amd64, arm64), windows/amd64 and macOS (arm64, amd64). Unzip and put ogo on PATH; the C backend and the board loader are embedded, so there is nothing else to install. ogo help starts you off.
This is the first binary release since v0.41.0; the full story of the tags between is in CHANGELOG.md. The headline of v0.42.0, which had no binaries: the C backend regenerated at spin2cpp v7.7.3, whose own fixes for the three optimizer faults v0.40.0 carried patches for (flexprop#109–#111) replace those patches — measured before adoption to build every reproducer, run case and fuzzer seed as the patched backend did, but for 13 programs 8 bytes larger, all of which pass on the board.
What v0.43.0 itself changes — the headlines; every entry, with what each program used to become, is in the CHANGELOG under v0.43.0 (231 commits):
Language
- A struct holding an array crosses every boundary: a parameter, a value receiver, a result — beside another result too — and a channel's element, through direct calls, methods, interface slots, function values, and deferred and started calls. The target's C compiler cannot pass or return one by value, so each is carried by pointer and copied where Go copies it.
- Arrays: a result beside another, a method called on a call's array result (
mk(5).Count(),rot.Mul(rot).Mul(rot)), an array result as a deferred or started call's argument, and a variadic parameter of an array type. - Function values: the state-function idiom,
type stateFn func(*lexer) stateFn; a call through a function value takes what a declared function's call takes — variadic arguments, interfaces, nil; a function ranged out of a table of a named function type, received from a channel, parenthesised, or of another package's function type, may be called; a function element, a variadic call and a call through an interface may be deferred or started on a cog. - Statement headers: an
iforswitchinit may assign,if s, keep = f.Apply(s); !keep, or step a variable,if n++; n > limit; aforinit or post may take one call's several results; a select clause may be written on one line, and a labeled select is a break target. - Conversions: a constant string converts to a byte or rune slice,
[]byte("123456789"), and to a defined byte slice type; a conversion to a type written out,[]int(x)and[3]int(s), is spelt as Go spells it; a slice of a defined type keeps its type — and its methods — through a reslice, an append and a call's result. - printf:
%vof a struct under a flag, a width or a precision; the element-wise verbs over a multi-dimensional array;%q,%Uand the hex dump% xwith flags, widths and precisions;%F,%Oand the#flag where fmt takes them; a received array and a call's array result printed where they stand. - Complex numbers are specified, for release 1.1: they need no heap, so they are owed, and until then an imaginary literal says "complex numbers are not supported yet".
Behaviour changes
Each is the compiler refusing a program the last binary release took.
- The lifetime rules believe only what holds on every path. A reference to a function's own storage stored through a pointer or a call's result, into an element of a slice not known to view the frame, or handed to a callee that stores it through its receiver or a pointer argument, is refused unless that storage is KNOWN to be the frame's — a pointer written once with a local's address, a slice written once as a view of a local array. Each such program read garbage once the function returned:
p := &gq; p.p = &x, read through afterwards on a P2-EDGE, was -179917308 where Go prints 42. - What has no Kind is asked what it is:
x++, arithmetic, shifts, orderings and conversions of pointers, functions, channels, slices, arrays, structs and interfaces are refused as Go refuses them, in every place a value is stored; a function of the wrong signature is refused wherever it is stored; a store through more than one step, a range clause's=target and aforclause's assignments are checked; a case of an expression switch is checked as the value it is. - Go's words for Go's refusals: a store into a call's value, a pointer method on a value with no storage, the address of a field of a call's value, a method value whose receiver is a pointer, and a step after
(&v)that the address does not take. - Syntax: two switch or select clauses on one line need a semicolon between them, and a target of
:=that is no name is refused wherever it is written.
Fixed
- Silent miscompiles, most measured on a P2-EDGE: a float constant halfway between two floats read as the wrong one by the target's C compiler —
2.000872e+09as 2000872064 — found by the fuzzer's board sweep and reported upstream as flexprop#112; a variadic call through a function value,f(1, 2, 3)counting 2; a function, and a float constant, passed to a call through a function value arriving as garbage; every element of a slice of functions reading as the first;return b, awith a defer returning 2, 2 for Go's 2, 1; an array result taking its value after the deferred calls ran, and bound ahead of the arguments before it; a range over an array through a pointer reading it live; a function ending in a loop left by a labeled break returning 0;&o.Basein an interface calling the outer type's method; a type namedShape_vtmerged with an interface's table; an array printed where it stands, and a literal of a defined slice type, printed as an address. - Names: a program may use any name the target's C library has taken —
read,close,clock,FILE, a fieldEOF— or one the compiler joins from two, and a local of any kind may be named what C reserves. - Programs that did not build: a function of many string constants or hex dumps, out of cog registers; a struct holding an array of 12 or 16 bytes copied by assignment; a padded bool in a program with no string; and more. A backend crash is reported as one rather than as a stack trace.
- Lifetime: the address of an array or a slice literal outlived its storage.
Toolchain and verification
- The names C speaks for are generated:
internal/octogo/cnames.golists every macro, type and file-scope name of the target's C library, derived from the embedded tree byTestCNames, which fails when a backend regeneration adds one. - The fuzzer names things as a C programmer might: one name in six is a C keyword, a macro, a library function or a type. With this generator, seeds 1–600 on a P2-EDGE: 576 passing, 23 too big for a cog, and seed 479, which found the float constant above and passes now. With the previous one, seeds 25–1000 were clean.
- Upstream: flexprop#112 is reported with a fix measured natively — the literal's text converted with
strtof— under which spin2cpp'smake test_offlinepasses 588 of 588 and no binary of 1194 other programs changes. - The whole run-case corpus passes on a P2-EDGE under this release's compiler, and the host suite with it.
The five zips are cross-compiled from one host; see SHA256SUMS. As before, only the linux/amd64 binary has been run before publishing.
OctoGo v0.41.0 (preview)
OctoGo v0.41.0 (preview) - prebuilt binaries for linux (amd64, arm64), windows/amd64 and macOS (arm64, amd64). Unzip and put ogo on PATH; the C backend and the board loader are embedded, so there is nothing else to install. ogo help starts you off.
This is the first binary release since v0.39.0; the full story of the tags between is in CHANGELOG.md. The headlines of v0.40.0, which had no binaries: the C backend regenerated with three fixes of its own for SILENT optimizer faults (a 64-bit add or subtract of a constant, two if bodies updating one global, a signed compare of two values far apart — each reported upstream, flexprop#109–#111); goto, local type declarations, type A = B aliases, [...]T{...} and stores through a call's result in the language; evaluation ORDER made Go's everywhere it was C's — value lists, composite literals, the calls of an expression, multiple assignment; initialization order followed through function and method bodies, with cycles refused; and a 1000-seed fuzzer sweep clean on the board.
What v0.41.0 itself changes — the headlines; every entry, with what each program used to become, is in the CHANGELOG under v0.41.0 (176 commits):
Language
bytes, the allocation-free part of Go's — what makes thestrings-style functions reachable for bytes received over a line, wherestring(rx[:n])would allocate.- Method expressions,
T.Mand(*T).M; conversions to pointer types,(*T)(x); directional channels,chan<- Tand<-chan T; a struct that embeds a pointer; an alias of another package's type; defined types over a slice, an interface and a pointer that behave as such wherever one is declared. - printf prints what fmt prints: a struct under
%v, an interface value by what it holds (at depth too), every verb over a slice or an array element by element,#, widths and precisions laid out by fmt's rules rather than C's,Error()asked beforeString(), and the arguments read before any of them is formatted. - Grammar gaps closed: a parenthesised expression read and written through fields and indexes, a string and a slice literal indexed and sliced, a struct literal read through a suffix, a suffix in a statement header, an
if/switchinit with a value for each name, a select's comma-ok stored anywhere an assignment writes. (One is known and open: a conversion to a type written out,[]byte(x); a named type's converts.) - Programs Go refuses are refused here — see Behaviour changes.
Behaviour changes
Each is the compiler refusing a program the last release took.
- The lifetime rules are asked of every form that binds a value and at every way to call: parentheses, typed
var, list forms, destructuring,forclauses, deferred calls, literals, method values, a method keeping its receiver, a slice's elements, whatmakeallocates, callbacks, and a call through a function value nothing can name. Each such program handed out a reference to storage that was gone by the time it was read, in silence. - A condition is a bool:
if p {for a pointer,!ffor a function,ch && ok— C has a truth value for these, so they compiled and ran as C means them. nil is no struct and no array:return nilfrom a function returning a struct built a binary, without a word, that handed its caller garbage. A function literal has its type as a value:f := func(n int) int {...}thenf("x")orf(1, 2)went through. - A struct that embeds itself and a local type that holds itself (these did not reach the C compiler: the emitter followed the type without end and took the memory of the machine compiling it); a conditionless switch with a non-boolean case;
make([]int, 5, 2); a write into a string; a struct indexed; a channel compared with a number; a range over what has no elements; an elided literal with a value too many or another package's unexported field;fallthroughin a type switch; a constant its declared type cannot hold; a struct converted to a number; an integer-only operator on a float; the address of a call's result; an append of the wrong element type; a pointer ordered or compared with a number.
Fixed
- Silent miscompiles, each found by comparing a program with real Go, most on a P2-EDGE: a
forloop's init and post clauses lowered differently from the statement (seven faults, a lost guard and acontinuethat never ended among them); a declaration giving a name another KIND, and one reading the name it shadows;a | b ^ canda & b << 2associated as C does; a range over an array iterating the live array rather than a copy; a multiple assignment of a constant wider than an int; an empty slice being nil; a constant past 64 bits computed at run time as 0;math.Sqrt(2)being 1 on the board; a value sent, selected or appended evaluated twice; a deferred printf reading its arguments one place off; a function literal of a struct result taken as a value. - Compiler crashes on legal programs: a function literal in a package variable's initializer whose body assigns; a struct embedding a pointer to itself; a composite literal in a switch case. And a local struct holding a slice of itself, which did not compile.
- Across a package boundary: another package's struct compared as this package's are, its function type-checked as a value, its variables as targets of a list assignment, its member in a switch case, a promoted method's receiver and results.
Toolchain and verification
- The oracle fuzzer writes function literals, method expressions, a select over two workers, pointers to structs, embedded structs, labeled
break/continue, two-dimensional arrays, struct and array comparison,copy/clear/three-index reslices and the spread append — and counts CALLS, so an operand evaluated twice or not at all shows as a number. scripts/rejects.shsweeps programs Go rejects, and every probe script runs the compiler under a memory cap and tells a compiler fault from a refusal. Of 212 such programs 204 agree with Go and none is taken where Go refuses it; of the 8 that differ, 7 do by design (no heap, no vet) and one is a gap, the no-ops = append(s).- The whole run-case corpus passes on a P2-EDGE under this release's compiler, and the host suite with it.
The five zips are cross-compiled from one host; see SHA256SUMS. As before, only the linux/amd64 binary has been run before publishing.
OctoGo v0.39.0 (preview)
OctoGo v0.39.0 (preview) — prebuilt binaries for linux (amd64, arm64), windows/amd64 and macOS (arm64, amd64). Unzip and put ogo on PATH; the C backend and the board loader are embedded, so there is nothing else to install. ogo help starts you off.
This is the first binary release since v0.33.0; the full story of the tags between is in CHANGELOG.md. The headlines since then: interfaces completed (struct fields of interface type, embedded interfaces, struct{ error } passing as an error, a nil interface call panics as Go's does), defined non-struct types embed with method promotion, ogo fmt now formats byte-identically to gofmt across the whole test corpus, the export rule holds on every route to another package's member, nil channels behave as Go's do, and dispatch tables (handlers[i](x)) work in every call position.
What v0.39.0 itself changes:
Language
-
%qof a slice prints element-wise, as fmt prints it.['h' '\t' ...]
style rune quoting for integer elements, quoted strings for string elements,
[]for an empty slice -- Go's escapes throughout, multi-byte runes
included. This was the last entry on the recorded ordinary-Go gap list. -
A call's results forward into a variadic callee.
sum(two())packs the
results past the fixed parameters into the variadic slice, exactly as Go
does -- all of them, some after fixed parameters take their share, or none
when the results just cover the fixed ones. The count and each result's type
are checked where the call is written, against the fixed parameters pairwise
and the element type for the rest. This was the last "assign them first"
refusal in the ordinary-Go set. -
A locally declared channel's lock is actually allocated. The synthesized
package-init function that news every channel cell's hardware lock was
emitted but, for a program whose only channels are locals, never CALLED: the
decision was made before main's body was emitted, and emitting the body is
what registers those cells. Every such lock was an un-newed zero that
ALIASED whatever_locknewhanded out first -- the cog pool's own lock, in
any program that starts a goroutine -- and merely shared it; a program that
started no goroutine hung at the first lock attempt on hardware, where the
host shim forgave the un-newed id. The decision now follows the body. Found
because the new select machinery was the first thing to take a channel's
lock in a goroutine-free program. -
A select's send clause works beside a
default, and a select may have
several send clauses. Both were refused ("whether a receiver is ready
cannot be known without offering the value") because the rendezvous cell
carried no such signal. It does now: a parked receiver -- a blocking receive,
a range over the channel, or a select's armed receive clause -- announces
itself on the cell, and a gated non-blocking send offers only when a taker
exists, taking the offer back if an armed select chose another clause in the
window. Soselect { case ch <- v: default: }answers its default honestly,
two send clauses never stand two offers at once, and two selects -- one
sending with a default, one receiving -- pair with each other. The single
send clause without a default keeps its standing offer, unchanged. Verified
on hardware in five shapes, each against its Go twin. -
Multiplexing a channel and a Smart Pin is a documented, hardware-verified
idiom. The spec's select section now shows it: the select'sdefaultarm
polls the pin's IN flag and acknowledges each event, so a command is taken
whenever one is ready and the pin is serviced the rest of the time. The new
pinselectexample runs it live -- a self-clocking ADC pin raising IN every
2^13 clocks, ten channel commands landing between hundreds of pin events,
every claim checked on a P2-EDGE. A pin CLAUSE of the select's own was
considered and DECLINED: the idiom is the same semantics with no new syntax,
and the spec says so -- the decision is revisited only if real programs show
the idiom wearing badly.
Toolchain
- Domain-probe round seventeen (a deadline scheduler) is pinned as a run
case. Wrap-safeuint32clock arithmetic across the wrap point, comparator
function values -- one chosen at run time, one a method value whose
receiver's state flips between sorts -- struct swaps, wrap-safe binary
search, nil-guarded function fields, and cross-package callbacks all match
Go bit for bit on the board. The round found nothing to fix; the case is the
program that says those pieces compose.
OctoGo v0.33.0 (preview)
OctoGo v0.33.0 — a preview release of the OctoGo compiler for the Parallax Propeller 2.
This one is mostly about concurrency and package boundaries: a producer can now
say it is finished, a bank of channels can be indexed by worker number, and several
things another package declares became usable from outside it. Alongside them, seven
rules the compiler was not enforcing — each one may reject a program v0.32.0 accepted.
Everything below was verified on a real P2-EDGE against the same program in Go.
Behaviour changes
-
goanddefertake a CALL, and two shapes end in one without being one.
defer int(x)anddefer Q(x)compiled and did nothing at all -- a conversion
computes a value and throws it away, which is never what a defer meant -- while
go int(x)reached the emitter, which said "onlygo f(args)on a package
function or a variable holding one is supported yet", describing a missing feature
rather than the mistake. Both arerequires function call, not conversion int(x)
now, in Go's words and at the operand. The built-ins whose whole purpose is the
value they return --append,cap,len,make-- aredefer discards result of len(xs), likewise Go's.copy,printlnandpanicreturn nothing anyone
wants at a defer and stay valid, as in Go. -
A label's scope is the whole function, and an unused one is an error. Two
SIBLING labels of one name compiled: the duplicate check asked the stack of
ENCLOSING labels, which a sibling has already been popped from, so it saw only the
nested case. And nothing reported a label no break or continue names -- which Go
makes an error, and which is worth having: a label is written to be jumped to, so
one nothing reaches is either a typo in the break that meant to name it or a
leftover of a loop since restructured, and both read as working code. A function
LITERAL is a function of its own here, its labels neither colliding with the
enclosing function's nor satisfying them. -
A method's receiver type is checked. Nothing asked what a method was declared
ON, sofunc (n int) g() intcompiled -- a method on a predeclared type, emitted
as a function no selector could ever reach -- and so didfunc (p P) g() intfor
atype P *T, a receiver Go has no method set for at all: a pointer type's method
set is empty, so nothing declared with one could implement anything. An INTERFACE
receiver went the same way. The rule is Go's and so are its words:invalid receiver type P (pointer or interface type),cannot define new methods on non-local type int, and the same for another package's,geo.Pt. -
A struct's embedded field is checked where it is written. Four different
mistakes shared one emitter message with no position -- "an embedded field must be
a struct type of this package, and an untyped field is not a field" -- and another
package's UNEXPORTED struct did not even get that:lib.leafwas quietly
embedded, the emitter keying structs by their C symbol and a C symbol carrying no
case rule. They areundefined: Nope,undefined: lib.leafandcannot embed lib.Count: only a struct type may be embedded herenow, at the name. That last
one also covers embedding an INTERFACE, which used to be accepted at the
declaration and refused at the use, as "type M has no method Read". -
An address may not be taken of a constant or a function.
&Kand&f
compiled, and&pkg.Kdid too;&7reached the C compiler, which called it
"Cannot take address of e". A constant has no storage and a function's name is
not a variable, so neither has an address -- Go says exactly that, and so does
this now. A variable, a field, an element and a composite literal are unchanged. -
A local that shadows an import is the local.
lib := 1; lib.Take(2)
compiled and CALLED THE IMPORT: the emitter resolves a qualified name against
the package, and nothing had asked whether the name was a variable here. It ran
a program that says something else. A selection on such a variable is refused
now, in Go's words. -
An embedded name that is not an interface, or is nothing, is refused where it
is written. Both reached the emitter, which says so with no position and only
for a program that gets that far -- and another package's UNEXPORTED interface
reached it and was quietly embedded, the emitter keying interfaces by their C
symbol and a C symbol carrying no case rule. The three areundefined: Nope,
undefined: lib.readerandcannot embed lib.S in an interface: it is not an interface type, at the name.
Language
-
close(ch)and the comma-ok receive. A producer had no way to say it was
finished. The only end-of-stream was a sentinel value the consumer had to agree
on, and a consumer that waited for one value more than the producer sent simply
hung -- with nothing in the program saying what it was waiting for.closewas
"the close builtin is not supported yet" andv, ok := <-chwas "multiple
assignment requires a single function call on the right-hand side", a true
statement about a shape Go does not require a call for.for { v, more := <-ch if !more { break } // ... }
The cell carries a closed flag, set once under its lock. Every receive then yields
the element's zero value at once instead of waiting, and a value already in the
cell is taken even after the close, so nothing sent before it is lost. Sending on
a closed channel and closing a closed channel both panic, as in Go. Verified on
the board against the same programs in Go, for a scalar element, a struct element,
the declaration and the assignment forms, and a channel closed before anything was
sent. -
A struct may hold an array of channels.
type bank struct { q [4]chan req ... }was "a channel field that is an array is not supported yet", which is how a
bank of channels is packaged once it has a name or an output channel to carry
alongside it. Each element owns a cell, as an array variable's elements now do,
andb.q[i] <- vsends to it.The send needed the checker to tell WHERE an index sits. One flag said only that
the target had one somewhere, and the send read it as an index on the HEAD --
ws[i].cmd <- v, where what the field is does not depend on which element. An
index AFTER the last selector is the field's own, and the field is then the array
while the channel is its element. The two are told apart now. -
An array of channels is a bank of channels.
var q [7]chan req-- one
request channel per worker cog, indexed by worker number -- allocated NOTHING: the
array was emitted as what it is in C, an array of pointers, and every element
stayed null. The checker accepted<-q[0]through one path and refusedq[0] <- vthrough another, so the half that compiled read rubbish off address zero. A
local one did the same without even a refusal to warn about it.Each element owns a cell now, on the rule a channel variable and a struct's
channel field already obey: the DECLARATION owns the cell. Sending, receiving,
binding an element to a name and handing one to a cog all work, at package scope
and in a function. Seven worker cogs driven through one -- the whole machine, one
cog left for main -- match the same program in Go on the board. -
A struct may embed another package's struct, written qualified:
type Mid struct { lib.Leaf M int }
The grammar already admitted it and nothing else did. The field list read the two
identifiers as two NAMES, so the struct had two fields nothing could refer to --
Mid{a, b}was "2 values but 3 fields" -- and no embedding at all:V.Read()was
"type Mid has no method Read" andV.L"has no field L". The field is named after
the type UNQUALIFIED, as in Go, so what is embedded is reached asV.Leaf, and
every promotion the language has follows through it: a field, a value-receiver
method, a pointer-receiver one, a method VALUE, two levels with the deeper one
crossing the boundary from inside the other package, and an interface of that
package satisfied by a method promoted from its own type. Verified on the board
against the same program in Go, in eleven positions.The three walks behind promotion -- the field set, the selector-depth count that
reports an ambiguous one, and the method-set search -- now carry the SCOPE a name
resolves in rather than assuming the asking one. What an imported type embeds is a
name of its own package, which this one need not have and may have differently. -
A method value may be taken of another package's variable, and of a PROMOTED
method:p := lib.Kit.Inc // lib.Kit is that package's own variable s := lib.Deck.Step // Step is declared on a type lib.Deck embeds
Both were "type lib.Counter has no field Bump" -- the walk asked the variable's
type for a FIELD and never for a method, so the one receiver a method value can
bind, a package-level variable, was unreachable across a package boundary. The
promoted half was missing at home too:V.Base2for an embedded Base was a
missing field in its own package.What is refused is unchanged and now says so where it is written rather than in
the emitter: a value-receiver method (cannot take lib.V.Get as a value: a method value copies its receiver, naming the expression the program wrote rather than a
type nobody can go and look at) and an unexported one. -
A promoted method may be called on another package's variable.
lib.Deck.Twice()for aTwicedeclared on a typelib.Deckembeds was
"unsupported call in expression". A qualified head has one path into the chain
renderer and that path took any Selector-then-CallSuffix for a method the type
itself declares, so a promoted one ended the chain. Reading a promoted FIELD the
same way, `lib.Dec...