Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 20 additions & 21 deletions docs/typescript-parity-report.md

Large diffs are not rendered by default.

21 changes: 17 additions & 4 deletions internal/backend/llvm/emit.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ func EmitWithOptions(module ir.Module, options Options) (string, error) {
out.WriteString("declare i32 @scriptgo_string_from_bigint(i64, ptr)\n")
out.WriteString("declare i32 @scriptgo_string_from_bigint_locale(i64, ptr)\n")
out.WriteString("declare i32 @scriptgo_error_to_string(ptr, ptr)\n")
out.WriteString("declare i32 @scriptgo_error_capture_stack(ptr, ptr, ptr)\n")
out.WriteString("declare i32 @scriptgo_string_from_bool(i32, ptr)\n")
out.WriteString("declare i32 @scriptgo_string_slice(ptr, double, double, ptr)\n")
out.WriteString("declare i32 @scriptgo_string_trim(ptr, ptr)\n")
Expand Down Expand Up @@ -473,6 +474,8 @@ func EmitWithOptions(module ir.Module, options Options) (string, error) {
out.WriteString("declare i32 @scriptgo_net_socket_write(double, ptr, double, ptr)\n")
out.WriteString("declare i32 @scriptgo_net_socket_read(double, double, ptr, ptr)\n")
out.WriteString("declare i32 @scriptgo_net_socket_close(double)\n")
out.WriteString("declare i32 @scriptgo_net_socket_set_nodelay(double, double)\n")
out.WriteString("declare i32 @scriptgo_net_socket_set_keepalive(double, double, double)\n")
out.WriteString("declare i32 @scriptgo_net_server_listen(ptr, double, double, ptr)\n")
out.WriteString("declare i32 @scriptgo_net_server_accept(double, ptr, ptr, ptr)\n\n")
out.WriteString("declare i32 @scriptgo_dgram_socket_create(double, ptr)\n")
Expand Down Expand Up @@ -935,7 +938,17 @@ func EmitWithOptions(module ir.Module, options Options) (string, error) {
out.WriteString("declare i32 @scriptgo_os_tmpdir(ptr)\n")
out.WriteString("declare i32 @scriptgo_os_uptime(ptr)\n")
out.WriteString("declare i32 @scriptgo_os_totalmem(ptr)\n")
out.WriteString("declare i32 @scriptgo_os_freemem(ptr)\n\n")
out.WriteString("declare i32 @scriptgo_os_freemem(ptr)\n")
out.WriteString("declare i32 @scriptgo_os_available_parallelism(ptr)\n")
out.WriteString("declare i32 @scriptgo_os_hostname(ptr)\n")
out.WriteString("declare i32 @scriptgo_os_loadavg(ptr)\n")
out.WriteString("declare i32 @scriptgo_os_cpus(ptr)\n")
out.WriteString("declare i32 @scriptgo_os_network_interfaces(ptr)\n")
out.WriteString("declare i32 @scriptgo_os_user_info(ptr)\n")
out.WriteString("declare i32 @scriptgo_os_machine(ptr)\n")
out.WriteString("declare i32 @scriptgo_os_version(ptr)\n")
out.WriteString("declare i32 @scriptgo_os_get_priority(double, ptr)\n")
out.WriteString("declare i32 @scriptgo_os_set_priority(double, double)\n\n")
out.WriteString("declare i32 @scriptgo_process_pid(ptr)\n")
out.WriteString("declare i32 @scriptgo_process_ppid(ptr)\n")
out.WriteString("declare i32 @scriptgo_process_version(ptr)\n\n")
Expand Down Expand Up @@ -1164,7 +1177,7 @@ func emitFunction(function ir.Function, functions map[string]ir.Function, string
name := function.Name
var out strings.Builder
if name == "main" {
out.WriteString("define i32 @main(i32 %argc, ptr %argv) nounwind")
out.WriteString("define i32 @main(i32 %argc, ptr %argv) nounwind \"frame-pointer\"=\"all\"")
} else {
out.WriteString(fmt.Sprintf("define internal %s @%s(", returnType, mangleFunctionName(name)))
parameterIndex := 0
Expand All @@ -1181,9 +1194,9 @@ func emitFunction(function ir.Function, functions map[string]ir.Function, string
parameterIndex++
}
if strings.HasSuffix(name, "_constructor") && len(function.Body) <= 15 {
out.WriteString(") alwaysinline nounwind")
out.WriteString(") alwaysinline nounwind \"frame-pointer\"=\"all\"")
} else {
out.WriteString(") nounwind")
out.WriteString(") nounwind \"frame-pointer\"=\"all\"")
}
}
if debug != nil {
Expand Down
12 changes: 12 additions & 0 deletions internal/backend/llvm/instructions_calls.go
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,18 @@ func (e *functionEmitter) emitCall(out *strings.Builder, instruction ir.Instruct
}
return nil
}
if strings.HasPrefix(instruction.Callee, "__error.") {
if err := e.emitErrorIntrinsic(out, instruction); err != nil {
return err
}
if instruction.Result != "" {
e.types[instruction.Result] = instruction.Type
if instruction.Type == ir.TypeString {
e.ownedStrings = append(e.ownedStrings, instruction.Result)
}
}
return nil
}
if strings.HasPrefix(instruction.Callee, "__tty.") {
if err := e.emitTtyIntrinsic(out, instruction); err != nil {
return err
Expand Down
8 changes: 7 additions & 1 deletion internal/backend/llvm/instructions_ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ func (e *functionEmitter) emitCompare(out *strings.Builder, instruction ir.Instr
out.WriteString(fmt.Sprintf(" %%%s = icmp %s i64 %%%s, %%%s\n", instruction.Result, predicate, arg0, arg1))
return nil
}
if isFunctionType(leftType) {
if isFunctionType(leftType) || isFunctionType(rightType) || leftType == ir.TypeClosure || rightType == ir.TypeClosure {
if instruction.Operator == "==" || instruction.Operator == "!=" {
p0Null := fmt.Sprintf("%s.p0_null.%d", instruction.Result, e.loadCounter)
p0Undef := fmt.Sprintf("%s.p0_undef.%d", instruction.Result, e.loadCounter)
Expand Down Expand Up @@ -870,6 +870,12 @@ func (e *functionEmitter) emitCheckedCast(out *strings.Builder, instruction ir.I
out.WriteString(fmt.Sprintf(" %%%s = icmp eq i32 %%%s, 7\n", cmpFn, tagVar))
out.WriteString(fmt.Sprintf(" %%%s = icmp eq i32 %%%s, 5\n", cmpObj, tagVar))
out.WriteString(fmt.Sprintf(" %%%s = or i1 %%%s, %%%s\n", cmpVar, cmpFn, cmpObj))
} else if expectedTag == 5 {
cmpObj := fmt.Sprintf("cast.cmp_obj.%d", id)
cmpFn := fmt.Sprintf("cast.cmp_fn.%d", id)
out.WriteString(fmt.Sprintf(" %%%s = icmp eq i32 %%%s, 5\n", cmpObj, tagVar))
out.WriteString(fmt.Sprintf(" %%%s = icmp eq i32 %%%s, 7\n", cmpFn, tagVar))
out.WriteString(fmt.Sprintf(" %%%s = or i1 %%%s, %%%s\n", cmpVar, cmpObj, cmpFn))
} else {
out.WriteString(fmt.Sprintf(" %%%s = icmp eq i32 %%%s, %d\n", cmpVar, tagVar, expectedTag))
}
Expand Down
24 changes: 24 additions & 0 deletions internal/backend/llvm/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,30 @@ func (e *functionEmitter) emitNetIntrinsic(out *strings.Builder, instruction ir.
fmt.Fprintf(out, " call void @scriptgo_runtime_abort_if_failed(i32 %%%s)\n", status)
return nil

case "__net.socketSetNoDelay":
// Args: [fd: number, no_delay: number] -> void
if len(instruction.Args) < 2 {
return fmt.Errorf("net.socketSetNoDelay requires 2 arguments")
}
status := fmt.Sprintf("runtime.status.%d", e.runtimeStatus)
e.runtimeStatus++
fmt.Fprintf(out, " %%%s = call i32 @scriptgo_net_socket_set_nodelay(double %%%s, double %%%s)\n",
status, resolvedArgs[0], resolvedArgs[1])
fmt.Fprintf(out, " call void @scriptgo_runtime_abort_if_failed(i32 %%%s)\n", status)
return nil

case "__net.socketSetKeepAlive":
// Args: [fd: number, enable: number, initial_delay: number] -> void
if len(instruction.Args) < 3 {
return fmt.Errorf("net.socketSetKeepAlive requires 3 arguments")
}
status := fmt.Sprintf("runtime.status.%d", e.runtimeStatus)
e.runtimeStatus++
fmt.Fprintf(out, " %%%s = call i32 @scriptgo_net_socket_set_keepalive(double %%%s, double %%%s, double %%%s)\n",
status, resolvedArgs[0], resolvedArgs[1], resolvedArgs[2])
fmt.Fprintf(out, " call void @scriptgo_runtime_abort_if_failed(i32 %%%s)\n", status)
return nil

case "__net.serverListen":
// Args: [host: string, port: number, backlog: number] -> server_fd: number
if len(instruction.Args) < 3 {
Expand Down
51 changes: 49 additions & 2 deletions internal/backend/llvm/sys.go
Original file line number Diff line number Diff line change
Expand Up @@ -991,7 +991,9 @@ func (e *functionEmitter) emitWebIntrinsic(out *strings.Builder, instruction ir.

func (e *functionEmitter) emitOsIntrinsic(out *strings.Builder, instruction ir.Instruction) error {
switch instruction.Callee {
case "__os.platform", "__os.arch", "__os.homedir", "__os.type", "__os.release", "__os.tmpdir":
case "__os.platform", "__os.arch", "__os.homedir", "__os.type", "__os.release", "__os.tmpdir",
"__os.hostname", "__os.loadavg", "__os.cpus", "__os.network_interfaces", "__os.user_info",
"__os.machine", "__os.version":
if len(instruction.Args) != 0 || instruction.Type != ir.TypeString {
return fmt.Errorf("%s has invalid signature", instruction.Callee)
}
Expand All @@ -1004,7 +1006,7 @@ func (e *functionEmitter) emitOsIntrinsic(out *strings.Builder, instruction ir.I
fmt.Fprintf(out, " call void @scriptgo_runtime_abort_if_failed(i32 %%%s)\n", status)
fmt.Fprintf(out, " %%%s = load ptr, ptr %%%s\n", instruction.Result, slot)
return nil
case "__os.uptime", "__os.totalmem", "__os.freemem":
case "__os.uptime", "__os.totalmem", "__os.freemem", "__os.available_parallelism":
if len(instruction.Args) != 0 || instruction.Type != ir.TypeNumber {
return fmt.Errorf("%s has invalid signature", instruction.Callee)
}
Expand All @@ -1017,6 +1019,27 @@ func (e *functionEmitter) emitOsIntrinsic(out *strings.Builder, instruction ir.I
fmt.Fprintf(out, " call void @scriptgo_runtime_abort_if_failed(i32 %%%s)\n", status)
fmt.Fprintf(out, " %%%s = load double, ptr %%%s\n", instruction.Result, slot)
return nil
case "__os.get_priority":
if len(instruction.Args) != 1 || instruction.Type != ir.TypeNumber {
return fmt.Errorf("%s has invalid signature", instruction.Callee)
}
slot := instruction.Result + ".slot"
status := fmt.Sprintf("runtime.status.%d", e.runtimeStatus)
e.runtimeStatus++
fmt.Fprintf(out, " %%%s = alloca double\n", slot)
fmt.Fprintf(out, " %%%s = call i32 @scriptgo_os_get_priority(double %%%s, ptr %%%s)\n", status, instruction.Args[0], slot)
fmt.Fprintf(out, " call void @scriptgo_runtime_abort_if_failed(i32 %%%s)\n", status)
fmt.Fprintf(out, " %%%s = load double, ptr %%%s\n", instruction.Result, slot)
return nil
case "__os.set_priority":
if len(instruction.Args) != 2 || instruction.Type != ir.TypeVoid {
return fmt.Errorf("%s has invalid signature", instruction.Callee)
}
status := fmt.Sprintf("runtime.status.%d", e.runtimeStatus)
e.runtimeStatus++
fmt.Fprintf(out, " %%%s = call i32 @scriptgo_os_set_priority(double %%%s, double %%%s)\n", status, instruction.Args[0], instruction.Args[1])
fmt.Fprintf(out, " call void @scriptgo_runtime_abort_if_failed(i32 %%%s)\n", status)
return nil
default:
return fmt.Errorf("unknown os intrinsic %q", instruction.Callee)
}
Expand Down Expand Up @@ -1382,3 +1405,27 @@ func (e *functionEmitter) emitStreamIntrinsic(out *strings.Builder, instruction
return fmt.Errorf("unknown stream intrinsic %q", instruction.Callee)
}
}

func (e *functionEmitter) emitErrorIntrinsic(out *strings.Builder, instruction ir.Instruction) error {
resolvedArgs := make([]string, len(instruction.Args))
for i, arg := range instruction.Args {
resolvedArgs[i] = e.resolveArg(out, arg)
}
switch instruction.Callee {
case "__error.captureStack":
if len(instruction.Args) < 2 {
return fmt.Errorf("__error.captureStack requires 2 arguments")
}
slot := instruction.Result + ".slot"
fmt.Fprintf(out, " %%%s = alloca ptr\n", slot)
status := fmt.Sprintf("runtime.status.%d", e.runtimeStatus)
e.runtimeStatus++
fmt.Fprintf(out, " %%%s = call i32 @scriptgo_error_capture_stack(ptr %%%s, ptr %%%s, ptr %%%s)\n",
status, resolvedArgs[0], resolvedArgs[1], slot)
fmt.Fprintf(out, " call void @scriptgo_runtime_abort_if_failed(i32 %%%s)\n", status)
fmt.Fprintf(out, " %%%s = load ptr, ptr %%%s\n", instruction.Result, slot)
return nil
default:
return fmt.Errorf("unknown error intrinsic %q", instruction.Callee)
}
}
44 changes: 38 additions & 6 deletions internal/compiler/testdata/corpus/api/http.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
// ScriptGo Corpus: Http Standard Builtin APIs
// Consolidated test suite with inline assertions.

import {
METHODS,
STATUS_CODES,
maxHeaderSize,
validateHeaderName,
validateHeaderValue
validateHeaderValue,
globalAgent,
createServer,
get,
request,
setMaxIdleHTTPParsers,
WebSocket,
Agent
} from "node:http";

// @api: METHODS
// @api: http.METHODS
// @expect: true
// @expect: GET
console.log(METHODS.length > 0);
console.log(METHODS[6]);

// @api: STATUS_CODES
// @api: http.STATUS_CODES
// @expect: OK
// @expect: Not Found
console.log(STATUS_CODES["200"]);
Expand All @@ -31,6 +36,33 @@ console.log("valid");
validateHeaderValue("Content-Type", "application/json");
console.log("val-ok");

// @api: maxHeaderSize
// @api: http.maxHeaderSize
// @expect: 16384
console.log(maxHeaderSize);

// @api: http.globalAgent
// @expect: true
console.log(globalAgent instanceof Agent);

// @api: http.setMaxIdleHTTPParsers
// @expect: max-parsers-ok
setMaxIdleHTTPParsers(500);
console.log("max-parsers-ok");

// @api: http.createServer
// @expect: true
const srv = createServer();
console.log(typeof srv.listen === "function");
srv.close();

// @api: http.get
// @expect: true
console.log(typeof get === "function");

// @api: http.request
// @expect: true
console.log(typeof request === "function");

// @api: http.WebSocket
// @expect: true
console.log(typeof WebSocket === "function");
68 changes: 68 additions & 0 deletions internal/compiler/testdata/corpus/api/http_agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// ScriptGo Corpus: Node.js http.Agent Parity Tests
import { Agent, ClientRequest } from "node:http";
import { Socket } from "node:net";

// @api: http.Agent
// @expect: true
const agent = new Agent({
keepAlive: true,
maxSockets: 50,
maxFreeSockets: 10,
maxTotalSockets: 100,
});
console.log(agent instanceof Agent);

// @api: http.Agent.maxSockets
// @expect: 50
console.log(agent.maxSockets);

// @api: http.Agent.maxFreeSockets
// @expect: 10
console.log(agent.maxFreeSockets);

// @api: http.Agent.maxTotalSockets
// @expect: 100
console.log(agent.maxTotalSockets);

// @api: http.Agent.freeSockets
// @expect: true
console.log(typeof agent.freeSockets === "object" && agent.freeSockets !== null);

// @api: http.Agent.sockets
// @expect: true
console.log(typeof agent.sockets === "object" && agent.sockets !== null);

// @api: http.Agent.requests
// @expect: true
console.log(typeof agent.requests === "object" && agent.requests !== null);

// @api: http.Agent.getName
// @expect: localhost:8080:127.0.0.1
console.log(agent.getName({ host: "localhost", port: 8080, localAddress: "127.0.0.1" }));

// @api: http.Agent.createConnection
// @expect: true
const sock = agent.createConnection({ host: "127.0.0.1", port: 80 });
console.log(sock instanceof Socket);
sock.destroy();

// @api: http.Agent.keepSocketAlive
// @expect: true
const testSock = new Socket();
console.log(agent.keepSocketAlive(testSock));
testSock.destroy();

// @api: http.Agent.reuseSocket
// @expect: true
const req = new ClientRequest({ host: "localhost", path: "/" });
req.on("error", () => {});
const reuseSock = new Socket();
agent.reuseSocket(reuseSock, req);
console.log(req.reusedSocket === true);
reuseSock.destroy();
req.destroy();

// @api: http.Agent.destroy
// @expect: true
agent.destroy();
console.log(Object.keys(agent.sockets).length === 0);
Loading
Loading