mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-09-11 15:09:32 +00:00
02021b5853
C malloc storage has explicit lifetime: it must remain allocated until free even when no GC-visible pointer references it. Treating it as an ordinary NoPtrs allocation breaks bare-metal C object graphs, while conservatively scanning arbitrary C bytes creates false Go roots. Add allocManual/freeManual so collectors can represent pointer-free, explicitly managed storage. Block GC keeps these objects permanently marked and releases their blocks on free; Boehm uses atomic uncollectable allocations; leaking and custom collectors provide equivalent behavior. Wasm keeps its allocation map only for validation and sizes, and WASIp2 realloc now copies min(oldSize, newSize). Bump the Boehm library cache version because enabling atomic uncollectable allocations changes its compiled flags and exported API. Also handle zero-size and overflowing allocations, serialize allocation registries, reject Go finalizers on manual storage, and add CGo regressions for C pointer graphs, hidden until-free allocations, repeated free/reuse, and allocation edge cases.
260 lines
8.3 KiB
Go
260 lines
8.3 KiB
Go
package main
|
|
|
|
/*
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
int fortytwo(void);
|
|
#include "main.h"
|
|
#include "test.h"
|
|
int mul(int, int);
|
|
#include <string.h>
|
|
#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 (
|
|
"runtime"
|
|
"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")))))
|
|
|
|
// malloc allocations remain live until free, even when C pointers are the
|
|
// only links between them.
|
|
mallocChain := C.makeMallocChain()
|
|
runtime.GC()
|
|
C.clobberMalloc()
|
|
println("malloc chain:", C.mallocChainValue(mallocChain))
|
|
|
|
// malloc lifetime ends at free, not when the allocation becomes invisible
|
|
// to the GC. Encode the address so neither Go nor C exposes a pointer root.
|
|
hiddenMallocChan := make(chan C.uintptr_t, 1)
|
|
hiddenMallocDone := make(chan struct{})
|
|
go func() {
|
|
hiddenMallocChan <- C.makeHiddenMalloc()
|
|
close(hiddenMallocDone)
|
|
}()
|
|
hiddenMalloc := <-hiddenMallocChan
|
|
<-hiddenMallocDone
|
|
C.clobberStack()
|
|
runtime.GC()
|
|
C.clobberMalloc()
|
|
println("hidden malloc:", C.hiddenMallocValue(hiddenMalloc))
|
|
C.freeHiddenMalloc(hiddenMalloc)
|
|
|
|
C.mallocFreeStress()
|
|
println("malloc/free stress: ok")
|
|
C.mallocZero()
|
|
println("malloc zero: ok")
|
|
println("calloc overflow:", C.callocOverflowReturnsNull() != 0)
|
|
|
|
// 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)))
|
|
}
|