Skip to content
This repository was archived by the owner on Mar 11, 2019. It is now read-only.

Commit e1e14f6

Browse files
committed
Support non-ARM builds (but without stack unwinding)
1 parent 21df028 commit e1e14f6

File tree

7 files changed

+274
-247
lines changed

7 files changed

+274
-247
lines changed

Makefile

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ GOFRONTEND = ./gofrontend
5656
#PKGROOT = /usr/share/go-1.7/src
5757
PKGROOT = $(GOFRONTEND)/libgo/go
5858

59+
# Find the current architecture.
60+
# TODO: cross compilation
61+
ARCH_TUPLE = $(shell gcc -dumpmachine)
62+
ifneq (,$(findstring arm-,"$(ARCH_TUPLE)"))
63+
ARCH = "arm"
64+
else
65+
ARCH = "other"
66+
endif
67+
5968
# Directories required during the build. Will be created by mkdir at the start.
6069
DIRS = \
6170
$(BUILD)/tinygo \
@@ -102,7 +111,6 @@ SRC_C_LIBGO += \
102111
# Runtime C sources from tinygo. These live in src/runtime.
103112
SRC_C_TINYGO = \
104113
tinygo.c \
105-
tinygo_arm.c \
106114
channel.c \
107115
panic.c \
108116
libgo-go-iface.c \
@@ -116,9 +124,12 @@ SRC_GO_RUNTIME += \
116124
src/runtime/runtime.go \
117125
$(GOFRONTEND)/libgo/go/runtime/error.go
118126

119-
# Build a single runtime.o for all Go sources.
120-
$(BUILD)/pkg/runtime.o: $(SRC_GO_RUNTIME)
121-
gccgo $(GOFLAGS) -fgo-pkgpath=runtime -c -o $@ $^
127+
ifeq ($(ARCH),arm)
128+
SRC_C_TINYGO += tinygo_arm.c
129+
SRC_GO_RUNTIME += src/runtime/runtime_arm.go
130+
else
131+
SRC_GO_RUNTIME += src/runtime/runtime_other.go
132+
endif
122133

123134
# Create a lit of all Go package dependencies, stored as $(BUILD)/pkg/*.o
124135
# files.
@@ -141,7 +152,11 @@ endif
141152
mkdir:
142153
@mkdir -p $(DIRS)
143154

