From 794b546c49cb41f1912df8fb151c007f481ebd29 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 11 Jul 2026 20:42:47 +0200 Subject: [PATCH] Implemented function pointer calls --- lib/process/clone_linux.q | 4 +- lib/process/fork_linux.q | 2 +- lib/thread/thread.q | 1 + lib/thread/thread_linux.q | 20 ++++++++++ lib/thread/thread_linux_arm.q | 20 ---------- lib/thread/thread_linux_x86.q | 20 ---------- src/codegen/execute.go | 2 + src/codegen/executeCall.go | 9 ++--- src/codegen/executeCallPointer.go | 24 ++++++++++++ src/core/TypeFromTokens.go | 4 ++ src/core/evaluateCall.go | 7 ++++ src/core/evaluateCallPointer.go | 16 ++++++++ src/ssa/CallPointer.go | 63 +++++++++++++++++++++++++++++++ 13 files changed, 143 insertions(+), 49 deletions(-) create mode 100644 lib/thread/thread_linux.q delete mode 100644 lib/thread/thread_linux_arm.q delete mode 100644 lib/thread/thread_linux_x86.q create mode 100644 src/codegen/executeCallPointer.go create mode 100644 src/core/evaluateCallPointer.go create mode 100644 src/ssa/CallPointer.go diff --git a/lib/process/clone_linux.q b/lib/process/clone_linux.q index 90974f0..82cbb32 100644 --- a/lib/process/clone_linux.q +++ b/lib/process/clone_linux.q @@ -1,5 +1,5 @@ -clone3(args *CloneArgs, size uint) -> int { - return syscall(_clone3, args, size) +const { + CLONE_ARGS_SIZE = 88 } CloneArgs { diff --git a/lib/process/fork_linux.q b/lib/process/fork_linux.q index e1ce0df..6cd3af4 100644 --- a/lib/process/fork_linux.q +++ b/lib/process/fork_linux.q @@ -7,5 +7,5 @@ fork() -> int { exit_signal: SIGCHLD } - return clone3(args, 88) + return syscall(_clone3, args, CLONE_ARGS_SIZE) } \ No newline at end of file diff --git a/lib/thread/thread.q b/lib/thread/thread.q index 253ba00..87892ef 100644 --- a/lib/thread/thread.q +++ b/lib/thread/thread.q @@ -1,3 +1,4 @@ const { STACK_SIZE = 4096 + TLS_SIZE = 32 } \ No newline at end of file diff --git a/lib/thread/thread_linux.q b/lib/thread/thread_linux.q new file mode 100644 index 0000000..b0c86df --- /dev/null +++ b/lib/thread/thread_linux.q @@ -0,0 +1,20 @@ +import mem +import process + +create(func ()) -> (tid int) { + stack := mem.mmap(0, STACK_SIZE, mem.read|mem.write, mem.private|mem.anonymous, -1, 0) + tls := stack + STACK_SIZE - TLS_SIZE + args := tls - process.CLONE_ARGS_SIZE as *process.CloneArgs + args.flags = CLONE_THREAD | CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_IO | CLONE_SIGHAND | CLONE_SETTLS + args.stack = stack as uint64 + args.stack_size = STACK_SIZE - TLS_SIZE + args.tls = tls as uint64 + tid := syscall(process._clone3, args, process.CLONE_ARGS_SIZE) as int + + if tid == 0 { + func() + syscall(_exit, 0) + } + + return tid +} \ No newline at end of file diff --git a/lib/thread/thread_linux_arm.q b/lib/thread/thread_linux_arm.q deleted file mode 100644 index f427353..0000000 --- a/lib/thread/thread_linux_arm.q +++ /dev/null @@ -1,20 +0,0 @@ -import mem -import process - -create(func any) -> (tid int) { - stack := mem.mmap(0, STACK_SIZE, mem.read|mem.write, mem.private|mem.anonymous, -1, 0) - tls := stack + STACK_SIZE - 32 - entry := tls - 32 - [entry as *uint64] = func as uint64 - [entry + 16 as *uint64] = end as uint64 - args := entry - 88 as *process.CloneArgs - args.flags = CLONE_THREAD | CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_IO | CLONE_SIGHAND | CLONE_SETTLS - args.stack = stack as uint64 - args.stack_size = STACK_SIZE - 32 - 32 - args.tls = tls as uint64 - return process.clone3(args, 88) -} - -end() { - syscall(_exit, 0) -} \ No newline at end of file diff --git a/lib/thread/thread_linux_x86.q b/lib/thread/thread_linux_x86.q deleted file mode 100644 index 5acd919..0000000 --- a/lib/thread/thread_linux_x86.q +++ /dev/null @@ -1,20 +0,0 @@ -import mem -import process - -create(func any) -> (tid int) { - stack := mem.mmap(0, STACK_SIZE, mem.read|mem.write, mem.private|mem.anonymous, -1, 0) - tls := stack + STACK_SIZE - 32 - entry := tls - 16 - [entry as *uint64] = func as uint64 - [entry + 8 as *uint64] = end as uint64 - args := entry - 88 as *process.CloneArgs - args.flags = CLONE_THREAD | CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_IO | CLONE_SIGHAND | CLONE_SETTLS - args.stack = stack as uint64 - args.stack_size = STACK_SIZE - 32 - 16 - args.tls = tls as uint64 - return process.clone3(args, 88) -} - -end() { - syscall(_exit, 0) -} \ No newline at end of file diff --git a/src/codegen/execute.go b/src/codegen/execute.go index 97fafd2..6c3bba0 100644 --- a/src/codegen/execute.go +++ b/src/codegen/execute.go @@ -19,6 +19,8 @@ func (f *Function) execute(step *Step) { f.executeCall(step, instr) case *ssa.CallExtern: f.executeCallExtern(step, instr) + case *ssa.CallPointer: + f.executeCallPointer(step, instr) case *ssa.Cas: f.executeCas(step, instr) case *ssa.Copy: diff --git a/src/codegen/executeCall.go b/src/codegen/executeCall.go index c2466f1..01d647f 100644 --- a/src/codegen/executeCall.go +++ b/src/codegen/executeCall.go @@ -7,13 +7,10 @@ import ( func (f *Function) executeCall(step *Step, instr *ssa.Call) { f.moveValuesToRegisters(instr.Arguments, f.CPU.Call.In) - functionPointer, isPointer := f.ValueToStep[instr.Func] - if isPointer { - f.Assembler.Append(&asm.CallRegister{Address: functionPointer.Register}) - } else { - f.Assembler.Append(&asm.Call{Label: instr.Func.String()}) - } + f.Assembler.Append(&asm.Call{ + Label: instr.Func.String(), + }) if step.Register == -1 || step.Register == f.CPU.Call.Out[0] { return diff --git a/src/codegen/executeCallPointer.go b/src/codegen/executeCallPointer.go new file mode 100644 index 0000000..c82194e --- /dev/null +++ b/src/codegen/executeCallPointer.go @@ -0,0 +1,24 @@ +package codegen + +import ( + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/ssa" +) + +func (f *Function) executeCallPointer(step *Step, instr *ssa.CallPointer) { + f.moveValuesToRegisters(instr.Arguments[1:], f.CPU.Call.In) + functionPointer := f.ValueToStep[instr.Arguments[0]] + + f.Assembler.Append(&asm.CallRegister{ + Address: functionPointer.Register, + }) + + if step.Register == -1 || step.Register == f.CPU.Call.Out[0] { + return + } + + f.Assembler.Append(&asm.Move{ + Destination: step.Register, + Source: f.CPU.Call.Out[0], + }) +} \ No newline at end of file diff --git a/src/core/TypeFromTokens.go b/src/core/TypeFromTokens.go index ea87ddb..35be112 100644 --- a/src/core/TypeFromTokens.go +++ b/src/core/TypeFromTokens.go @@ -78,6 +78,10 @@ func (env *Environment) TypeFromTokens(tokens token.List, file *fs.File) (types. return env.Slice(typ), nil } + if len(tokens) >= 2 && tokens[0].Kind == token.GroupStart && tokens[1].Kind == token.GroupEnd { + return &types.Function{}, nil + } + if tokens[0].Kind != token.Identifier { return nil, errors.New(&UnknownType{Name: tokens.StringFrom(file.Bytes)}, file, tokens[0]) } diff --git a/src/core/evaluateCall.go b/src/core/evaluateCall.go index 08e872a..1b93fba 100644 --- a/src/core/evaluateCall.go +++ b/src/core/evaluateCall.go @@ -4,6 +4,7 @@ import ( "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/ssa" + "git.urbach.dev/cli/q/src/types" ) // evaluateCall converts a call expression to an SSA value. @@ -23,6 +24,12 @@ func (f *Function) evaluateCall(expr *expression.Expression) (ssa.Value, error) ssaFunc, isFunction := funcValue.(*ssa.Function) if !isFunction { + _, isFunctionPointer := funcValue.Type().(*types.Function) + + if isFunctionPointer { + return f.evaluateCallPointer(funcValue, expr.Source()) + } + return nil, errors.New(&TypeMismatch{Expected: "function", Encountered: funcValue.Type().Name()}, f.File, identifier.Source()) } diff --git a/src/core/evaluateCallPointer.go b/src/core/evaluateCallPointer.go new file mode 100644 index 0000000..0d63cc1 --- /dev/null +++ b/src/core/evaluateCallPointer.go @@ -0,0 +1,16 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/ssa" + "git.urbach.dev/cli/q/src/token" +) + +// evaluateCallPointer converts a function pointer call to an SSA value. +func (f *Function) evaluateCallPointer(funcValue ssa.Value, source token.Source) (ssa.Value, error) { + call := f.Append(&ssa.CallPointer{ + Arguments: ssa.Arguments{funcValue}, + Source: source, + }) + + return call, nil +} \ No newline at end of file diff --git a/src/ssa/CallPointer.go b/src/ssa/CallPointer.go new file mode 100644 index 0000000..df2cc4f --- /dev/null +++ b/src/ssa/CallPointer.go @@ -0,0 +1,63 @@ +package ssa + +import ( + "fmt" + + "git.urbach.dev/cli/q/src/types" +) + +// CallPointer is a call using a function pointer. +type CallPointer struct { + Arguments + Liveness + Source +} + +// Equals returns true if the calls are equal. +func (a *CallPointer) Equals(v Value) bool { + b, sameType := v.(*CallPointer) + + if !sameType { + return false + } + + return a.Arguments.Equals(b.Arguments) +} + +// IsPure returns false because a function call can have side effects. +func (c *CallPointer) IsPure() bool { return false } + +// String returns a human-readable representation of the call. +func (c *CallPointer) String() string { + return fmt.Sprintf("%p(%s)", c.Arguments[0], c.Arguments[1:].String()) +} + +// Type returns the return type of the function. +func (c *CallPointer) Type() types.Type { + output := c.Arguments[0].Type().(*types.Function).Output + + if len(output) == 0 { + return types.Void + } + + if len(output) == 1 { + return output[0] + } + + tuple := &types.Tuple{} + + for _, typ := range output { + structure, isStruct := types.Unwrap(typ).(*types.Struct) + + if !isStruct { + tuple.Types = append(tuple.Types, typ) + continue + } + + for _, field := range structure.Fields { + tuple.Types = append(tuple.Types, field.Type) + } + } + + return tuple +} \ No newline at end of file -- 2.51.2