144-
# Build the runtime package.
155+
# Build a single runtime.o for all Go sources.
156+
$(BUILD)/pkg/runtime.o: $(SRC_GO_RUNTIME)
157+
gccgo $(GOFLAGS) -fgo-pkgpath=runtime -c -o $@ $^
158+
159+
# Build the 'main' package (with program name as filename)
145160
$(BUILD)/pkg/$(PKG).o: src/$(PKG)/*.go
146161
gccgo $(GOFLAGS) -c -o $@ $^ -fgo-relative-import-path=/home/ayke/src/tinygo -I $(BUILD)/pkg
147162

README.markdown

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,15 @@ scheduler, memory allocator (no GC yet) and channel send/receive primitives.
6060

6161
There are many.
6262

63-
* No support for anything except ARM.
6463
* Works with gccgo 6.3 (Debian stretch), no other compiler versions have been
6564
tested.
66-
* Source files must be placed in a subdirectory of src/.
65+
* Source files must be placed in a subdirectory of `src/`.
6766
* No `recover()`
68-
* No function names or line numbers in the `panic()` output. I would like to
69-
fix this at some time, but it will increase the binary size if it must work
70-
after stripping. It currently outputs the function addresses, which you can
71-
look up with `nm` (e.g. `nm -S ./build/example | egrep ' [tT] main\.'`).
67+
* No function names or line numbers in the `panic()` output, and no backtraces
68+
on anything except ARM. I would like to fix this at some time, but it will
69+
increase the binary size if it must work after stripping. It currently
70+
outputs the function addresses, which you can look up with `nm` (e.g. `nm -S
71+
./build/example | egrep ' [tT] main\.'`).
7272
* No garbage collector, yet. Allocated memory is never freed.
7373
* Many types might not be implemented, but support will probably be pretty
7474
easy to add by including the correct files from `gofrontend/libgo/runtime`.

src/runtime/panic.c

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,34 +38,22 @@ bool tinygo_print_stackitem(goroutine_t *r, uint32_t *pc_top, uint32_t *pc_call,
3838
printf("\?\?\?(...)");
3939
}
4040
printf(" at %p\n", pc_top);
41-
printf("\t%p (+0x%x, stack usage: %u/%u)\n", pc_call - 1, (pc_call - pc_top - 1) * sizeof(func), STACK_SIZE-sp*sizeof(uintptr_t), STACK_SIZE);
41+
printf("\t%p (+0x%lx, stack usage: %lu/%u)\n", pc_call - 1, ((pc_call - pc_top - 1) * sizeof(func)), (STACK_SIZE-sp*sizeof(uintptr_t)), STACK_SIZE);
4242
return found_root;
4343
}
4444

4545
void tinygo_unwind_fail(void *pc, String msg) {
46-
printf("[cannot unwind further: %.*s, PC=%p]\n", msg.len, msg.str, pc);
46+
printf("[cannot unwind further: %.*s, PC=%p]\n", (int)msg.len, msg.str, pc);
4747
}
4848

4949
void tinygo_unwind(goroutine_t *r, uint32_t *pc, uint32_t *sp) {
5050
// The current goroutine is by definition running, so hard-code the
5151
// status [running].
52-
printf("\ngoroutine %u [running]:\n", r->num);
52+
printf("\ngoroutine %zu [running]:\n", r->num);
5353

5454
tinygo_port_unwind(r, pc, sp);
5555
}
5656

5757
void __go_panic(struct __go_empty_interface msg) {
5858
runtime_Panic(msg, false);
5959
}
60-
61-
__attribute__((naked))
62-
void tinygo_get_lr_sp(uintptr_t *args) {
63-
// Capture the current stack pointer and program counter of the
64-
// caller. The exact location of the PC shouldn't matter. The stack
65-
// pointer shouldn't matter too much either, as long as it's at the
66-
// same position captured as the program counter.
67-
__asm__ __volatile__(
68-
"str lr, [r0, #0]\n"
69-
"str sp, [r0, #4]\n"
70-
"bx lr\n");
71-
}

src/runtime/runtime.go

Lines changed: 1 addition & 221 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,6 @@ import (
88
const STACK_SIZE = 2 * 1024
99
const STACK_SIZE_PTRS = STACK_SIZE / unsafe.Sizeof(uintptr)
1010

11-
// TODO
12-
const GOARCH = "arm"
13-
14-
// Choose which undwinder implementation to use. "armdecode" is the one that
15-
// actually works at the moment.
16-
const UNWINDER = "armdecode"
17-
//const UNWINDER = "exidx"
18-
1911
// Must be the same as defer_t in tinygo.h.
2012
type deferred struct {
2113
fn unsafe.Pointer
@@ -62,209 +54,6 @@ func tinygo_get_code_bounds() (uintptr, uintptr) __asm__("tinygo_get_code_bounds
6254
func tinygo_get_unwind_sections() (uintptr, uintptr) __asm__("tinygo_get_unwind_sections")
6355
func exit(int) __asm__("exit")
6456

65-
func unwind_armdecode(r *goroutine, pc uintptr, sp int) {
66-
stext, etext := tinygo_get_code_bounds()
67-
lr := uintptr(0)
68-
pc_call := pc
69-
for sp >= 0 && sp < len(r.stack) && pc >= stext && pc < etext {
70-
pc -= 4
71-
instr := *(*uint32)(unsafe.Pointer(pc))
72-
//printf("unwinding... PC=%p SP=%p LR=%p: ", pc, sp, lr);
73-
if ((instr >> 16) & 0xffff) == 0xe92d { // STMFD sp!, {...}: P=1, U=0, S=0, W=1, L=0
74-
// STM is: 1110 100P USWL Rn reglist...
75-
// cond
76-
// Note: condition code 1110 means "always"
77-
//printf("STMFD sp!, {...}\n"); // aka push
78-
for i := uint(0); i < 16; i++ {
79-
if (instr >> i) & 1 == 1 {
80-
//printf("\tr%-2d = %x\n", i, *sp);
81-
if i == 14 {
82-
lr = r.stack[sp] - 4
83-
// We have found the link register.
84-
// Note that we will continue walking up, as there
85-
// might be more SP-altering instructions (but
86-
// we'll break at the first unrecognized/non-SP-
87-
// related function as that means we're past the
88-
// prologue).
89-
}
90-
// reading back so reversing direction
91-
sp += 1
92-
}
93-
}
94-
} else if (((instr >> 12) & 0xffeff) == 0xe24dd) { // SUB sp, sp, #imm
95-
imm := instr & 0xff
96-
shift := (instr >> 8) & 0xf
97-
//printf("SUB sp, sp, #%d\n", imm << shift);
98-
sp += int(imm << shift) / 4 // reverse operation, so ADD instead of SUB
99-
} else {
100-
//printf("unknown instruction\n");
101-
if lr != 0 {
102-
// Done with this function. Let's move up a stack frame!
103-
// But first print the stack trace frame.
104-
if tinygo_print_stackitem(r, pc + 4, pc_call + 4, sp) {
105-
// We're done: this is the last frame.
106-
return
107-
}
108-
pc = lr
109-
//printf("next LR: %p\n", lr);
110-
lr = 0
111-
pc_call = pc
112-
}
113-
}
114-
}
115-
116-
// SP or PC was out of range, probably an error while walking the
117-
// stack.
118-
tinygo_unwind_fail(pc, "pc or sp out of range")
119-
}
120-
121-
type exidxEntry struct {
122-
address uintptr
123-
command uintptr
124-
}
125-
126-
type decoder struct {
127-
lr uintptr
128-
pc uintptr
129-
sp int
130-
}
131-
132-
// Decode takes a stream of unwind instructions (possibly unbounded) and decodes
133-
// them until the first finish or unrecognized instruction. It returns true on
134-
// failure.
135-
func (d *decoder) decode(r *goroutine, instructions []byte) bool {
136-
// See section 9.3, "Frame unwinding instructions":
137-
// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038b/IHI0038B_ehabi.pdf
138-
139-
for i := 0; i < len(instructions); i++ {
140-
instr := instructions[i]
141-
// Interpret stack unwinding instructions.
142-
// This is something quite different from ARM instructions.
143-
//println(" ", hex(uintptr(instr)))
144-
switch {
145-
case instr & 0xc0 == 0x00: // 00xxxxxx
146-
imm := int(instr & 0x3f)
147-
d.sp = d.sp + imm + 1
148-
//println("\tsp +=", (imm << 2) + 4)
149-
case instr & 0xf8 == 0xa8: // 10101nnn
150-
imm := int(instr & 0x07)
151-
// pop r4-r[4+nnn], r14
152-
d.sp += imm * 1 + 1
153-
d.lr = r.stack[d.sp]
154-
//println("\tpop PC:", hex(d.lr), "registers:", imm + 1)
155-
case instr == 0xb0: // 10110000: finish
156-
break
157-
case instr & 0xf0 == 0x80:
158-
i++
159-
regs := (uintptr(instr & 0x0f) << 8) | uintptr(instructions[i])
160-
for regnr := uint(15); regnr >= 4; regnr-- {
161-
if (regs >> (regnr - 4)) & 1 == 1 {
162-
//println("\t\tpop reg:", regnr)
163-
d.sp++
164-
if regnr == 14 { // lr
165-
d.lr = r.stack[d.sp]
166-
}
167-
if regnr == 13 { // pc
168-
return true // TODO
169-
}
170-
}
171-
}
172-
default:
173-
//println("\tunknown unwind instruction:", hex(uintptr(instr)))
174-
return true
175-
}
176-
}
177-
178-
// implicit finish instruction
179-
d.pc = d.lr
180-
return false
181-
}
182-
183-
func unwind_exidx(r *goroutine, pc uintptr, sp int) {
184-
// Scan trough the unwind table until we've found the right PC.
185-
// https://wiki.linaro.org/KenWerner/Sandbox/libunwind?action=AttachFile&do=get&target=libunwind-LDS.pdf
186-
// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038b/IHI0038B_ehabi.pdf
187-
188-
// Load .ARM.exidx from __exidx_start and __exidx_end and create a slice
189-
// with exactly the contents of it.
190-
// https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
191-
stext, etext := tinygo_get_code_bounds()
192-
exidx_start, exidx_end := tinygo_get_unwind_sections()
193-
size := (exidx_end - exidx_start) / 4
194-
exidx := (*[1 << 30]exidxEntry)(unsafe.Pointer(exidx_start))[:size/2:size/2]
195-
_ = exidx
196-
197-
d := decoder{pc: pc, sp: sp}
198-
199-
for d.sp >= 0 && d.sp < len(r.stack) && d.pc >= stext && d.pc < etext {
200-
entry := exidxEntry{}
201-
functionAddress := uintptr(0)
202-
entryNum := -1
203-
204-
// Do a linear scan for the right function.
205-
// We could improve here with a binary search when performance becomes
206-
// an issue, but a linear scan is easier to implement right now.
207-
//println("\nsearching for address:", hex(d.pc - 4))
208-
for i, e := range exidx {
209-
addr := e.address + exidx_start + uintptr(i) * 8 - (1 << 31)
210-
//println(" found address + command:", i, hex(addr), hex(e.command))
211-
if addr > d.pc - 4 {
212-
//println("\tcheck")
213-
break
214-
}
215-
entry = e
216-
functionAddress = addr
217-
entryNum = i
218-
}
219-
//println("using entry for address:", hex(functionAddress), "raw:", hex(entry.address), hex(entry.command))
220-
221-
if tinygo_print_stackitem(r, functionAddress, d.pc, d.sp) {
222-
// We're done: this is the last frame.
223-
return
224-
}
225-
226-
if entry.command == 1 {
227-
tinygo_unwind_fail(d.pc - 4, "not in unwind table")
228-
return
229-
} else if entry.command >> 31 == 1 {
230-
//println("function encoded inline:", hex(entry.command))
231-
// Obtain unwind instructions
232-
instructions := []byte{
233-
byte(entry.command >> 16),
234-
byte(entry.command >> 8),
235-
byte(entry.command)}
236-
_ = instructions
237-
if d.decode(r, instructions) {
238-
tinygo_unwind_fail(d.pc - 4, "unknown unwind instruction")
239-
return
240-
}
241-
} else {
242-
// Pointer to the exidx table.
243-
_ = entryNum
244-
//exidxAddress := entry.command + exidx_start + uintptr(entryNum) * 8 - (1 << 31) + 4
245-
//println("function encoded externally:", hex(exidxAddress), hex(entry.command), entryNum)
246-
247-
// Pointer to the personality function.
248-
//personalityAddressRelative := *(*uintptr)(unsafe.Pointer(exidxAddress))
249-
//personalityAddress := exidxAddress - ((1 << 31) - personalityAddressRelative)
250-
//println(hex(personalityAddressRelative), hex(personalityAddress))
251-
252-
// Now we're supposed to call the personality function, I think.
253-
254-
// Construct a byteslice from the pointer in `address`.
255-
// TODO: length of slice
256-
//instructions := (*[1 << 30]byte)(unsafe.Pointer(exidxAddress + 4))[:256:256]
257-
//if d.decode(r, instructions) {
258-
// break
259-
//}
260-
tinygo_unwind_fail(d.pc - 4, "external unwind instructions")
261-
return
262-
}
263-
}
264-
265-
tinygo_unwind_fail(d.pc - 4, "pc or sp out of range")
266-
}
267-
26857
func Panic(msg interface{}, fatal bool) {
26958
if !fatal {
27059
// TODO: actually unwind the stack and call __go_undefer where
@@ -280,17 +69,8 @@ func Panic(msg interface{}, fatal bool) {
28069
println()
28170

28271
println("\ngoroutine", GR.num, "[running]:")
283-
pc, _sp := tinygo_get_lr_sp()
284-
_sp_base := uintptr(unsafe.Pointer(&GR.stack[0]))
285-
sp := int((_sp - _sp_base) / 4)
28672

287-
if UNWINDER == "armdecode" {
288-
unwind_armdecode(GR, pc, sp)
289-
} else if UNWINDER == "exidx" {
290-
unwind_exidx(GR, pc, sp)
291-
} else {
292-
panic("unreachable")
293-
}
73+
unwind(GR)
29474

29575
if fatal {
29676
exit(1)

0 commit comments

Comments
 (0)